1<!DOCTYPE html> 2<html lang="en"> 3<head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <title>FlyGym Platform | Juvion Health Sciences</title> 7 8 <meta name="description" content="FlyGym is Juvion's proprietary robotics and AI platform for high-throughput screening of compounds using Drosophila melanogaster as a validated model for human aging."> 9 <meta name="robots" content="index, follow"> 10 <link rel="canonical" href="https://juvionhs.com/flygym"> 11 12 <meta property="og:type" content="website"> 13 <meta property="og:url" content="https://juvionhs.com/flygym"> 14 <meta property="og:title" content="FlyGym Platform | Juvion Health Sciences"> 15 <meta property="og:description" content="High-throughput screening platform using Drosophila melanogaster to accelerate healthy aging therapeutics."> 16 <meta property="og:image" content="https://juvionhs.com/assets/JuvionLogo.png"> 17 18 <!-- Twitter Card --> 19 <meta name="twitter:card" content="summary_large_image"> 20 <meta name="twitter:title" content="FlyGym Platform | Juvion Health Sciences"> 21 <meta name="twitter:description" content="High-throughput screening platform using Drosophila melanogaster to accelerate healthy aging therapeutics."> 22 <meta name="twitter:image" content="https://juvionhs.com/assets/JuvionLogo.png"> 23 24
24<script src="https://cdn.tailwindcss.com"></script>
24 25 <link rel="preconnect" href="https://fonts.googleapis.com"> 26 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> 27 <link rel="preload" href="https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400;0,500;0,600;0,700;1,400;1,600&family=Inter:wght@300;400;500;600&display=swap" as="style" onload="this.onload=null;this.rel='stylesheet'"> 28 <noscript><link href="https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400;0,500;0,600;0,700;1,400;1,600&family=Inter:wght@300;400;500;600&display=swap" rel="stylesheet"></noscript> 29 <link rel="stylesheet" href="styles.css"> 30 <link rel="icon" type="image/png" href="assets/JuvionLogo.png"> 31 <link rel="apple-touch-icon" href="assets/JuvionLogo.png"> 32
32<script> 33 tailwind.config = { 34 theme: { 35 extend: { 36 colors: { brand: '#fe6603' }, 37 fontFamily: { 38 display: ['"Playfair Display"', 'Georgia', 'serif'], 39 sans: ['Inter', 'system-ui', 'sans-serif'], 40 } 41 } 42 } 43 } 44 </script>
44 45
45<script src="https://cdn.jsdelivr.net/npm/chart.js@4/dist/chart.umd.min.js"></script>
45 46
46<script defer src="/_vercel/speed-insights/script.js"></script>
46 47
47<script type="application/ld+json"> 48 {"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[ 49 {"@type":"ListItem","position":1,"name":"Home","item":"https://juvionhs.com/"}, 50 {"@type":"ListItem","position":2,"name":"FlyGym","item":"https://juvionhs.com/flygym"} 51 ]} 52 </script>
52 53</head> 54<body class="bg-[#080808] text-[#F2EFE9] font-sans antialiased"> 55 56 <div class="grain-overlay" aria-hidden="true"></div> 57 58 <!-- ââ NAVIGATION âââââââââââââââââââââââââââââââââââââââââââ --> 59 <nav id="nav"> 60 <div class="nav-inner"> 61 <a href="/" class="nav-brand"> 62 <img src="assets/JuvionLogo.png" class="nav-logo-icon" alt="Juvion Health Sciences"> 63 <span class="logo-sub" style="margin-left:0">Juvion Health Sciences</span> 64 </a> 65 <div class="nav-links"> 66 <a href="/" class="nav-link">Home</a> 67 <a href="/products" class="nav-link">Products</a> 68 <a href="/flygym" class="nav-link">FlyGym & Services</a> 69 <a href="/news" class="nav-link">News & Publications</a> 70 <a href="/career" class="nav-link">Careers</a> 71 <a href="/contact" class="nav-cta">Contact</a> 72 </div> 73 <button class="nav-burger" id="navBurger" aria-label="Menu" aria-expanded="false"> 74 <span></span><span></span><span></span> 75 </button> 76 </div> 77 <div class="nav-mobile" id="navMobile"> 78 <a href="/" class="nav-mobile-link">Home</a> 79 <a href="/products" class="nav-mobile-link">Products</a> 80 <a href="/flygym" class="nav-mobile-link">FlyGym & Services</a> 81 <a href="/news" class="nav-mobile-link">News & Publications</a> 82 <a href="/career" class="nav-mobile-link">Careers</a> 83 <a href="/contact" class="nav-mobile-link">Contact</a> 84 <div class="lang-switcher lang-switcher--mobile"> 85 <a class="lang-btn" data-lang="en" href="/">EN</a> 86 <a class="lang-btn" data-lang="fr" href="/fr/">FR</a> 87 <a class="lang-btn" data-lang="de" href="/de/">DE</a> 88 </div> 89 </div> 90 </nav> 91 92 <!-- ââ PAGE SUBNAV âââââââââââââââââââââââââââââââââââââââââ --> 93 <div class="page-subnav" id="pageSubnav"> 94 <div class="page-subnav-inner"> 95 <a href="#what-is" class="page-subnav-link">What is FlyGym</a> 96 <a href="#measures" class="page-subnav-link">Measures</a> 97 <a href="#why-drosophila" class="page-subnav-link">Why Drosophila</a> 98 <a href="#work-with-us" class="page-subnav-link">Partner with Us</a> 99 100 <div class="lang-switcher" id="langSwitcher"> 101 <a class="lang-btn" data-lang="en" href="/">EN</a> 102 <a class="lang-btn" data-lang="fr" href="/fr/">FR</a> 103 <a class="lang-btn" data-lang="de" href="/de/">DE</a> 104 </div> 105 </div> 106 </div> 107 108 <!-- ââ PAGE HERO ââââââââââââââââââââââââââââââââââââââââââââ --> 109 <section class="page-hero page-hero--split"> 110 <div class="container"> 111 <div class="page-hero-inner reveal"> 112 113 <!-- Left: text --> 114 <div class="page-hero-text"> 115 <div class="page-breadcrumb"> 116 <a href="/">Home</a> 117 <span>â</span> 118 <span>FlyGym Platform</span> 119 </div> 120 <h1 class="page-hero-heading"><em>FlyGym</em> Platform</h1> 121 <p class="page-hero-sub">Brain function emerges from whole-system interaction over time. FlyGym captures that complexity earlier, reducing animal use and accelerating candidate selection before the clinic.</p> 122 </div> 123 124 <!-- Right: fly-tube simulation --> 125 <div class="fly-tube-wrap" aria-hidden="true"> 126 <svg id="flyTubeSvg" class="fly-tube-svg" viewBox="0 0 460 300" xmlns="http://www.w3.org/2000/svg"> 127 <defs> 128 <symbol id="fly-shape" viewBox="0 0 230 230" overflow="visible"> 129 <g transform="translate(0,-236.14582)"> 130 <path style="fill:none;stroke:currentColor;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" d="m 139.21827,332.03946 37.28607,37.28607 13.98227,32.62531"/> 131 <path style="fill:none;stroke:currentColor;stroke-width:3;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" d="m 148.53979,313.39643 h 55.92909"/> 132 <path style="fill:currentColor;fill-opacity:1;stroke:currentColor;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" d="m 115.91448,420.59387 c -27.964549,0 -27.964549,-55.9291 -27.964549,-55.9291 0,0 0,-23.30379 27.964549,-23.30379 27.96455,0 27.96455,23.30379 27.96455,23.30379 0,0 0,55.9291 -27.96455,55.9291 z"/> 133 <path style="fill:currentColor;stroke:none;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" d="m 90.746386,387.96857 4.660758,16.77872 5.592906,9.32152 8.38937,5.59291 8.38936,0.93215 7.45722,-2.79646 4.66076,-3.7286 6.52506,-9.32152 3.7286,-10.25367 0.93216,-6.52505 z"/>
133 134 <path style="fill:none;stroke:currentColor;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" d="M 87.949931,360.00401 H 143.87903"/> 135 <path style="fill:none;stroke:currentColor;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" d="M 87.949931,373.98628 H 143.87903"/> 136 <path style="fill:none;stroke:currentColor;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" d="M 90.746386,387.96857 H 141.08258"/> 137 <circle style="fill:currentColor;fill-opacity:1;stroke:currentColor;stroke-width:3;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" cx="115.91447" cy="311.53214" r="33.593773"/> 138 <path style="fill:currentColor;fill-opacity:1;stroke:currentColor;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" d="M 115.91448,276.11037 H 87.949931 c 0,0 0,-23.30379 27.964549,-23.30379 27.96455,0 27.96455,23.30379 27.96455,23.30379 h -27.96455"/> 139 <path style="fill:currentColor;fill-opacity:1;stroke:currentColor;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" d="m 143.87903,276.11037 c -4.93874,2.64313 -10.58217,-4.86995 -13.98227,-9.32152 -2.98209,-3.90429 -7.99881,-10.37758 -4.66076,-13.98227 4.35227,-4.69993 14.70095,-0.38817 18.64303,4.66076 3.82437,4.89817 5.47902,15.71074 0,18.64303 z"/> 140 <path style="fill:none;fill-opacity:1;stroke:currentColor;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" d="m 147.93997,292.1488 c 17.27296,11.6661 34.93169,30.24164 47.31396,49.38253 13.61416,21.0452 26.42826,44.70528 28.1967,69.70766 0.57047,8.06547 1.38507,19.91387 -5.84018,23.54332 -15.82876,7.95123 -35.01044,-9.68299 -49.01548,-20.52874 -13.54969,-10.49312 -23.42938,-25.61608 -31.09519,-40.94366 -4.90011,-9.79761 -9.87174,-20.68104 -9.0182,-31.60237 0.87346,-11.17613 12.78439,-19.3854 14.1408,-30.51324 0.59445,-4.87675 -4.86552,-10.01037 -2.80345,-14.46953 1.30417,-2.82021 5.54614,-6.31504 8.12104,-4.57597 z"/> 141 <path style="fill:none;stroke:currentColor;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" d="m 140.17344,295.45508 c -0.28226,0.41434 23.32086,56.47482 30.37499,82.38954 3.28979,12.08567 -0.22617,37.57556 -0.22617,37.57556"/> 142 <path style="fill:none;stroke:currentColor;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" d="m 142.96425,292.81258 c 0,0 22.46111,55.68145 35.8083,82.53787 10.37137,20.86867 34.31978,60.90775 34.31978,60.90775"/> 143 <path style="fill:none;stroke:currentColor;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" d="m 179.23015,376.16257 -8.68172,1.68205"/> 144 <path style="fill:none;stroke:currentColor;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" d="m 143.77635,292.35499 27.55892,50.80798 c 21.5446,28.77409 38.94787,56.44409 52.10821,82.98285"/> 145 <path style="fill:none;stroke:currentColor;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" d="m 171.33527,343.16297 -5.68471,3.20319"/> 146 <path style="fill:none;stroke:currentColor;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" d="m 160.24985,321.5907 48.87979,43.07407"/> 147 <path style="fill:none;stroke:currentColor;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" d="m 139.21827,285.43188 27.96455,-32.6253 18.64303,-4.66076"/> 148 <path style="fill:none;stroke:currentColor;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" d="M 92.610689,332.03946 55.324625,369.32553 41.34235,401.95084"/> 149 <path style="fill:none;stroke:currentColor;stroke-width:3;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" d="M 83.289174,313.39643 H 27.360077"/> 150 <path style="fill:currentColor;fill-opacity:1;stroke:currentColor;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" d="m 87.949931,276.11037 c 4.938735,2.64313 10.582177,-4.86995 13.982279,-9.32152 2.98208,-3.90429 7.9988,-10.37758 4.66076,-13.98227 -4.35229,-4.69993 -14.70095,-0.38817 -18.643039,4.66076 -3.824375,4.89817 -5.479029,15.71074 0,18.64303 z"/> 151 <path style="fill:none;fill-opacity:1;stroke:currentColor;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" d="m 83.888995,292.1488 c -17.272975,11.6661 -34.931693,30.24164 -47.313966,49.38253 -13.614159,21.0452 -26.42826,44.70528 -28.1966917,69.70766 -0.5704768,8.06547 -1.3850841,19.91387 5.8401817,23.54332 15.828755,7.95123 35.010431,-9.68299 49.015477,-20.52874 13.549691,-10.49312 23.42938,-25.61608 31.095189,-40.94366 4.900098,-9.79761 9.871735,-20.68104 9.018195,-31.60237 -0.87346,-11.17613 -12.784386,-19.3854 -14.140796,-30.51324 -0.594452,-4.87675 4.865514,-10.01037 2.803445,-14.46953 -1.304173,-2.82021 -5.546143,-6.31504 -8.121034,-4.57597 z"/> 152 <path style="fill:none;stroke:currentColor;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;
152stroke-opacity:1" d="m 91.655522,295.45508 c 0.282256,0.41434 -23.320857,56.47482 -30.374989,82.38954 -3.289787,12.08567 0.226167,37.57556 0.226167,37.57556"/> 153 <path style="fill:none;stroke:currentColor;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" d="m 88.864708,292.81258 c 0,0 -22.461107,55.68145 -35.808297,82.53787 C 42.68504,396.21912 18.73663,436.2582 18.73663,436.2582"/> 154 <path style="fill:none;stroke:currentColor;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" d="m 52.598818,376.16257 8.681715,1.68205"/> 155 <path style="fill:none;stroke:currentColor;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" d="M 88.052608,292.35499 60.493685,343.16297 C 38.949098,371.93706 21.545818,399.60706 8.3854775,426.14582"/> 156 <path style="fill:none;stroke:currentColor;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" d="m 60.493685,343.16297 5.684717,3.20319"/> 157 <path style="fill:none;stroke:currentColor;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" d="M 71.579111,321.5907 22.699318,364.66477"/> 158 <path style="fill:none;stroke:currentColor;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" d="M 92.610689,285.43188 64.646141,252.80658 46.003108,248.14582"/> 159 </g> 160 </symbol> 161 <clipPath id="clipTube0"> 162 <rect x="10" y="20" width="440" height="100" rx="12"/> 163 </clipPath> 164 <clipPath id="clipTube1"> 165 <rect x="10" y="178" width="440" height="100" rx="12"/> 166 </clipPath> 167 </defs> 168 <!-- Young tube --> 169 <rect x="10" y="20" width="440" height="100" rx="12" fill="rgba(255,255,255,0.03)" stroke="rgba(255,255,255,0.08)" stroke-width="1"/> 170 <text x="22" y="16" fill="#9A9590" font-size="14" font-family="Inter,sans-serif" letter-spacing="0.08em" text-transform="uppercase" style="text-transform:uppercase;letter-spacing:0.08em">YOUNG</text> 171 <!-- Old tube --> 172 <rect x="10" y="178" width="440" height="100" rx="12" fill="rgba(255,255,255,0.03)" stroke="rgba(255,255,255,0.08)" stroke-width="1"/> 173 <text x="22" y="174" fill="#9A9590" font-size="14" font-family="Inter,sans-serif" letter-spacing="0.08em" style="text-transform:uppercase;letter-spacing:0.08em">OLD</text> 174 <!-- Flies injected by script --> 175 </svg> 176 </div> 177 178 </div> 179 </div> 180 </section> 181 182 <!-- ââ WHAT IS FLYGYM ââââââââââââââââââââââââââââââââââââââââ --> 183 <section class="section" id="what-is"> 184 <div class="container"> 185 <div class="split-grid"> 186 <div class="split-text reveal"> 187 <h2 class="section-heading">What is FlyGym?</h2> 188 <p class="section-why-tagline">Behavioral and neuronal readouts. De-risk your compound before the clinic, in weeks.</p> 189 <p class="body-text"> 190 FlyGym integrates automation, artificial intelligence, and neuroscience to uncover the causal link between adaptive behavioral changes and their origin in neuromuscular pathways, the root driver of behavior. It converts complex behavior into measurable biology, enabling the identification of precise interventions at the source of functional decline. 191 </p> 192 </div> 193 <div class="reveal" style="--delay:0.15s"> 194 <div class="approach-media"> 195 <img src="./assets/flygym/FlyGym_at_a_glance_whiteBG.jpg" alt="FlyGym at a glance" width="1400" height="1023" loading="lazy" class="approach-img" style="height:auto;object-fit:contain;"> 196 <p class="approach-media-caption">FlyGym at a glance</p> 197 </div> 198 </div> 199 </div> 200 </div> 201 </section> 202 203 <!-- ââ WHAT FLYGYM MEASURES ââââââââââââââââââââââââââââââââââ --> 204 <section class="section section--surface" id="measures"> 205 <div class="container"> 206 <div class="section-header reveal"> 207 <h2 class="section-heading">What FlyGym Measures</h2> 208 <p class="body-text" style="max-width:600px;margin-top:0.5rem">FlyGym simultaneously quantifies motor performance across key biological dimensions directly relevant to human healthy aging.</p> 209 </div> 210 211 <!-- Behavioral Automation --> 212 <h3 class="team-group-label reveal" style="margin-top:2rem">Behavioral Automation</h3> 213 <div class="measurement-group"> 214 215 <!-- Box 1: Toxicity & Lifespan --> 216 <div class="measurement-box reveal" style="--delay:0.05s"> 217 <div class="measurement-text"> 218 <div class="metric-icon" style="margin-bottom:1.25rem"> 219 <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"> 220 <path d="M9 3h6M10 3v6l-4 8a2 2 0 001.8 3h8.4a2 2 0 001.8-3L14 9V3"/> 221 </svg> 222 </div> 223 <h3 class="measurement-title">Toxicity & Lifespan</h3> 224 <p class="measurement-desc">Rapid identification of toxic compounds and lifespan effects at scale. Screen thousands of compounds for safety and longevity impact before advancing to costly vertebrate studies, dramatically reducing R&D risk and timeline.</p> 225 </div> 226 <div class="approach-media"> 227 <div class="survival-chart-wrap" style="padding:0;overflow:hidden"> 228 <svg id="vialCanvas" style="display:block;width:100%;
228height:100%" viewBox="0 0 400 280"></svg> 229 </div> 230 <p class="approach-media-caption">Computer vision detection of fly death in lifespan assays</p> 231 </div> 232 </div> 233 234 <!-- Box 2: Mobility --> 235 <div class="measurement-box reveal" style="--delay:0.1s"> 236 <div class="measurement-text"> 237 <div class="metric-icon" style="margin-bottom:1.25rem"> 238 <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"> 239 <g stroke-linejoin="round"> 240 <circle cx="15" cy="5" r="2" /> 241 <path d="M6 9.5H12.2L15.2 12.2H20" /> 242 <path d="M12.2 9.5L9.8 13" /> 243 <path d="M9.8 13L4 17" /> 244 <path d="M9.8 13L13.5 16.2" /> 245 <path d="M13.5 16.2L15 21" /> 246 </g> 247 </svg> 248 </div> 249 <h3 class="measurement-title">Mobility</h3> 250 <p class="measurement-desc">Automated locomotion tracking and neuromuscular function quantification. Gait, speed, endurance, and grip strength decline are the most predictive biomarkers of aging, measured simultaneously across large fly populations.</p> 251 </div> 252 <div class="approach-media"> 253 <video class="approach-img" autoplay muted loop playsinline preload="auto" style="background:#0d0d0d"> 254 <source src="assets/flygym/mobility_flies.mp4" type="video/mp4"> 255 </video> 256 <p class="approach-media-caption">Locomotion tracking: mobility performance</p> 257 </div> 258 </div> 259 260 <!-- Box 3: Kinematics --> 261 <div class="measurement-box reveal" style="--delay:0.15s"> 262 <div class="measurement-text"> 263 <div class="metric-icon" style="margin-bottom:1.25rem"> 264 <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"> 265 <circle cx="5" cy="19" r="2"/> 266 <circle cx="12" cy="7" r="2"/> 267 <circle cx="19" cy="14" r="2"/> 268 <path d="M6.5 17.5 Q9 13 10.5 9"/> 269 <path d="M13.5 8.5 Q16 11 17.5 12.5"/> 270 </svg> 271 </div> 272 <h3 class="measurement-title">Kinematics</h3> 273 <p class="measurement-desc">Unlock high-resolution behavioral insights with our fly-on-the-ball phenotyping platform. By combining precise locomotor assays with state-of-the-art 3D motion reconstruction, we capture subtle compound-induced effects that traditional assays often miss. Our platform quantifies detailed locomotor parameters such as leg coordination, and inertial dynamics, enabling sensitive and mechanistic evaluation of compound effects.</p> 274 </div> 275 <div class="approach-media"> 276 <video class="approach-img" autoplay muted loop playsinline preload="auto" style="background:#0d0d0d;width:100%;padding:0;object-fit:cover"> 277 <source src="assets/flygym/kinematics_anim.mp4" type="video/mp4"> 278 </video> 279 <p class="approach-media-caption">3D motion reconstruction and kinematic analysis</p> 280 </div> 281 </div> 282 283 </div><!-- /.measurement-group behavioral --> 284 285 <!-- Neuronal Automation --> 286 <h3 class="team-group-label reveal" style="margin-top:2.5rem">Neuronal Automation</h3> 287 <div class="measurement-group"> 288 289 <!-- Box 4: Motor Neuron Degeneration --> 290 <div class="measurement-box reveal" style="--delay:0.05s"> 291 <div class="measurement-text"> 292 <div class="metric-icon" style="margin-bottom:1.25rem"> 293 <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"> 294 <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2z"/> 295 <path d="M12 8v4l3 3"/> 296 </svg> 297 </div> 298 <h3 class="measurement-title">Motor Neuron Degeneration</h3> 299 <p class="measurement-desc">Motor neuron degeneration with aging and disease is one of the most evolutionarily conserved biological phenomena. FlyGym enables longitudinal visualization, quantification, and analysis of motor neuron structural changes under compound intervention.</p> 300 </div> 301 <div class="approach-media"> 302 <div style="position:relative;width:100%"> 303 <video id="nmjVideo" class="approach-img" autoplay muted loop playsinline preload="auto" style="background:#0d0d0d;width:100%;padding:0;object-fit:cover"> 304 <source src="assets/flygym/nmj_anim.mp4" type="video/mp4"> 305 </video> 306 <div id="nmjLabel" style="position:absolute;bottom:12px;left:12px;font-size:10px;font-weight:600;letter-spacing:0.08em;color:#fe6603;font-family:Inter,sans-serif;background:rgba(0,0,0,0.55);padding:3px 8px;border-radius:4px">
306YOUNG</div> 307 </div> 308 <p class="approach-media-caption">Fluorescence imaging of motor neuron morphology over time</p> 309 </div> 310 </div> 311 312 <!-- Box 5: Communication --> 313 <div class="measurement-box reveal" style="--delay:0.1s"> 314 <div class="measurement-text"> 315 <div class="metric-icon" style="margin-bottom:1.25rem"> 316 <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"> 317 <path d="M22 12h-4l-3 9L9 3l-3 9H2"/> 318 </svg> 319 </div> 320 <h3 class="measurement-title">Communication</h3> 321 <p class="measurement-desc">Motor neurons signal muscles via neurotransmitters to enable rapid, on-demand actions such as running or climbing. FlyGym quantifies motor system structureâfunction coupling by measuring neuromuscular transmission longitudinally during aging under compound intervention.</p> 322 </div> 323 <div class="approach-media"> 324 <div id="commPlotWrap" style="width:100%;height:320px;background:#0d0d0d;border-radius:var(--radius);border:1px solid var(--border);overflow:hidden;padding:0.75rem 0.25rem"> 325 <svg id="commPlotSvg" style="display:block;width:100%;height:100%"></svg> 326 </div> 327 <p class="approach-media-caption">Live patch-clamp recording at the neuromuscular junction</p> 328 </div> 329 </div> 330 331 <!-- Box 6: Muscle Activity --> 332 <div class="measurement-box reveal" style="--delay:0.15s"> 333 <div class="measurement-text"> 334 <div class="metric-icon" style="margin-bottom:1.25rem"> 335 <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"> 336 <path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/> 337 </svg> 338 </div> 339 <h3 class="measurement-title">Muscle Activity</h3> 340 <p class="measurement-desc">FlyGym measures real-time muscle activity to link motor neuron function to mobility, enabling causal mapping of compound effects across the motor system and behavioral output.</p> 341 </div> 342 <div class="approach-media"> 343 <div class="muscle-vid-grid"> 344 <div style="position:relative"> 345 <video class="approach-img" autoplay muted loop playsinline preload="auto" style="background:#0d0d0d;width:100%"> 346 <source src="assets/flygym/young_muscle.mp4" type="video/mp4"> 347 </video> 348 <div style="position:absolute;bottom:12px;left:12px;font-size:10px;font-weight:600;letter-spacing:0.08em;color:#fe6603;font-family:Inter,sans-serif;background:rgba(0,0,0,0.55);padding:3px 8px;border-radius:4px">YOUNG</div> 349 </div> 350 <div style="position:relative"> 351 <video class="approach-img" autoplay muted loop playsinline preload="auto" style="background:#0d0d0d;width:100%"> 352 <source src="assets/flygym/old_muscle.mp4" type="video/mp4"> 353 </video> 354 <div style="position:absolute;bottom:12px;left:12px;font-size:10px;font-weight:600;letter-spacing:0.08em;color:#bbbbbb;font-family:Inter,sans-serif;background:rgba(0,0,0,0.55);padding:3px 8px;border-radius:4px">OLD</div> 355 </div> 356 </div> 357 <p class="approach-media-caption">Real-time electromyography during locomotion</p> 358 </div> 359 </div> 360 </div><!-- /.measurement-group neuronal --> 361 362 <!-- Additional features --> 363 <h3 class="team-group-label reveal" style="margin-top:2.5rem">Additional Features</h3> 364 <div class="additional-features reveal" style="--delay:0.1s;margin-top:1rem"> 365 <div class="additional-feature"> 366 <div class="af-icon"> 367 <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"> 368 <path d="M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z"/> 369 </svg> 370 </div> 371 <span class="af-label">Sleep</span> 372 </div> 373 <div class="additional-feature"> 374 <div class="af-icon">
375 <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"> 376 <circle cx="12" cy="12" r="10"/> 377 <polyline points="12 6 12 12 16 14"/> 378 </svg> 379 </div> 380 <span class="af-label">Circadian Rhythm</span> 381 </div> 382 <div class="additional-feature"> 383 <div class="af-icon"> 384 <svg width="16" height="16" viewBox="0 0 410 430" fill="currentColor" stroke="currentColor" stroke-width="10" stroke-linejoin="round" stroke-linecap="round"> 385 <path d="M366.214,129.976h-21.621V57.313c0-24.708-20.102-44.809-44.81-44.809c-13.976,0-26.475,6.435-34.698,16.493 C258.68,12.07,242.31,0,223.167,0c-19.277,0-35.747,12.236-42.058,29.348c-8.225-9.96-20.664-16.318-34.56-16.318 c-24.708,0-44.809,20.101-44.809,44.809v0.563c-8.125-8.198-19.383-13.285-31.81-13.285c-24.708,0-44.809,20.101-44.809,44.809 v139.655c0,55.446,18.966,109.714,53.454,153.087v38.013c0,3.59,2.91,6.5,6.5,6.5s6.5-2.91,6.5-6.5v-40.3 c0-1.491-0.513-2.936-1.451-4.094c-33.534-41.359-52.003-93.46-52.003-146.707v-33.708c8.125,8.198,19.382,13.285,31.809,13.285 c16.71,0,31.303-9.2,39.006-22.795c7.992,12.324,21.861,20.5,37.612,20.5c16.212,0,30.44-8.654,38.308-21.585 c7.869,12.929,22.096,21.581,38.307,21.581c4.686,0,9.243-0.723,13.62-2.124c8.616,12.478,23.009,20.676,39.287,20.676h13.992v2.058 h-13c-47.421,0-86,38.58-86,86c0,3.59,2.91,6.5,6.5,6.5s6.5-2.91,6.5-6.5c0-40.252,32.748-73,73-73h19.5c3.59,0,6.5-2.91,6.5-6.5 v-8.558h39.326c3.59,0,6.5-2.91,6.5-6.5s-2.91-6.5-6.5-6.5h-66.318c-19.144,0-34.719-15.575-34.719-34.718 s15.575-34.718,34.719-34.718h90.143c12.598,0,22.846,10.249,22.846,22.846v90.861c0,36.452-17.734,70.829-47.439,91.958 l-0.291,0.207c-21.61,15.372-34.512,40.38-34.512,66.899c0,3.59,2.91,6.5,6.5,6.5s6.5-2.91,6.5-6.5 c0-22.319,10.858-43.368,29.047-56.306l0.291-0.207c33.127-23.563,52.904-61.899,52.904-102.551v-90.861 C402.06,146.056,385.98,129.976,366.214,129.976z M101.741,164.35c0,17.54-14.27,31.809-31.81,31.809 c-17.539,0-31.809-14.27-31.809-31.809V89.927c0-17.54,14.27-31.809,31.809-31.809c17.54,0,31.81,14.269,31.81,31.809V164.35z M146.549,193.864c-17.539,0-31.809-14.269-31.809-31.809V57.84c0-17.54,14.27-31.809,31.809-31.809S178.358,40.3,178.358,57.84 v0.001h-0.004v104.21c0,0.027,0.002,0.053,0.002,0.079C178.315,179.635,164.063,193.864,146.549,193.864z M228.353,177.694 c0,5.322,0.889,10.438,2.504,15.222c-2.494,0.62-5.064,0.945-7.693,0.945c-17.513,0-31.765-14.227-31.809-31.731 c0-0.025,0.002-0.05,0.002-0.075V44.809c0-17.54,14.27-31.809,31.809-31.809s31.809,14.269,31.809,31.809v90.098 C239.218,142.708,228.353,158.953,228.353,177.694z M267.975,130.677V57.313c0-17.54,14.27-31.809,31.81-31.809 c17.539,0,31.809,14.269,31.809,31.809v72.663h-55.521C273.311,129.976,270.609,130.224,267.975,130.677z"/> 386 </svg> 387 </div> 388 <span class="af-label">Grip Strength</span> 389 </div> 390 <div class="additional-feature"> 391 <div class="af-icon"> 392 <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"> 393 <circle cx="12" cy="12" r="2.5"/> 394 <circle cx="4" cy="6" r="2"/> 395 <circle cx="20" cy="6" r="2"/> 396 <circle cx="12" cy="21" r="2"/> 397 <line x1="5.4" y1="7.8" x2="10.2" y2="10.5"/> 398 <line x1="18.6" y1="7.8" x2="13.8" y2="10.5"/> 399 <line x1="12" y1="14.5" x2="12" y2="19"/> 400 </svg> 401 </div> 402 <span class="af-label">Associative Learning</span> 403 </div> 404 </div> 405 </div> 406 </section> 407 408 <!-- ââ WHY DROSOPHILA âââââââââââââââââââââââââââââââââââââââ --> 409 <section class="section" id="why-drosophila"> 410 <div class="container"> 411 <div class="section-header reveal"> 412 <h2 class="section-heading">Why <em>Drosophila</em>?</h2> 413 </div> 414 <div class="pillars-grid"> 415 <div class="pillar-card reveal"> 416 <div class="pillar-header"> 417 <div class="pillar-icon-wrap"> 418 <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"> 419 <path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/> 420 </svg> 421 </div> 422 <h3 class="pillar-title">Human-Relevant Biology</h3> 423 </div> 424 <p class="pillar-text">Along with conserved structural and functional features of NMJs, about <strong style="color:var(--brand)">75%</strong> of genes involved in human neurodegeneration and aging pathways have homologs in fruit flies.</p> 425 </div> 426 <div class="pillar-card reveal" style="--delay:0.1s"> 427 <div class="pillar-header"> 428 <div class="pillar-icon-wrap"> 429 <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"> 430 <circle cx="12" cy="12" r="10"/> 431 <polyline points="12 6 12 12 16 14"/> 432 </svg> 433 </div> 434 <h3 class="pillar-title">Results in Weeks, Not Years</h3> 435 </div> 436 <p class="pillar-text">A 50-day lifespan enables affordable long term aging studies while capturing whole organism brain function and tissue interactions that cell culture models cannot replicate.</p> 437 </div> 438 <div class="pillar-card reveal" style="--delay:0.2s"> 439 <div class="pillar-header"> 440 <div class="pillar-icon-wrap"> 441 <img src="./assets/drosophila.svg" alt="" aria-hidden="true" style="width:20px;height:20px;object-fit:contain;"> 442 </div> 443 <h3 class="pillar-title">Behavioral Complexity That Counts</h3> 444 </div> 445 <p class="pillar-text">Aggression, mating, drunkenness, memory, sociability, sleep, circadian rhythms, neurodegenerative diseases, and many more.</p> 446 </div> 447 <div class="pillar-card reveal" style="--delay:0.3s"> 448 <div class="pillar-header"> 449 <div class="pillar-icon-wrap"> 450 <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"> 451 <path d="M9 11l3 3L22 4"/> 452 <path d="M21 12v7a2 2 0 01-2 2H5a2 2 0 01-2-2V5a2 2 0 012-2h11"/> 453 </svg> 454 </div> 455 <h3 class="pillar-title">Aligned with NAM Standards</h3> 456 </div> 457 <p class="pillar-text">FlyGym, our Drosophila-based discovery platform, is NAM-aligned: it screens for neuromuscular efficacy without mammalian testing, in step with the 3Rs and the regulatory move toward New Approach Methodologies.</p> 458 </div> 459 </div> 460 461 <!-- Stats â business case framing --> 462 <h3 class="team-group-label reveal" style="margin-top:3rem">The numbers behind the platform</h3> 463 <div class="science-stats-grid science-stats-grid--4 reveal" style="--delay:0.1s;margin-top:1rem"> 464 <div class="science-stat"> 465 <div class="science-stat-num">6</div> 466 <div class="science-stat-benefit">Nobel Prizes</div> 467 <div class="science-stat-label">A century of validated science behind every assay we run</div> 468 </div> 469 <div class="science-stat"> 470 <div class="science-stat-num">100K+</div> 471 <div class="science-stat-benefit">Publications</div> 472 <div class="science-stat-label">The established baseline your data builds on</div> 473 </div> 474 <div class="science-stat"> 475 <div class="science-stat-num">100+</div> 476 <div class="science-stat-benefit">Years of research and discoveries</div> 477 <div class="science-stat-label">Drosophila has been a cornerstone model organism in biology since 1909</div> 478 </div> 479 <div class="science-stat"> 480 <div class="science-stat-num">3</div> 481 <div class="science-stat-benefit">Peer-reviewed validations for <span style="color:var(--brand)">FlyGym</span></div> 482 <div class="science-stat-label">Platform credibility you can cite in your own filings</div> 483 </div> 484 </div> 485 486 </div> 487 </section> 488 489 <!-- ââ WORK WITH US ââââââââââââââââââââââââââââââââââââââââ --> 490 <section class="section section--surface" id="work-with-us"> 491 <div class="container"> 492 <div class="section-header reveal"> 493 <h2 class="section-heading">Test Your Compound With FlyGym</h2> 494 <p class="body-text" style="max-width:620px;margin-top:0.5rem">We de-risk target identification and demonstrate compound impact on brain health in weeks. Here's how a partnership works.</p> 495 </div> 496 497 <!-- 3-step process --> 498 <div class="wwu-steps reveal" style="--delay:0.1s"> 499 <div class="wwu-step"> 500 <div class="wwu-step-num">01</div> 501 <h3 class="wwu-step-title">Submit your compound</h3> 502 <p class="wwu-step-text">Share your compound and research context. We assess feasibility and design the optimal assay panel for your target area.</p> 503 </div> 504 <div class="wwu-step-arrow" aria-hidden="true">â</div> 505 <div class="wwu-step"> 506 <div class="wwu-step-num">02</div> 507 <h3 class="wwu-step-title">FlyGym screens</h3> 508 <p class="wwu-step-text">Your compound is tested across our full behavioral assay panel: mobility, lifespan, grip strength, and humanized disease models.</p> 509 </div> 510 <div class="wwu-step-arrow" aria-hidden="true">â</div> 511 <div class="wwu-step"> 512 <div class="wwu-step-num">03</div> 513 <h3 class="wwu-step-title">Receive a full data report</h3> 514 <p class="wwu-step-text">Within 60 days, you receive a complete analysis package with statistical results, visualizations, and scientific interpretation.</p> 515 </div> 516 </div> 517 518 <!-- What you receive --> 519 <div class="wwu-outputs reveal" style="--delay:0.2s"> 520 <h3 class="wwu-outputs-title">What partners receive</h3> 521 <ul class="wwu-outputs-list"> 522 <li>Full behavioral dataset across all measured endpoints (mobility, lifespan, grip strength)</li> 523 <li>PDF summary report with statistical analysis and compound comparison</li> 524 <li>Call with the Juvion science team to review findings and discuss next steps</li> 525 <li>Sample report: <a href="#" class="wwu-sample-link" id="sampleReportCta">Download a personalized example report â</a></li> 526 </ul> 527 <button class="btn-primary wwu-cta" id="feasibilityCta">Book a feasibility call</button> 528 </div> 529 </div> 530 </section> 531 532 <!-- ââ SAMPLE REPORT MODAL âââââââââââââââââââââââââââââââââ --> 533 <div class="feasibility-modal" id="sampleReportModal" role="dialog" aria-modal="true" aria-label="Download sample report"> 534 <div class="feasibility-modal-backdrop" id="sampleReportBackdrop"></div> 535 <div class="feasibility-modal-box"> 536 <button class="feasibility-modal-close" id="sampleReportClose" aria-label="Close">â</button> 537 <h2 class="feasibility-modal-title">Download Sample Report</h2> 538 <p class="feasibility-modal-sub">Enter your details and we'll generate a personalized example FlyGym screening report for your organization.</p> 539 <form class="feasibility-form" id="sampleReportForm"> 540 <div class="contact-field"> 541 <label for="srCompany" class="contact-label">
541Company name <span class="contact-label-optional">(optional)</span></label> 542 <input type="text" id="srCompany" name="company" placeholder="Your organization" class="contact-input"> 543 </div> 544 <div class="contact-field"> 545 <label for="srCompound" class="contact-label">Compound <span class="contact-label-optional">(optional)</span></label> 546 <input type="text" id="srCompound" name="compound" placeholder="e.g. natural extract, peptide..." class="contact-input"> 547 </div> 548 <button type="submit" class="contact-submit">Generate & Download PDF â</button> 549 </form> 550 </div> 551 </div> 552 553 <!-- ââ FEASIBILITY MODAL ââââââââââââââââââââââââââââââââââââ --> 554 <div class="feasibility-modal" id="feasibilityModal" role="dialog" aria-modal="true" aria-label="Book a feasibility call"> 555 <div class="feasibility-modal-backdrop" id="feasibilityBackdrop"></div> 556 <div class="feasibility-modal-box"> 557 <button class="feasibility-modal-close" id="feasibilityClose" aria-label="Close">â</button> 558 <h2 class="feasibility-modal-title">Book a Feasibility Call</h2> 559 <p class="feasibility-modal-sub">Tell us about your compound and area of interest. We'll reach out to schedule a call with our science team.</p> 560 <form class="feasibility-form" id="feasibilityForm"> 561 <div class="contact-field"> 562 <label for="feasName" class="contact-label">Name</label> 563 <input type="text" id="feasName" name="name" placeholder="Your full name" class="contact-input" required> 564 </div> 565 <div class="contact-field"> 566 <label for="feasEmail" class="contact-label">Email</label> 567 <input type="email" id="feasEmail" name="email" placeholder="[email protected]" class="contact-input" required> 568 </div> 569 <div class="contact-field"> 570 <label for="feasCompany" class="contact-label">Company <span class="contact-label-optional">(optional)</span></label> 571 <input type="text" id="feasCompany" name="company" placeholder="Your organisation" class="contact-input"> 572 </div> 573 <div class="contact-field"> 574 <label for="feasSubject" class="contact-label">Subject <span class="contact-label-optional">(optional)</span></label> 575 <input type="text" id="feasSubject" name="subject" placeholder="e.g. Mobility compound screening" class="contact-input"> 576 </div> 577 <div class="contact-field"> 578 <label for="feasCompound" class="contact-label">Message</label> 579 <textarea id="feasCompound" name="message" placeholder="Briefly describe your compound or research area..." class="contact-input contact-textarea" rows="4" required></textarea> 580 </div> 581 <button type="submit" class="contact-submit">Submit request â</button> 582 <p class="contact-status" id="feasibilityStatus" aria-live="polite"></p> 583 </form> 584 </div> 585 </div> 586 587 <!-- ââ FOOTER âââââââââââââââââââââââââââââââââââââââââââââââ --> 588 <footer id="contact" class="footer"> 589 <div class="container footer-grid"> 590 <div class="footer-col"> 591 <a href="/" class="nav-brand footer-brand"> 592 <img src="assets/JuvionLogo.png" class="nav-logo-icon" alt="Juvion Health Sciences"> 593 <span class="logo-sub" style="margin-left:0">Juvion Health Sciences</span> 594 </a> 595 <p class="footer-tagline">Science to Sustain Motion</p> 596 <address class="footer-location footer-address"> 597 EPFL Innovation Park, Bâtiment C<br> 598 Lausanne, VD 1015<br> 599 Switzerland 600 </address> 601 <div class="footer-socials"> 602 <a href="https://www.linkedin.com/company/juvion-health-sciences/posts/?feedView=all" class="social-btn" aria-label="LinkedIn" target="_blank" rel="noopener"> 603 <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"> 604 <path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 01-2.063-2.065 2.064 2.064 0 112.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/> 605 </svg> 606 </a> 607 </div> 608 </div> 609 <div class="footer-col"> 610 <h4 class="footer-heading">Quick Links</h4> 611 <ul class="footer-links"> 612 <li><a href="/">Home</a></li> 613 <li><a href="/flygym">FlyGym & Services</a></li> 614 <li><a href="/products">Products</a></li> 615 <li><a href="/news">News & Publications</a></li> 616 <li><a href="/career">Careers</a></li> 617 <li><a href="/contact">Contact</a></li> 618 </ul> 619 </div> 620 <div class="footer-col"> 621 <h4 class="footer-heading">Stay Updated</h4> 622 <p class="footer-newsletter-copy">Get the latest from Juvion delivered monthly.</p> 623 <form class="newsletter-form" id="newsletterForm"> 624 <input type="email" name="email" placeholder="[email protected]" class="newsletter-input" aria-label="Email address" required> 625 <button type="submit" class="newsletter-btn" aria-label="Subscribe">â</button> 626 </form> 627 <div class="footer-contact-info"> 628 <p>[email protected]</p> 629 <p>+41 77 529 74 99</p> 630 </div> 631 </div> 632 </div> 633 <div class="footer-bottom"> 634 <div class="container footer-bottom-inner"> 635 <p>© 2026 Juvion Health Sciences. All rights reserved.</p> 636 <a href="/privacy" class="footer-legal-link">Privacy Notice</a> 637 </div> 638 </div> 639 </footer> 640 641 <!-- ââ CUSTOMER FLY LAYER ââââââââââââââââââââââââââââââââââââ --> 642 <div id="flyLayer" class="fly-layer" aria-hidden="true"></div> 643 <div id="flyTooltip" class="fly-tooltip-global"> 644 <div class="fly-tooltip-name"></div> 645 <div class="fly-tooltip-location"></div> 646 <div class="fly-tooltip-date"></div> 647 <a class="fly-tooltip-link" target="_blank" rel="noopener">Visit â</a> 648 </div> 649 650
650<script src="content/team.js"></script>
650 651
651<script src="content/partners.js"></script>
651 652
652<script src="content/customers.js"></script>
652 653
653<script src="content/news.js"></script>
653 654
654<script src="content/publications.js"></script>
654 655
655<script src="content/jobs.js"></script>
655 656
656<script src="content/products.js"></script>
656 657
657<script src="app.js"></script>
657 658
658<script> 659 document.querySelectorAll('.why-flygym-grid').forEach(function(grid) { 660 var panel = grid.querySelector('.why-flygym-detail-panel'); 661 if (!panel) return; 662 var panelText = panel.querySelector('.why-flygym-text'); 663 var cards = grid.querySelectorAll('.why-flygym-card--exp'); 664 cards.forEach(function(card) { 665 card.addEventListener('click', function() { 666 var isActive = card.classList.contains('active'); 667 cards.forEach(function(c) { c.classList.remove('active'); }); 668 panel.classList.remove('open'); 669 if (!isActive) { 670 card.classList.add('active'); 671 panelText.textContent = card.dataset.detail; 672 panel.classList.add('open'); 673 } 674 }); 675 }); 676 }); 677 </script>
677 678 <!-- ââ Vial Animation (Toxicity & Lifespan) ââââââââââââââââââââ --> 679
679<script> 680 (function() { 681 const svg = document.getElementById('vialCanvas'); 682 if (!svg) return; 683 const NS = 'http://www.w3.org/2000/svg'; 684 const W = 400, H = 280; 685 const TOTAL_FLIES = 30; 686 const margin = 16; 687 const vW = W - margin * 2, vH = H - 48, vY = 36; 688 function rnd(a, b) { return a + Math.random() * (b - a); } 689 690 const vials = [ 691 { x: margin, y: vY, w: vW, h: vH, label: 'ASSAY', clipId: 'vcClip0' }, 692 ]; 693 694 // ââ Static SVG structure âââââââââââââââââââââââââââââââââââââ 695 const defs = document.createElementNS(NS, 'defs'); 696 vials.forEach(v => { 697 const cp = document.createElementNS(NS, 'clipPath'); 698 cp.setAttribute('id', v.clipId); 699 const r = document.createElementNS(NS, 'rect'); 700 r.setAttribute('x', v.x); r.setAttribute('y', v.y); 701 r.setAttribute('width', v.w); r.setAttribute('height', v.h); 702 r.setAttribute('rx', '10'); 703 cp.appendChild(r); defs.appendChild(cp); 704 }); 705 svg.appendChild(defs); 706 707 vials.forEach(v => { 708 const rect = document.createElementNS(NS, 'rect'); 709 rect.setAttribute('x', v.x); rect.setAttribute('y', v.y); 710 rect.setAttribute('width', v.w); rect.setAttribute('height', v.h); 711 rect.setAttribute('rx', '10'); 712 rect.setAttribute('fill', 'rgba(255,255,255,0.025)'); 713 rect.setAttribute('stroke', 'rgba(255,255,255,0.1)'); 714 rect.setAttribute('stroke-width', '1'); 715 svg.appendChild(rect); 716 const label = document.createElementNS(NS, 'text'); 717 label.setAttribute('x', v.x + 10); label.setAttribute('y', v.y - 6); 718 label.setAttribute('fill', 'rgba(154,149,144,0.7)'); 719 label.setAttribute('font-size', '9'); label.setAttribute('font-family', 'Inter,sans-serif'); 720 label.textContent = v.label; 721 svg.appendChild(label); 722 }); 723 724 // Subtle measurement lines â behind flies 725 const v0 = vials[0]; 726 [1/3, 2/3].forEach(frac => { 727 const ml = document.createElementNS(NS, 'line'); 728 const ly = (v0.y + v0.h * frac).toFixed(1); 729 ml.setAttribute('x1', v0.x); ml.setAttribute('y1', ly); 730 ml.setAttribute('x2', v0.x + v0.w); ml.setAttribute('y2', ly); 731 ml.setAttribute('stroke', 'rgba(255,255,255,0.045)'); 732 ml.setAttribute('stroke-width', '1'); 733 ml.setAttribute('stroke-dasharray', '4 10'); 734 svg.appendChild(ml); 735 }); 736 737 const flyGroups = vials.map(v => { 738 const g = document.createElementNS(NS, 'g'); 739 g.setAttribute('clip-path', `url(#${v.clipId})`); 740 svg.appendChild(g); 741 return g; 742 }); 743 744 const deadText = document.createElementNS(NS, 'text'); 745 deadText.setAttribute('fill', 'rgba(254,102,3,0.85)'); 746 deadText.setAttribute('font-size', '9'); deadText.setAttribute('font-family', 'Inter,sans-serif'); 747 deadText.setAttribute('text-anchor', 'end'); 748 deadText.setAttribute('x', margin + vW); deadText.setAttribute('y', vY - 6); 749 svg.appendChild(deadText); 750 751 const restartText = document.createElementNS(NS, 'text'); 752 restartText.setAttribute('fill', 'rgba(154,149,144,0.4)'); 753 restartText.setAttribute('font-size', '11'); restartText.setAttribute('font-family', 'Inter,sans-serif'); 754 restartText.setAttribute('text-anchor', 'middle'); 755 restartText.setAttribute('x', W / 2); restartText.setAttribute('y', H / 2); 756 restartText.textContent = 'Restarting assayâ¦'; restartText.style.display = 'none'; 757 svg.appendChild(restartText); 758 759 // Age annotation â descriptive label inside vial top-left 760 const ageAnnotation = document.createElementNS(NS, 'text'); 761 ageAnnotation.setAttribute('x', v0.x + 10); ageAnnotation.setAttribute('y', v0.y + 32); 762 ageAnnotation.setAttribute('font-size', '8.5'); ageAnnotation.setAttribute('font-family', 'Inter,sans-serif'); 763 ageAnnotation.setAttribute('letter-spacing', '0.5'); 764 svg.appendChild(ageAnnotation); 765 766 767 // ââ Experiment annotations (on top of flies) ââââââââââââââââââ 768 // REC dot 769 const recDot = document.createElementNS(NS, 'circle'); 770 recDot.setAttribute('cx', v0.x + 10); recDot.setAttribute('cy', v0.y + 13); 771 recDot.setAttribute('r', '2.5'); recDot.setAttribute('fill', 'rgba(254,102,3,0.9)'); 772 svg.appendChild(recDot); 773 // REC label 774 const recLabel = document.createElementNS(NS, 'text'); 775 recLabel.setAttribute('x', v0.x + 17); recLabel.setAttribute('y', v0.y + 17); 776 recLabel.setAttribute('fill', 'rgba(254,102,3,0.65)'); 777 recLabel.setAttribute('font-size', '7.5');
777 recLabel.setAttribute('font-family', 'Inter,sans-serif'); 778 recLabel.setAttribute('letter-spacing', '1'); 779 recLabel.textContent = 'REC'; 780 svg.appendChild(recLabel); 781 // Metadata strip (bottom of vial) 782 const metaText = document.createElementNS(NS, 'text'); 783 metaText.setAttribute('x', v0.x + v0.w / 2); metaText.setAttribute('y', v0.y + v0.h - 9); 784 metaText.setAttribute('fill', 'rgba(154,149,144,0.28)'); 785 metaText.setAttribute('font-size', '7.5'); metaText.setAttribute('font-family', 'Inter,sans-serif'); 786 metaText.setAttribute('text-anchor', 'middle'); metaText.setAttribute('letter-spacing', '0.4'); 787 metaText.textContent = 'D. melanogaster · Experiment X · T = 25°C · n = 30'; 788 svg.appendChild(metaText); 789 790 // ââ Survival curve (Toxic group) ââââââââââââââââââââââââââââââ 791 const SURVIVAL_CURVE = [ 792 {day:0,pct:100},{day:3,pct:98},{day:7,pct:96},{day:14,pct:93}, 793 {day:21,pct:88},{day:28,pct:78},{day:35,pct:60},{day:42,pct:35}, 794 {day:49,pct:12},{day:52,pct:3},{day:56,pct:0}, 795 ]; 796 function survivalPct(day) { 797 let pct = 100; 798 for (const pt of SURVIVAL_CURVE) { if (day >= pt.day) pct = pt.pct; else break; } 799 return pct; 800 } 801 function expectedDead(day) { 802 return Math.round(TOTAL_FLIES * (1 - survivalPct(day) / 100)); 803 } 804 805 // ââ Dynamic state âââââââââââââââââââââââââââââââââââââââââââââ 806 let flies = [], restarting = false, restartTimer = 0; 807 let recBlink = true, recBlinkTimer = 0; 808 let frameCount = 0, lastDeathFrame = -999; 809 810 function createFlyEl(vi) { 811 const g = document.createElementNS(NS, 'g'); 812 // SVG fly shape â same #fly-shape symbol as top vials 813 const flyShapeG = document.createElementNS(NS, 'g'); 814 flyShapeG.style.color = '#F2EFE9'; 815 flyShapeG.setAttribute('opacity', '0.88'); 816 const use = document.createElementNS(NS, 'use'); 817 use.setAttribute('href', '#fly-shape'); 818 use.setAttribute('width', '9'); use.setAttribute('height', '9'); 819 use.setAttribute('x', '-4.5'); use.setAttribute('y', '-4.5'); 820 flyShapeG.appendChild(use); 821 g.appendChild(flyShapeG); 822 // CV detection frame â 4 corner L-brackets (hidden until dead) 823 const frameGroup = document.createElementNS(NS, 'g'); 824 frameGroup.setAttribute('opacity', '0'); 825 const corners = [ 826 [[-8,-8],[-2,-8]], [[-8,-8],[-8,-2]], // top-left 827 [[ 8,-8],[ 2,-8]], [[ 8,-8],[ 8,-2]], // top-right 828 [[-8, 8],[-2, 8]], [[-8, 8],[-8, 2]], // bottom-left 829 [[ 8, 8],[ 2, 8]], [[ 8, 8],[ 8, 2]], // bottom-right 830 ]; 831 corners.forEach(([[x1,y1],[x2,y2]]) => { 832 const line = document.createElementNS(NS, 'line'); 833 line.setAttribute('x1', x1); line.setAttribute('y1', y1); 834 line.setAttribute('x2', x2); line.setAttribute('y2', y2); 835 line.setAttribute('stroke', 'rgba(254,102,3,0.85)'); 836 line.setAttribute('stroke-width', '1.5'); 837 line.setAttribute('stroke-linecap', 'square'); 838 frameGroup.appendChild(line); 839 }); 840 g.appendChild(frameGroup); 841 flyGroups[vi].appendChild(g); 842 return { el: g, flyShapeG, frameGroup }; 843 } 844 845 function setup() { 846 flyGroups.forEach(g => { g.innerHTML = ''; }); 847 flies = []; restarting = false; restartTimer = 0; 848 recBlink = true; recBlinkTimer = 0; frameCount = 0; lastDeathFrame = -999;
849 recDot.setAttribute('opacity', '1'); 850 restartText.style.display = 'none'; deadText.textContent = ''; 851 vials.forEach((v, vi) => { 852 for (let i = 0; i < TOTAL_FLIES; i++) { 853 const spd = rnd(0.6, 1.2), ang = rnd(0, Math.PI * 2); 854 const { el, flyShapeG, frameGroup } = createFlyEl(vi); 855 flies.push({ 856 el, flyShapeG, frameGroup, vi, 857 x: rnd(v.x + 14, v.x + v.w - 14), y: rnd(v.y + 44, v.y + v.h - 14), 858 vx: Math.cos(ang) * spd, vy: Math.sin(ang) * spd, 859 dead: false, dying: false, dyingAge: 0, frameOpacity: 0, 860 lastAngle: ang * 180 / Math.PI + 90, 861 }); 862 } 863 }); 864 } 865 866 setup(); 867 868 function tick() { 869 if (restarting) { 870 restartTimer++; 871 restartText.style.display = ''; 872 if (restartTimer > 80) setup(); 873 requestAnimationFrame(tick); 874 return; 875 } 876 877 // Simulated day â computed early so dying flies can record their deathDay 878 const simDay = Math.min(56, Math.floor(frameCount / 24)); 879 880 // Death scheduling â data-driven from Toxic survival curve 881 const target = expectedDead(simDay); 882 const activeDeadOrDying = flies.filter(f => f.dead || f.dying).length; 883 if (activeDeadOrDying < target && frameCount - lastDeathFrame >= 20) { 884 const alive = flies.filter(f => !f.dead && !f.dying); 885 if (alive.length) { 886 alive[Math.floor(Math.random() * alive.length)].dying = true; 887 lastDeathFrame = frameCount; 888 } 889 } 890 if (flies.filter(f => f.dead).length >= flies.length * 0.9) { 891 restarting = true; restartTimer = 0; 892 } 893 894 flies.forEach(f => { 895 const v = vials[f.vi]; 896 897 if (f.dead) { 898 // Dead: faded fly + CV corner-bracket detection frame fading in 899 if (f.frameOpacity < 1) { 900 f.frameOpacity = Math.min(1, f.frameOpacity + 1 / 30); 901 f.frameGroup.setAttribute('opacity', f.frameOpacity.toFixed(3)); 902 } 903 return; 904 } 905 906 if (f.dying) { 907 // Dying: ease out â decelerate and fade before stopping 908 f.dyingAge++; 909 f.vx *= 0.96; f.vy *= 0.96; 910 f.x += f.vx; f.y += f.vy; 911 const pad = 10, topPad = (f.x < v.x + 130) ? 38 : pad; 912 if (f.x < v.x + pad) { f.x = v.x + pad; f.vx = Math.abs(f.vx) * 0.4; } 913 if (f.x > v.x + v.w - pad) { f.x = v.x + v.w - pad; f.vx = -Math.abs(f.vx) * 0.4; } 914 if (f.y < v.y + topPad) { f.y = v.y + topPad; f.vy = Math.abs(f.vy) * 0.4; } 915 if (f.y > v.y + v.h - pad) { f.y = v.y + v.h - pad; f.vy = -Math.abs(f.vy) * 0.4; } 916 // Keep last valid angle so the fly doesn't snap when speed â 0 917 if (Math.hypot(f.vx, f.vy) > 0.05) { 918 f.lastAngle = Math.atan2(f.vy, f.vx) * 180 / Math.PI + 90; 919 } 920 const fadeProgress = Math.min(1, f.dyingAge / 100); 921 const opacity = (0.88 - 0.35 * fadeProgress).toFixed(3); 922 f.flyShapeG.style.color = '#C0B4A8'; 923 f.flyShapeG.setAttribute('opacity', opacity); 924 f.flyShapeG.setAttribute('transform', `rotate(${f.lastAngle.toFixed(1)})`); 925 f.el.setAttribute('transform', `translate(${f.x.toFixed(1)},${f.y.toFixed(1)})`); 926 f.frameGroup.setAttribute('opacity', '0'); 927 if (f.dyingAge >= 120) { 928 f.dying = false; f.dead = true; 929 f.flyShapeG.style.color = '#fe6603'; 930 f.flyShapeG.setAttribute('opacity', '0.53'); 931 } 932 return; 933 } 934 935 // Alive: normal wandering movement 936 if (Math.random() < 0.018) { 937 f.vx += rnd(-0.35, 0.35); f.vy += rnd(-0.35, 0.35); 938 const spd = Math.hypot(f.vx, f.vy); 939 if (spd > 1.3) { f.vx = f.vx / spd * 1.3; f.vy = f.vy / spd * 1.3; } 940 if (spd < 0.3 && spd > 0) { f.vx = f.vx / spd * 0.6; f.vy = f.vy / spd * 0.6; } 941 } 942 f.x += f.vx; f.y += f.vy; 943 const pad = 10, topPad = (f.x < v.x + 130) ? 38 : pad; 944 if (f.x < v.x + pad) { f.x = v.x + pad; f.vx = Math.abs(f.vx); } 945 if (f.x >
945 v.x + v.w - pad) { f.x = v.x + v.w - pad; f.vx = -Math.abs(f.vx); } 946 if (f.y < v.y + topPad) { f.y = v.y + topPad; f.vy = Math.abs(f.vy); } 947 if (f.y > v.y + v.h - pad) { f.y = v.y + v.h - pad; f.vy = -Math.abs(f.vy); } 948 const ang = Math.atan2(f.vy, f.vx) * 180 / Math.PI + 90; 949 f.lastAngle = ang; 950 f.flyShapeG.style.color = '#F2EFE9'; 951 f.flyShapeG.setAttribute('opacity', '0.88'); 952 f.flyShapeG.setAttribute('transform', `rotate(${ang.toFixed(1)})`); 953 f.el.setAttribute('transform', `translate(${f.x.toFixed(1)},${f.y.toFixed(1)})`); 954 f.frameGroup.setAttribute('opacity', '0'); 955 }); 956 957 // REC blink 958 if (++recBlinkTimer >= 45) { 959 recBlink = !recBlink; 960 recBlinkTimer = 0; 961 recDot.setAttribute('opacity', recBlink ? '1' : '0.12'); 962 } 963 964 frameCount++; 965 966 // Dead count readout 967 const deadCount = flies.filter(f => f.dead).length; 968 deadText.textContent = deadCount > 0 969 ? `â ${deadCount} dead · ${Math.round(deadCount / flies.length * 100)}%` 970 : ''; 971 972 // Age annotation â descriptive, scaled to Toxic curve (max day 39) 973 let ageLabel, ageColor; 974 if (simDay < 24) { ageLabel = 'AGE YOUNG'; ageColor = 'rgba(254,102,3,0.9)'; } 975 else if (simDay < 45) { ageLabel = 'AGE MIDDLE AGE'; ageColor = 'rgba(251,191,36,0.85)'; } 976 else { ageLabel = 'AGE OLD'; ageColor = 'rgba(187,187,187,0.9)'; } 977 ageAnnotation.textContent = ageLabel; 978 ageAnnotation.setAttribute('fill', ageColor); 979 980 981 requestAnimationFrame(tick); 982 } 983 984 requestAnimationFrame(tick); 985 })(); 986 </script>
986 987 988 <!-- ââ Survival Chart (commented out â keep for future reference) ââ 989
989<script> 990 // ââ Survival Chart ââââââââââââââââââââââââââââââââââââââââââââ 991 (function () { 992 const canvas = document.getElementById('survivalChart'); 993 if (!canvas) return; 994 995 const controlData = [{x:0,y:100},{x:6,y:96},{x:9,y:96},{x:13,y:96},{x:16,y:96},{x:21,y:96},{x:27,y:96},{x:32,y:89},{x:36,y:89},{x:42,y:50},{x:45,y:39},{x:49,y:13},{x:52,y:3},{x:56,y:0}]; 996 const extenderData = [{x:0,y:100},{x:6,y:95},{x:9,y:95},{x:13,y:92},{x:17,y:92},{x:23,y:92},{x:27,y:92},{x:32,y:84},{x:36,y:84},{x:42,y:73},{x:45,y:68},{x:49,y:57},{x:52,y:43},{x:56,y:30},{x:60,y:11},{x:64,y:0}]; 997 const toxicData = [{x:0,y:100},{x:7,y:100},{x:13,y:92},{x:16,y:87},{x:20,y:65},{x:23,y:60},{x:27,y:40},{x:31,y:26},{x:34,y:5},{x:39,y:0}]; 998 const controlCIUp = [{x:0,y:100},{x:6,y:100},{x:9,y:100},{x:13,y:100},{x:16,y:100},{x:21,y:100},{x:27,y:100},{x:32,y:100},{x:36,y:100},{x:42,y:73},{x:45,y:55},{x:49,y:23},{x:52,y:8},{x:56,y:0}]; 999 const controlCILo = [{x:0,y:100},{x:6,y:87},{x:9,y:87},{x:13,y:87},{x:16,y:87},{x:21,y:87},{x:27,y:87},{x:32,y:67},{x:36,y:67},{x:42,y:20},{x:45,y:13},{x:49,y:0},{x:52,y:0},{x:56,y:0}]; 1000 const extenderCIUp = [{x:0,y:100},{x:6,y:100},{x:9,y:100},{x:13,y:100},{x:17,y:100},{x:23,y:100},{x:27,y:100},{x:32,y:91},{x:36,y:91},{x:42,y:76},{x:45,y:69},{x:49,y:66},{x:52,y:50},{x:56,y:46},{x:60,y:22},{x:64,y:0}]; 1001 const extenderCILo = [{x:0,y:100},{x:6,y:92},{x:9,y:92},{x:13,y:86},{x:17,y:86},{x:23,y:86},{x:27,y:86},{x:32,y:77},{x:36,y:77},{x:42,y:68},{x:45,y:67},{x:49,y:41},{x:52,y:34},{x:56,y:8},{x:60,y:0},{x:64,y:0}]; 1002 const toxicCIUp = [{x:0,y:100},{x:7,y:100},{x:13,y:100},{x:16,y:95},{x:20,y:79},{x:23,y:79},{x:27,y:54},{x:31,y:35},{x:34,y:8},{x:39,y:0}]; 1003 const toxicCILo = [{x:0,y:100},{x:7,y:100},{x:13,y:86},{x:16,y:85},{x:20,y:39},{x:23,y:39},{x:27,y:8},{x:31,y:8},{x:34,y:0},{x:39,y:0}]; 1004 1005 function ciDataset(upper, lower, color) { 1006 return [ 1007 { data: upper, borderWidth: 0, pointRadius: 0, tension: 0.3, backgroundColor: color, fill: '+1' }, 1008 { data: lower, borderWidth: 0, pointRadius: 0, tension: 0.3, backgroundColor: 'transparent', fill: false }, 1009 ]; 1010 } 1011 1012 new Chart(canvas, { 1013 type: 'line', 1014 data: { 1015 datasets: [ 1016 // CI bands first (indices 0â5) 1017 ...ciDataset(extenderCIUp, extenderCILo, 'rgba(254,102,3,0.09)'), 1018 ...ciDataset(controlCIUp, controlCILo, 'rgba(242,239,233,0.05)'), 1019 ...ciDataset(toxicCIUp, toxicCILo, 'rgba(220,70,60,0.07)'), 1020 // Main lines (indices 6â8) 1021 { 1022 label: 'Life extender', 1023 data: extenderData, 1024 borderColor: '#fe6603', 1025 backgroundColor: 'transparent', 1026 borderWidth: 2.5, 1027 pointRadius: 0, 1028 tension: 0.3, 1029 }, 1030 { 1031 label: 'Control', 1032 data: controlData, 1033 borderColor: 'rgba(242,239,233,0.6)', 1034 backgroundColor: 'transparent', 1035 borderWidth: 2, 1036 pointRadius: 0, 1037 tension: 0.3, 1038 borderDash: [4, 3], 1039 }, 1040 { 1041 label: 'Toxic', 1042 data: toxicData, 1043 borderColor: 'rgba(220,70,60,0.85)', 1044 backgroundColor: 'transparent', 1045 borderWidth: 2.5, 1046 pointRadius: 0, 1047 tension: 0.3, 1048 }, 1049 ] 1050 }, 1051 options: { 1052 parsing: false, 1053 animation: false, 1054 responsive: true, 1055 maintainAspectRatio: false, 1056 plugins: { 1057 legend: { 1058 position: 'top', 1059 align: 'end', 1060 labels: { 1061 filter: item => item.datasetIndex >= 6, 1062 color: 'rgba(220,215,208,1)', 1063 font: { family: 'Inter', size: 12 }, 1064 boxWidth: 20, 1065 boxHeight: 2, 1066 padding: 12, 1067 } 1068 }, 1069 tooltip: { 1070 filter: item => item.datasetIndex >= 6, 1071 backgroundColor: '#141414', 1072 borderColor: 'rgba(255,255,255,0.09)', 1073 borderWidth: 1, 1074 titleColor: '#F2EFE9', 1075 bodyColor: '#9A9590', 1076 callbacks: { 1077 label: ctx => ` ${ctx.dataset.label}: ${ctx.parsed.y}%`, 1078 title: items => `Day ${items[0].parsed.x}`, 1079 } 1080 } 1081 }, 1082 scales: { 1083 x: { 1084 type: 'linear', 1085 title: { display: true, text: 'Age (days)', color: '#9A9590', font: { family: 'Inter', size: 11 } }, 1086 grid: { color: 'rgba(255,255,255,0.07)' }, 1087 ticks: { color: '#9A9590', font: { family: 'Inter', size: 11 } }, 1088 }, 1089 y: { 1090 min: 0, 1091 max: 100, 1092 title: { display: true, text: 'Survival (%)', color: '#9A9590', font: { family: 'Inter', size: 11 } }, 1093 grid: { color: 'rgba(255,255,255,0.07)' }, 1094 ticks: { color: '#9A9590', font: { family: 'Inter', size: 11 }, callback: v => v + '%' }, 1095 } 1096 } 1097 } 1098 }); 1099 })(); 1100 // ââ end Survival Chart ââââââââââââââââââââââââââââââââââââââââââââ --> 1101 1102 <!-- ââ FLY TUBE ANIMATION âââââââââââââââââââââââââââââââââââ --> 1103
1103<script> 1104 (function() { 1105 const svg = document.getElementById('flyTubeSvg'); 1106 if (!svg) return; 1107 const NS = 'http://www.w3.org/2000/svg'; 1108 1109 const tubes = [ 1110 { y:20, h:100, color:'#fe6603', opacity:0.9, minSpeed:1.1, maxSpeed:2.2, drift:0.35, count:10, isOld:false }, 1111 { y:178, h:100, color:'#5A5755', opacity:0.6, minSpeed:0.4, maxSpeed:0.9, drift:0, gravity:0.35, count:10, isOld:true }, 1112 ]; 1113 const W = 460, PAD = 18; 1114 function rand(a, b) { return a + Math.random() * (b - a); } 1115 1116 const flies = []; 1117 const tubeGroups = tubes.map((_, i) => { 1118 const g = document.createElementNS(NS, 'g'); 1119 g.setAttribute('clip-path', `url(#clipTube${i})`); 1120 svg.appendChild(g); 1121 return g; 1122 }); 1123 tubes.forEach((tube, tubeIdx) => { 1124 for (let i = 0; i < tube.count; i++) { 1125 const g = document.createElementNS(NS, 'g'); 1126 g.style.color = tube.color; 1127 g.setAttribute('opacity', tube.opacity); 1128 const use = document.createElementNS(NS, 'use'); 1129 use.setAttribute('href', '#fly-shape'); 1130 use.setAttribute('width', '14'); 1131 use.setAttribute('height', '14'); 1132 use.setAttribute('x', '-7'); 1133 use.setAttribute('y', '-7'); 1134 g.appendChild(use); 1135 tubeGroups[tubeIdx].appendChild(g); 1136 1137 const startX = tube.gravity ? rand(PAD, W * 0.5) : rand(PAD, W - PAD); 1138 flies.push({ 1139 el: g, 1140 x: startX, 1141 y: rand(tube.y + 16, tube.y + tube.h - 16), 1142 vx: rand(-0.5, 0.5) + (tube.drift || 0), 1143 vy: rand(-0.3, 0.3), 1144 tube, 1145 stopTimer: 0, 1146 stopProb: tube.isOld ? 0.025 : 0.004, 1147 stopMin: tube.isOld ? 60 : 20, 1148 stopMax: tube.isOld ? 180 : 60, 1149 }); 1150 } 1151 }); 1152 1153 function tick() { 1154 flies.forEach(f => { 1155 const t = f.tube; 1156 if (f.stopTimer > 0) { 1157 f.stopTimer--; 1158 } else { 1159 if (Math.random() < f.stopProb) { 1160 f.stopTimer = Math.floor(rand(f.stopMin, f.stopMax)); 1161 f.vx = 0; f.vy = 0; 1162 } else { 1163 f.vx += rand(-0.25, 0.25); 1164 f.vy += rand(-0.2, 0.2); 1165 if (t.gravity) { 1166 f.vx -= t.gravity * (f.x - PAD) / (W - 2 * PAD); 1167 } else { 1168 f.vx += t.drift * 0.08; 1169 } 1170 const speed = Math.sqrt(f.vx * f.vx + f.vy * f.vy); 1171 if (speed > t.maxSpeed) { f.vx = f.vx / speed * t.maxSpeed; f.vy = f.vy / speed * t.maxSpeed; } 1172 f.x += f.vx; 1173 f.y += f.vy; 1174 if (t.gravity) { 1175 if (f.x > W - PAD) { f.x = W - PAD; f.vx *= -0.5; } 1176 if (f.x < PAD) { f.x = PAD; f.vx *= -0.5; } 1177 } else { 1178 if (f.x > W - PAD + 10) f.x = PAD - 10; 1179 if (f.x < PAD - 10) f.x = W - PAD + 10; 1180 } 1181 const yMin = t.y + 12, yMax = t.y + t.h - 12; 1182 if (f.y < yMin) { f.y = yMin; f.vy *= -0.6; } 1183 if (f.y > yMax) { f.y = yMax; f.vy *= -0.6; } 1184 } 1185 } 1186 const angle = Math.atan2(f.vy, f.vx) * 180 / Math.PI + 90; 1187 f.el.setAttribute('transform', `translate(${f.x.toFixed(1)},${f.y.toFixed(1)}) rotate(${angle.toFixed(1)})`); 1188 }); 1189 requestAnimationFrame(tick); 1190 } 1191 requestAnimationFrame(tick); 1192 })(); 1193 </script>
1193 1194 1195 <!-- ââ SAMPLE REPORT MODAL JS ââââââââââââââââââââââââââââââ --> 1196
1196<script src="https://cdn.jsdelivr.net/npm/jspdf@2/dist/jspdf.umd.min.js"></script>
1196 1197
1197<script> 1198 (function() { 1199 const modal = document.getElementById('sampleReportModal'); 1200 const openBtn = document.getElementById('sampleReportCta'); 1201 const closeBtn = document.getElementById('sampleReportClose'); 1202 const backdrop = document.getElementById('sampleReportBackdrop'); 1203 const form = document.getElementById('sampleReportForm'); 1204 if (!modal || !openBtn) return; 1205 1206 function openModal() { modal.classList.add('is-open'); document.body.style.overflow = 'hidden'; } 1207 function closeModal() { modal.classList.remove('is-open'); document.body.style.overflow = ''; } 1208 1209 openBtn.addEventListener('click', e => { e.preventDefault(); openModal(); }); 1210 closeBtn.addEventListener('click', closeModal); 1211 backdrop.addEventListener('click', closeModal); 1212 document.addEventListener('keydown', e => { if (e.key === 'Escape' && modal.classList.contains('is-open')) closeModal(); }); 1213 1214 form.addEventListener('submit', function(e) { 1215 e.preventDefault(); 1216 const company = document.getElementById('srCompany').value.trim(); 1217 const compound = document.getElementById('srCompound').value.trim(); 1218 const displayCompany = company || 'Your Organization'; 1219 const today = new Date().toLocaleDateString('en-GB', { day: 'numeric', month: 'long', year: 'numeric' }); 1220 1221 // Always send to Formspree 1222 fetch('https://formspree.io/f/xkopgyyr', { 1223 method: 'POST', 1224 headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' }, 1225 body: JSON.stringify({ company: company || '(anonymous)', compound: compound || '(not provided)', source: 'Sample Report Download' }), 1226 }).catch(() => {}); 1227 1228 const { jsPDF } = window.jspdf; 1229 const doc = new jsPDF({ unit: 'mm', format: 'a4' }); 1230 const W = 210, H = 297; 1231 1232 // ââ Cover page ââââââââââââââââââââââââââââââââââââââââââ 1233 // Background 1234 doc.setFillColor(8, 8, 8); 1235 doc.rect(0, 0, W, H, 'F'); 1236 1237 // Brand accent bar 1238 doc.setFillColor(254, 102, 3); 1239 doc.rect(0, 0, 6, H, 'F'); 1240 1241 // Company name chip 1242 doc.setFillColor(30, 28, 25); 1243 doc.roundedRect(18, 28, 80, 10, 2, 2, 'F'); 1244 doc.setFontSize(8); 1245 doc.setTextColor(254, 102, 3); 1246 doc.setFont('helvetica', 'normal'); 1247 doc.text('PREPARED FOR', 22, 34.5); 1248 doc.setTextColor(242, 239, 233); 1249 doc.text(displayCompany.toUpperCase(), 22 + 36, 34.5); 1250 1251 // Confidential tag 1252 doc.setFontSize(7); 1253 doc.setTextColor(120, 116, 110); 1254 doc.text('SAMPLE REPORT · CONFIDENTIAL', W - 20, 34.5, { align: 'right' }); 1255 1256 // Main heading 1257 doc.setFontSize(32); 1258 doc.setFont('helvetica', 'bold'); 1259 doc.setTextColor(242, 239, 233); 1260 doc.text('FlyGym', 18, 90); 1261 doc.setFontSize(22); 1262 doc.setFont('helvetica', 'normal'); 1263 doc.setTextColor(254, 102, 3); 1264 doc.text('Screening Report', 18, 102); 1265 1266 // Divider line 1267 doc.setDrawColor(50, 48, 44); 1268 doc.setLineWidth(0.4); 1269 doc.line(18, 112, W - 18, 112); 1270 1271 // Subtitle block 1272 doc.setFontSize(10); 1273 doc.setTextColor(160, 155, 148); 1274 doc.setFont('helvetica', 'normal'); 1275 const subtitle = [ 1276 'This report presents a sample output of the FlyGym behavioral screening', 1277 'platform, illustrating the depth of data and analysis delivered to partners.', 1278 '', 1279 'Assays performed: Mobility · Lifespan · Grip strength · Humanized disease models', 1280 ]; 1281 let y = 122; 1282 subtitle.forEach(line => { doc.text(line, 18, y); y += 6; }); 1283 1284 // Meta block 1285 y = 175; 1286 doc.setFontSize(8); 1287 doc.setTextColor(120, 116, 110); 1288 doc.text('DATE', 18, y); 1289 doc.text('PLATFORM', 80, y); 1290 doc.text('STUDY DURATION', 140, y); 1291 y += 5; 1292 doc.setFontSize(9); 1293 doc.setTextColor(242, 239, 233); 1294 doc.text(today, 18, y); 1295 doc.text('FlyGym v3.0', 80, y); 1296 doc.text('60 days', 140, y); 1297 1298 if (compound) { 1299 y += 10; 1300 doc.setFontSize(8); 1301 doc.setTextColor(120, 116, 110); 1302 doc.text('COMPOUND', 18, y); 1303 y += 5; 1304 doc.setFontSize(9); 1305 doc.setTextColor(242, 239, 233); 1306 doc.text(compound, 18, y); 1307 } 1308 1309 // Footer 1310 doc.setFontSize(7); 1311 doc.setTextColor(80, 78, 74); 1312 doc.text('Juvion Health Sciences · EPFL Innovation Park, Lausanne · juvionhs.com', W / 2, H - 12, { align: 'center' }); 1313 doc.text('This is a sample report. Data shown is illustrative only.', W / 2, H - 8, { align: 'center' }); 1314 1315 // ââ Page 2: Executive Summary ââââââââââââââââââââââââââ 1316 doc.addPage(); 1317 doc.setFillColor(8, 8, 8); 1318 doc.rect(0, 0, W, H, 'F'); 1319 doc.setFillColor(254, 102, 3); 1320 doc.rect(0, 0, 6, H, 'F'); 1321 1322 doc.setFontSize(14); 1323 doc.setFont('helvetica', 'bold'); 1324 doc.setTextColor(242, 239, 233); 1325 doc.text('Executive Summary', 18, 36); 1326 doc.setDrawColor(50, 48, 44); 1327 doc.line(18, 40, W - 18, 40); 1328 1329 const summaryLines = doc.splitTextToSize( 1330 'The compound submitted by ' + displayCompany + ' was screened across the full FlyGym behavioral assay panel over a 60-day study period. Results indicate a statistically significant improvement in age-dependent mobility, with treated cohorts maintaining locomotor performance 38% above control at day 42
1330(p < 0.01). Lifespan extension of 18% was observed relative to the untreated control group. Grip strength data revealed a dose-dependent response consistent with preservation of neuromuscular junction function. No adverse toxicity signals were detected at the tested concentration range.', 1331 W - 36 1332 ); 1333 doc.setFontSize(9); 1334 doc.setFont('helvetica', 'normal'); 1335 doc.setTextColor(200, 196, 190); 1336 doc.text(summaryLines, 18, 50); 1337 1338 // Key findings chips 1339 y = 50 + summaryLines.length * 5 + 10; 1340 doc.setFontSize(8); 1341 doc.setTextColor(120, 116, 110); 1342 doc.text('KEY FINDINGS', 18, y); 1343 y += 7; 1344 const findings = ['+38% mobility vs control', '+18% lifespan', 'No toxicity detected', 'NMJ function preserved']; 1345 findings.forEach((f, i) => { 1346 doc.setFillColor(30, 28, 25); 1347 doc.roundedRect(18 + i * 48, y - 5, 44, 9, 1.5, 1.5, 'F'); 1348 doc.setFontSize(7.5); 1349 doc.setTextColor(254, 102, 3); 1350 doc.text(f, 18 + i * 48 + 22, y, { align: 'center' }); 1351 }); 1352 1353 y += 18; 1354 doc.setFontSize(14); 1355 doc.setFont('helvetica', 'bold'); 1356 doc.setTextColor(242, 239, 233); 1357 doc.text('Mobility Performance', 18, y); 1358 doc.setDrawColor(50, 48, 44); 1359 doc.line(18, y + 4, W - 18, y + 4); 1360 1361 y += 14; 1362 doc.setFontSize(9); 1363 doc.setFont('helvetica', 'normal'); 1364 doc.setTextColor(200, 196, 190); 1365 const mobLines = doc.splitTextToSize( 1366 'Locomotion tracking was performed continuously across the full study duration using FlyGym\'s automated camera array. Distance traveled per 10-minute window was averaged across cohorts of n=30 per group. The treated cohort maintained significantly higher locomotor activity throughout the aging period, with divergence from control becoming statistically significant from day 20 onward.', 1367 W - 36 1368 ); 1369 doc.text(mobLines, 18, y); 1370 1371 // Placeholder chart area 1372 y += mobLines.length * 5 + 8; 1373 doc.setFillColor(18, 16, 14); 1374 doc.roundedRect(18, y, W - 36, 55, 2, 2, 'F'); 1375 doc.setFontSize(8); 1376 doc.setTextColor(80, 78, 74); 1377 doc.text('[Mobility performance chart â illustrative data]', W / 2, y + 28, { align: 'center' }); 1378 doc.setFontSize(7); 1379 doc.text('Figure 1. Mean locomotor distance (cm/10 min) over lifespan. Treated (orange) vs. Control (grey).', W / 2, y + 57, { align: 'center' }); 1380 1381 // Footer p2 1382 doc.setFontSize(7); 1383 doc.setTextColor(80, 78, 74); 1384 doc.text('Juvion Health Sciences · SAMPLE REPORT', W / 2, H - 8, { align: 'center' }); 1385 doc.text('2', W - 18, H - 8, { align: 'right' }); 1386 1387 // ââ Save âââââââââââââââââââââââââââââââââââââââââââââââââ 1388 const filename = 'FlyGym_Sample_Report_' + displayCompany.replace(/\s+/g, '_') + '.pdf'; 1389 doc.save(filename); 1390 closeModal(); 1391 form.reset(); 1392 }); 1393 })(); 1394 </script>
1394 1395 1396 <!-- ââ FEASIBILITY MODAL JS âââââââââââââââââââââââââââââââââ --> 1397
1397<script> 1398 (function() { 1399 const modal = document.getElementById('feasibilityModal'); 1400 const openBtn = document.getElementById('feasibilityCta'); 1401 const closeBtn = document.getElementById('feasibilityClose'); 1402 const backdrop = document.getElementById('feasibilityBackdrop'); 1403 const form = document.getElementById('feasibilityForm'); 1404 const status = document.getElementById('feasibilityStatus'); 1405 if (!modal || !openBtn) return; 1406 1407 function openModal() { modal.classList.add('is-open'); document.body.style.overflow = 'hidden'; } 1408 function closeModal() { modal.classList.remove('is-open'); document.body.style.overflow = ''; } 1409 1410 openBtn.addEventListener('click', openModal); 1411 closeBtn.addEventListener('click', closeModal); 1412 backdrop.addEventListener('click', closeModal); 1413 document.addEventListener('keydown', e => { if (e.key === 'Escape') closeModal(); }); 1414 1415 form.addEventListener('submit', async (e) => { 1416 e.preventDefault(); 1417 const btn = form.querySelector('[type="submit"]'); 1418 btn.textContent = 'Sendingâ¦'; 1419 btn.disabled = true; 1420 try { 1421 const res = await fetch('https://formspree.io/f/mreyqnyz', { 1422 method: 'POST', 1423 headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' }, 1424 body: JSON.stringify({ 1425 inquiry_type: 'Feasibility Call', 1426 name: form.querySelector('[name="name"]').value, 1427 email: form.querySelector('[name="email"]').value, 1428 company: form.querySelector('[name="company"]').value, 1429 subject: form.querySelector('[name="subject"]').value, 1430 message: form.querySelector('[name="message"]').value, 1431 _replyto: form.querySelector('[name="email"]').value, 1432 }), 1433 }); 1434 if (res.ok) { 1435 status.textContent = 'Request sent. We\'ll be in touch soon.'; 1436 status.style.color = '#2a9d5c'; 1437 form.reset(); 1438 } else { throw new Error(); } 1439 } catch { 1440 status.textContent = 'Something went wrong. Please email [email protected] directly.'; 1441 status.style.color = 'rgba(255,80,80,0.85)'; 1442 } 1443 btn.textContent = 'Submit request â'; 1444 btn.disabled = false; 1445 }); 1446 })(); 1447 </script>
1447 1448 1449 <!-- ââ Neuromuscular Transmission Plot âââââââââââââââââââââââ --> 1450
1450<script> 1451 (function() { 1452 const vid = document.getElementById('nmjVideo'); 1453 const label = document.getElementById('nmjLabel'); 1454 if (!vid || !label) return; 1455 1456 vid.addEventListener('loadedmetadata', () => { 1457 vid.playbackRate = 0.5; 1458 }); 1459 1460 vid.addEventListener('timeupdate', () => { 1461 const mid = vid.duration / 2; 1462 if (vid.currentTime >= mid) { 1463 label.textContent = 'OLD'; 1464 label.style.color = '#bbbbbb'; 1465 } else { 1466 label.textContent = 'YOUNG'; 1467 label.style.color = '#fe6603'; 1468 } 1469 }); 1470 })(); 1471 </script>
1471 1472 1473
1473<script src="https://cdn.jsdelivr.net/npm/d3@7/dist/d3.min.js"></script>
1473 1474
1474<script> 1475 (function() { 1476 const wrap = document.getElementById('commPlotWrap'); 1477 const svg = d3.select('#commPlotSvg'); 1478 if (!wrap) return; 1479 1480 const MARGIN = { top: 20, right: 10, bottom: 22, left: 42 }; 1481 const GAP = 32; 1482 const POINTS = 600; 1483 const FPS = 30; 1484 const DT = 1 / FPS; 1485 1486 function youngSample() { 1487 let v = -800 + (Math.random() - 0.5) * 100; 1488 if (Math.random() < 0.006) v -= Math.random() * 520 + 180; 1489 return v; 1490 } 1491 function oldSample() { 1492 let v = -2300 + (Math.random() - 0.5) * 55; 1493 if (Math.random() < 0.002) v -= Math.random() * 130 + 60; 1494 return v; 1495 } 1496 1497 const youngData = Array.from({ length: POINTS }, (_, i) => youngSample(i * DT)); 1498 const oldData = Array.from({ length: POINTS }, (_, i) => oldSample(i * DT)); 1499 1500 let W, H, panelW, panelH; 1501 let xScale, yYoung, yOld; 1502 let youngLine, oldLine; 1503 let youngPath, oldPath; 1504 let timeOffset = 0; 1505 1506 function build() { 1507 svg.selectAll('*').remove(); 1508 1509 const cs = getComputedStyle(wrap); 1510 const padV = parseFloat(cs.paddingTop) + parseFloat(cs.paddingBottom); 1511 const padH = parseFloat(cs.paddingLeft) + parseFloat(cs.paddingRight); 1512 W = wrap.clientWidth - padH; 1513 H = wrap.clientHeight - padV; 1514 panelW = (W - MARGIN.left - MARGIN.right - GAP) / 2; 1515 panelH = H - MARGIN.top - MARGIN.bottom; 1516 1517 xScale = d3.scaleLinear().domain([0, POINTS * DT]).range([0, panelW]); 1518 yYoung = d3.scaleLinear().domain([-1550, -550]).range([panelH, 0]); 1519 yOld = d3.scaleLinear().domain([-3050, -2050]).range([panelH, 0]); 1520 1521 youngLine = d3.line().x((_, i) => xScale(i * DT)).y(d => yYoung(d)).curve(d3.curveLinear); 1522 oldLine = d3.line().x((_, i) => xScale(i * DT)).y(d => yOld(d)).curve(d3.curveLinear); 1523 1524 const defs = svg.append('defs'); 1525 defs.append('clipPath').attr('id', 'clipYoung') 1526 .append('rect').attr('width', panelW).attr('height', panelH); 1527 defs.append('clipPath').attr('id', 'clipOld') 1528 .append('rect').attr('width', panelW).attr('height', panelH); 1529 1530 // ââ Young panel ââ 1531 const gY = svg.append('g') 1532 .attr('transform', `translate(${MARGIN.left},${MARGIN.top})`); 1533 1534 gY.append('rect').attr('width', panelW).attr('height', panelH) 1535 .attr('fill', '#111').attr('rx', 4); 1536 1537 gY.append('g').attr('class', 'grid').selectAll('line') 1538 .data(yYoung.ticks(4)) 1539 .join('line') 1540 .attr('x1', 0).attr('x2', panelW) 1541 .attr('y1', d => yYoung(d)).attr('y2', d => yYoung(d)) 1542 .attr('stroke', '#2a2a2a').attr('stroke-width', 1); 1543 1544 gY.append('g').attr('class', 'axis') 1545 .call(d3.axisLeft(yYoung).ticks(4).tickFormat('').tickSize(3)) 1546 .call(g => g.select('.domain').attr('stroke', '#333')) 1547 .call(g => g.selectAll('.tick line').attr('stroke', '#333')); 1548 1549 gY.append('g').attr('class', 'axis').attr('transform', `translate(0,${panelH})`) 1550 .call(d3.axisBottom(xScale).ticks(5).tickFormat('').tickSize(3)) 1551 .call(g => g.select('.domain').attr('stroke', '#333')) 1552 .call(g => g.selectAll('.tick line').attr('stroke', '#333')); 1553 1554 gY.append('text') 1555 .attr('x', panelW - 8).attr('y', 14) 1556 .attr('text-anchor', 'end') 1557 .attr('fill', '#fe6603').attr('font-size', '10px') 1558 .attr('font-family', 'Inter, sans-serif').attr('font-weight', 600) 1559 .attr('letter-spacing', '0.08em').text('YOUNG'); 1560 1561 gY.append('text') 1562 .attr('transform', 'rotate(-90)') 1563 .attr('x', -panelH / 2).attr('y', -MARGIN.left + 14) 1564 .attr('text-anchor', 'middle') 1565 .attr('fill', '#666').attr('font-size', '9px') 1566 .attr('font-family', 'Inter, sans-serif').text('Nerve activity'); 1567 1568 youngPath = gY.append('g').attr('clip-path', 'url(#clipYoung)') 1569 .append('path') 1570 .attr('fill', 'none') 1571 .attr('stroke', '#fe6603') 1572 .attr('stroke-width', 1) 1573 .attr('stroke-linejoin', 'round'); 1574 1575 // ââ Old panel ââ 1576 const gO = svg.append('g') 1577 .attr('transform', `translate(${MARGIN.left + panelW + GAP},${MARGIN.top})`); 1578 1579 gO.append('rect').attr('width', panelW).attr('height', panelH) 1580 .attr('fill', '#111').attr('rx', 4); 1581 1582 gO.append('g').attr('class', 'grid').selectAll('line') 1583 .data(yOld.ticks(4)) 1584 .join('line') 1585 .attr('x1', 0).attr('x2', panelW) 1586 .attr('y1', d => yOld(d)).attr('y2', d => yOld(d)) 1587 .attr('stroke', '#2a2a2a').attr('stroke-width', 1); 1588 1589 gO.append('g').attr('class', 'axis').attr('transform', `translate(0,${panelH})`) 1590 .call(d3.axisBottom(xScale).ticks(5).tickFormat('').tickSize(3)) 1591 .call(g => g.select('.domain').attr('stroke', '#333')) 1592 .call(g => g.selectAll('.tick line').attr('stroke', '#333')); 1593 1594 gO.append('text') 1595 .attr('x', panelW - 8).attr('y', 14) 1596 .attr('text-anchor', 'end') 1597 .attr('fill', '#bbbbbb').attr('font-size', '10px') 1598 .attr('font-family', 'Inter, sans-serif').attr('font-weight', 600) 1599 .attr('letter-spacing', '0.08em').text('OLD'); 1600 1601 oldPath = gO.append('g').attr('clip-path', 'url(#clipOld)') 1602 .append('path') 1603 .attr('fill', 'none') 1604 .attr('stroke', '#bbbbbb') 1605 .attr('stroke-width', 1) 1606 .attr('stroke-linejoin', 'round'); 1607 1608 svg.append('text') 1609 .attr('x', MARGIN.left + (W - MARGIN.left - MARGIN.right) / 2).attr('y', H - 2) 1610 .attr('text-anchor', 'middle') 1611 .attr('fill', '#555').attr('font-size', '9px') 1612 .attr('font-family', 'Inter, sans-serif').text('Time â'); 1613 } 1614 1615 function tick() { 1616 youngData.shift(); youngData.push(youngSample()); 1617 oldData.shift(); oldData.push(oldSample()); 1618 timeOffset += DT; 1619 youngPath.attr('d', youngLine(youngData)); 1620 oldPath.attr('d', oldLine(oldData)); 1621 } 1622 1623 let rafId; 1624 function loop() { tick(); rafId = requestAnimationFrame(loop); } 1625 1626 const observer = new IntersectionObserver(entries => {
1627 entries.forEach(e => { 1628 if (e.isIntersecting) { if (!rafId) loop(); } 1629 else { cancelAnimationFrame(rafId); rafId = null; } 1630 }); 1631 }, { threshold: 0.1 }); 1632 1633 build(); 1634 observer.observe(wrap); 1635 window.addEventListener('resize', () => { build(); }); 1636 })(); 1637 </script>
1637 1638 1639</body> 1640</html>
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.