PageSourceSearch

https://juvionhs.com/flygym

html juvionhs.com collected 2026-10-09 23:34:54 UTC 89,751 bytes, 1,640 lines download raw bytes

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 &amp; Services</a>
69        <a href="/news"     class="nav-link">News &amp; 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 &amp; Services</a>
81      <a href="/news"     class="nav-mobile-link">News &amp; 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 &amp; 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&amp;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 &amp; 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 &amp; Services</a></li>
614          <li><a href="/products">Products</a></li>
615          <li><a href="/news">News &amp; 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.