Spore Labs AI website prompt
A pixel-perfect recreation of Spore Labs featuring an interactive WebGL hero, responsive layouts, smooth animations.
Now put it in WordPress
You have a site. Now make it yours.
An AI build hands you a front end and stops there. No CMS, no client login, no real ownership: you cannot hand it over, and you cannot change it without going back and re-prompting. UiChemy turns that build into native, editable WordPress you own, in the builder you already use.
Bolt to WordPressClaude to WordPressCodex to WordPressCursor to WordPressLovable to WordPressv0 to WordPressAI to WordPress
Get Started
More prompts
Showing 6 prompts
Frequently asked questions
What does the Spore Labs prompt build?
A pixel-perfect recreation of Spore Labs featuring an interactive WebGL hero, responsive layouts, smooth animations.
Which AI tools does the Spore Labs prompt work with?
It's written for Claude. Paste the whole prompt into Claude and it builds the complete site for you, then bring the result into WordPress with UiChemy. This prompt also has a WordPress version: with the UiChemy MCP connected, Claude builds the page straight into WordPress.
How do I turn the result into a WordPress site?
After the AI builds your site, UiChemy Composer rebuilds it as native, editable WordPress in Elementor or Gutenberg (Bricks coming soon) — you own it and edit it in your builder. See AI to WordPress.
Is the Spore Labs prompt free?
Yes — it's free to copy, no account needed.
Can I edit the website after it's built?
Yes. Once it's in WordPress with UiChemy, every section is native and editable — no re-prompting, no lock-in.
Copy prompt
Copy code
Coming soon
Copy SKILL.md
Coming soon
Spore Labs · Section prompt
# MASTER PROMPT — Spore Labs — Venture studio for hard-tech founders
### Plain HTML, CSS and JavaScript. No WordPress, no page builder, no framework, no build step.
## ▶ HOW TO USE THIS PROMPT
**Paste this whole document into Claude. Nothing to attach.** It carries the complete source of the site plus the address of every image, so Claude can assemble a folder that owns its own assets and hand it back as a zip.
**INSTRUCTION TO CLAUDE — BUILD IT, DO NOT ASK.** Every decision is already made in this document. Do not stop to ask the user questions, do not offer options, and do not ask for confirmation before starting. Write the files, zip them, hand over the archive, then report what you built.
1. Create a folder named `spore-labs` and write out every file in the appendix below **exactly as written** — do not reformat, rename, shorten, reorder or "improve" anything.
2. **Download the 14 images into `assets/`.** The code already points at `assets/…` — nothing in it references a remote host. Run this from inside the folder; it reads the ASSET MANIFEST at the end of this document:
```bash
mkdir -p assets
curl -fsSL 'https://demo.uichemy.com/wp-content/uploads/2026/09/spore-labs__img-f1.jpg' -o 'assets/f1.jpg'
curl -fsSL 'https://demo.uichemy.com/wp-content/uploads/2026/09/spore-labs__img-f2.jpg' -o 'assets/f2.jpg'
curl -fsSL 'https://demo.uichemy.com/wp-content/uploads/2026/09/spore-labs__img-f3.jpg' -o 'assets/f3.jpg'
curl -fsSL 'https://demo.uichemy.com/wp-content/uploads/2026/09/spore-labs__img-lab.jpg' -o 'assets/lab.jpg'
curl -fsSL 'https://demo.uichemy.com/wp-content/uploads/2026/09/spore-labs__img-p-arm.jpg' -o 'assets/p-arm.jpg'
curl -fsSL 'https://demo.uichemy.com/wp-content/uploads/2026/09/spore-labs__img-p-bike.jpg' -o 'assets/p-bike.jpg'
curl -fsSL 'https://demo.uichemy.com/wp-content/uploads/2026/09/spore-labs__img-p-drone.jpg' -o 'assets/p-drone.jpg'
curl -fsSL 'https://demo.uichemy.com/wp-content/uploads/2026/09/spore-labs__img-p-hand.jpg' -o 'assets/p-hand.jpg'
curl -fsSL 'https://demo.uichemy.com/wp-content/uploads/2026/09/spore-labs__img-p-sensor.jpg' -o 'assets/p-sensor.jpg'
curl -fsSL 'https://demo.uichemy.com/wp-content/uploads/2026/09/spore-labs__img-p-solar.jpg' -o 'assets/p-solar.jpg'
curl -fsSL 'https://demo.uichemy.com/wp-content/uploads/2026/09/spore-labs__img-proto.jpg' -o 'assets/proto.jpg'
curl -fsSL 'https://demo.uichemy.com/wp-content/uploads/2026/09/spore-labs__img-seeds.jpg' -o 'assets/seeds.jpg'
curl -fsSL 'https://demo.uichemy.com/wp-content/uploads/2026/09/spore-labs__img-studio.jpg' -o 'assets/studio.jpg'
curl -fsSL 'https://demo.uichemy.com/wp-content/uploads/2026/09/spore-labs__img-team.jpg' -o 'assets/team.jpg'
```
Then check every file arrived and none is a zero-byte or HTML error page:
```bash
ls assets | wc -l # expect 14 files
find assets -size -1k # expect no output
```
If any download fails, retry it — hosts occasionally drop a request. Do **not** fall back to hotlinking the remote URL: the whole point is a folder that works offline.
3. **Zip that folder and give the user the archive.** Its contents:
```
spore-labs/
index.html
_view.html
sections/01-hero.html
sections/02-thesis.html
sections/03-programs.html
sections/04-specimens.html
sections/05-germination.html
sections/06-voices.html
sections/07-footer.html
template.html
assets/
f1.jpg
f2.jpg
f3.jpg
lab.jpg
… 10 more
```
```bash
zip -r spore-labs.zip spore-labs -x '*.DS_Store'
```
Then hand over `spore-labs.zip` as a file the user can download, not as text in the chat.
4. Before handing it over, serve the folder and confirm it runs:
```bash
cd spore-labs && python3 -m http.server 8000 # then open http://localhost:8000
```
Check the page renders, the images load from `assets/` with the network throttled or offline, and scrolling drives the animations.
The finished folder needs no network for its images — they are in `assets/`; only the motion libraries load from a CDN.
Do not rebuild the design from the description below. The description is there so a person can understand and edit the code. The appendix is the deliverable.
---
## ▶ WHAT YOU GET
Spore Labs is a Berlin venture studio that funds, houses and grows early-stage robotics, climate and health-hardware founders. 16-week residency, €150k–€600k first cheques.
**10 code files plus an `assets/` folder holding 14 images, delivered as `spore-labs.zip`.**
| File | What it is |
|---|---|
| `index.html` | a page, 195 KB |
| `_view.html` | a page, 0 KB |
| `sections/01-hero.html` | a page, 48 KB |
| `sections/02-thesis.html` | a page, 14 KB |
| `sections/03-programs.html` | a page, 21 KB |
| `sections/04-specimens.html` | a page, 21 KB |
| `sections/05-germination.html` | a page, 23 KB |
| `sections/06-voices.html` | a page, 24 KB |
| `sections/07-footer.html` | a page, 31 KB |
| `template.html` | a page, 9 KB |
| `assets/` | 14 images, downloaded at build time |
**Libraries, from a CDN, already referenced in the HTML:** `gsap.min.js`, `ScrollTrigger.min.js`, `Flip.min.js`, `lenis.min.js`, `three.min.js`. No npm install, no bundler, no framework.
**Sections (7):** `#top` · `#thesis` · `#programs` · `#specimens` · `#germination` · `#voices` · `#apply`.
---
## ▶ WHERE THE MEDIA COMES FROM
All 14 files (14 images) live in `assets/` inside the folder, and every reference in the markup, the CSS and the script is a relative `assets/…` path. Once the folder is assembled it owns its own media: no CDN, no media library, no external image host. Open it on a laptop with the wifi off and it still renders.
They are fetched **once, at build time**, from the WordPress media library on `demo.uichemy.com` — the same `spore-labs__` files the WordPress build of this page uses. That is only how they travel into the folder; nothing in the delivered code points back at that host. Replacing an image means dropping a new file into `assets/` under the same name.
The manifest at the end of this document lists every source URL against the filename it is saved as, so the download step is reproducible and checkable.
---
## ▶ WHAT THIS PROMPT DELIBERATELY DOES NOT CONTAIN
- No WordPress, no page builder, and none of the wrapper classes or compatibility CSS a builder needs. The WordPress build is a separate prompt.
- No framework, no TypeScript, no bundler, no `package.json`.
- No backend. Any form here validates and replies in place; it posts nowhere.
- No CMS. The content lives in the files.
- No external image host. The images ship inside the zip; nothing in the code calls out to a media library at runtime.
---
## ASSET MANIFEST — download these into `assets/`
Source is the media library on `demo.uichemy.com`. Save each one under the name in the second column; the code already refers to it by that name.
| Download from | Save as |
|---|---|
| `https://demo.uichemy.com/wp-content/uploads/2026/09/spore-labs__img-f1.jpg` | `assets/f1.jpg` |
| `https://demo.uichemy.com/wp-content/uploads/2026/09/spore-labs__img-f2.jpg` | `assets/f2.jpg` |
| `https://demo.uichemy.com/wp-content/uploads/2026/09/spore-labs__img-f3.jpg` | `assets/f3.jpg` |
| `https://demo.uichemy.com/wp-content/uploads/2026/09/spore-labs__img-lab.jpg` | `assets/lab.jpg` |
| `https://demo.uichemy.com/wp-content/uploads/2026/09/spore-labs__img-p-arm.jpg` | `assets/p-arm.jpg` |
| `https://demo.uichemy.com/wp-content/uploads/2026/09/spore-labs__img-p-bike.jpg` | `assets/p-bike.jpg` |
| `https://demo.uichemy.com/wp-content/uploads/2026/09/spore-labs__img-p-drone.jpg` | `assets/p-drone.jpg` |
| `https://demo.uichemy.com/wp-content/uploads/2026/09/spore-labs__img-p-hand.jpg` | `assets/p-hand.jpg` |
| `https://demo.uichemy.com/wp-content/uploads/2026/09/spore-labs__img-p-sensor.jpg` | `assets/p-sensor.jpg` |
| `https://demo.uichemy.com/wp-content/uploads/2026/09/spore-labs__img-p-solar.jpg` | `assets/p-solar.jpg` |
| `https://demo.uichemy.com/wp-content/uploads/2026/09/spore-labs__img-proto.jpg` | `assets/proto.jpg` |
| `https://demo.uichemy.com/wp-content/uploads/2026/09/spore-labs__img-seeds.jpg` | `assets/seeds.jpg` |
| `https://demo.uichemy.com/wp-content/uploads/2026/09/spore-labs__img-studio.jpg` | `assets/studio.jpg` |
| `https://demo.uichemy.com/wp-content/uploads/2026/09/spore-labs__img-team.jpg` | `assets/team.jpg` |
---
# APPENDIX · VERBATIM SOURCE
The complete source of every file. **Write them out exactly** — do not change a value, a comment, a class name, or the order of a declaration. Recreate this file layout:
```
index.html
_view.html
sections/01-hero.html
sections/02-thesis.html
sections/03-programs.html
sections/04-specimens.html
sections/05-germination.html
sections/06-voices.html
sections/07-footer.html
template.html
```
## `index.html`
```html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' fill='%23fff'/%3E%3Ccircle cx='16' cy='16' r='5' fill='%232EE06F'/%3E%3Cg stroke='%230C0D0F' stroke-width='2'%3E%3Cpath d='M16 2v6M16 24v6M2 16h6M24 16h6M6 6l4 4M22 22l4 4M26 6l-4 4M10 22l-4 4'/%3E%3C/g%3E%3C/svg%3E">
<title>Spore Labs — Venture studio for hard-tech founders</title>
<meta name="description" content="Spore Labs is a Berlin venture studio that funds, houses and grows early-stage robotics, climate and health-hardware founders. 16-week residency, €150k–€600k first cheques.">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Archivo:wdth,[email protected],300..900&family=Geist:[email protected]&family=Geist+Mono:wght@400;500;600&display=swap" rel="stylesheet">
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/gsap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/ScrollTrigger.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/Flip.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/lenis.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<style>
/* ---------- SPORE LABS locked palette (white + ink base, one accent) ---------- */
:root{
--bg:#FFFFFF; /* page */
--fog:#EEF0F2; /* cool grey panels / cards (never cream) */
--line:#D6D9DE; /* hairlines on white */
--ink:#0C0D0F; /* text, 1px rules, ink buttons — never a section background */
--mute:#5E636B; /* secondary text */
--spore:#2EE06F; /* the one accent: fills, dots, progress, highlights (ink text on it) */
--spore-deep:#13A94C; /* accent hover / accent text on white at large sizes only */
--spore-soft:#DDF9E7; /* accent tint for chips / selections */
--disp:'Archivo', 'Arial Narrow', sans-serif; /* condensed display: font-stretch 62% */
--body:'Geist', system-ui, sans-serif;
--mono:'Geist Mono', ui-monospace, monospace;
--ease:cubic-bezier(.2,.8,.2,1);
--ease-io:cubic-bezier(.7,0,.2,1);
--gut:clamp(16px,3vw,40px);
--hdr:64px;
}
*,*::before,*::after{box-sizing:border-box}
html.lenis,html.lenis body{height:auto}
.lenis.lenis-smooth{scroll-behavior:auto !important}
.lenis.lenis-stopped{overflow:hidden}
html{scroll-padding-top:var(--hdr)}
body{margin:0;background:var(--bg);color:var(--ink);font-family:var(--body);font-size:16px;line-height:1.55;overflow-x:hidden;-webkit-font-smoothing:antialiased}
::selection{background:var(--spore);color:var(--ink)}
img{display:block;max-width:100%;height:auto}
a{color:inherit}
button,input,textarea,select{font:inherit;color:inherit}
button{background:none;border:0;cursor:pointer;padding:0}
:focus-visible{outline:2px solid var(--ink);outline-offset:3px;box-shadow:0 0 0 5px var(--spore)}
h1,h2,h3,h4{margin:0;font-weight:inherit}
/* ---------- shared atoms ---------- */
.wrap{max-width:1440px;margin-inline:auto;padding-inline:var(--gut)}
/* condensed display type */
.disp{font-family:var(--disp);font-stretch:62%;font-weight:800;text-transform:uppercase;line-height:.84;letter-spacing:-.01em}
/* mono label with a small accent square */
.kick{font-family:var(--mono);font-size:12px;font-weight:500;letter-spacing:.08em;text-transform:uppercase;display:inline-flex;align-items:center;gap:10px}
.kick::before{content:"";width:8px;height:8px;background:var(--spore);flex:none}
.mono{font-family:var(--mono);font-size:13px;letter-spacing:.02em}
/* buttons: square, mono, uppercase */
.btn{display:inline-flex;align-items:center;gap:12px;height:48px;padding:0 22px;background:var(--spore);color:var(--ink);
font-family:var(--mono);font-size:13px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;text-decoration:none;
position:relative;overflow:hidden;isolation:isolate;transition:color .35s var(--ease)}
.btn::after{content:"";position:absolute;inset:0;background:var(--ink);transform:translateY(101%);transition:transform .45s var(--ease);z-index:-1}
.btn:hover{color:#fff}
.btn:hover::after{transform:none}
.btn .arr{width:14px;height:14px;flex:none}
.btn--ink{background:var(--ink);color:#fff}
.btn--ink::after{background:var(--spore)}
.btn--ink:hover{color:var(--ink)}
.btn--line{background:transparent;box-shadow:inset 0 0 0 1px var(--ink)}
/* corner ticks around a box: add class .ticks to any position:relative element */
.ticks{position:relative}
.ticks::before,.ticks::after{content:"";position:absolute;width:10px;height:10px;pointer-events:none;border:0 solid var(--ink)}
.ticks::before{top:-1px;left:-1px;border-top-width:1px;border-left-width:1px}
.ticks::after{bottom:-1px;right:-1px;border-bottom-width:1px;border-right-width:1px}
/* word split */
.w{display:inline-block;overflow:hidden;vertical-align:top;padding-bottom:.08em;margin-bottom:-.08em}
.w>span{display:inline-block;will-change:transform}
/* reveal-on-scroll default state is set by JS only (no-JS = visible) */
</style>
</head>
<body>
<script>
/* Shared runtime: window.SP
SP.reduced true under prefers-reduced-motion
SP.lenis smooth scroll (null when reduced), synced to ScrollTrigger
SP.split(el) wrap each word in <span class="w"><span>..</span></span>, returns inner spans
SP.rise(el,opts) split + words rise in when el enters the viewport (once)
SP.scramble(el,o) decode el's text from random glyphs, left to right. o: {duration, delay, text}
SP.scrambleOnView(el,o) same, triggered once when el enters
SP.count(el) counts el from 0 to data-to (supports data-prefix, data-suffix, data-dec) when it enters
SP.magnet(el,s) element follows the pointer a little while hovered (s = strength px)
Elements with [data-sp-reveal] fade up automatically on enter; [data-sp-scramble] scramble on enter. */
window.SP=(function(){
gsap.registerPlugin(ScrollTrigger,Flip);
const reduced=matchMedia('(prefers-reduced-motion: reduce) and (update: none)').matches;
let lenis=null;
if(!reduced){
lenis=new Lenis({duration:1.15,easing:t=>Math.min(1,1.001-Math.pow(2,-10*t))});
lenis.on('scroll',ScrollTrigger.update);
gsap.ticker.add(t=>lenis.raf(t*1000));
gsap.ticker.lagSmoothing(0);
document.addEventListener('click',e=>{const a=e.target.closest('a[href^="#"]');if(!a)return;const id=a.getAttribute('href');
if(id.length<2)return;const t=document.querySelector(id);if(!t)return;e.preventDefault();lenis.scrollTo(t,{offset:0,duration:1.4})});
}
function split(el){
const walk=n=>{[...n.childNodes].forEach(c=>{
if(c.nodeType===3){const f=document.createDocumentFragment();
c.textContent.split(/(\s+)/).forEach(t=>{if(!t)return;if(/^\s+$/.test(t)){f.appendChild(document.createTextNode(t));return}
const o=document.createElement('span');o.className='w';const i=document.createElement('span');i.textContent=t;o.appendChild(i);f.appendChild(o)});
c.replaceWith(f)}
else if(c.nodeType===1&&c.tagName!=='BR'&&!c.classList.contains('w'))walk(c)})};
walk(el);return [...el.querySelectorAll('.w>span')];
}
function rise(el,opts={}){
const s=split(el);if(reduced)return s;
gsap.from(s,{yPercent:110,duration:1,ease:'expo.out',stagger:.045,...opts,scrollTrigger:{trigger:el,start:'top 88%',once:true}});
return s;
}
const GLYPHS='01<>/\\[]{}#*+=-_:;';
function scramble(el,o={}){
const final=o.text!=null?o.text:(el.dataset.final||(el.dataset.final=el.textContent));
if(reduced){el.textContent=final;return}
const dur=(o.duration||.9)*1000,delay=(o.delay||0)*1000;let t0=null;
const step=ts=>{if(t0==null)t0=ts;const p=Math.min(1,(ts-t0)/dur);const n=Math.floor(p*final.length);let s='';
for(let i=0;i<final.length;i++){const ch=final[i];s+=(i<n||ch===' ')?ch:GLYPHS[(Math.random()*GLYPHS.length)|0]}
el.textContent=s;if(p<1)requestAnimationFrame(step);else el.textContent=final};
setTimeout(()=>requestAnimationFrame(step),delay);
}
function scrambleOnView(el,o={}){
if(reduced)return;
ScrollTrigger.create({trigger:el,start:'top 90%',once:true,onEnter:()=>scramble(el,o)});
}
function count(el){
const to=parseFloat(el.dataset.to),dec=+(el.dataset.dec||0),pre=el.dataset.prefix||'',suf=el.dataset.suffix||'';
const fmt=v=>pre+v.toLocaleString('en-US',{minimumFractionDigits:dec,maximumFractionDigits:dec})+suf;
if(reduced){el.textContent=fmt(to);return}
el.textContent=fmt(0);const o={v:0};
ScrollTrigger.create({trigger:el,start:'top 90%',once:true,onEnter:()=>gsap.to(o,{v:to,duration:1.6,ease:'power3.out',onUpdate:()=>el.textContent=fmt(o.v)})});
}
function magnet(el,s=10){
if(reduced||matchMedia('(hover:none)').matches)return;
el.addEventListener('pointermove',e=>{const r=el.getBoundingClientRect();
gsap.to(el,{x:((e.clientX-r.left)/r.width-.5)*s*2,y:((e.clientY-r.top)/r.height-.5)*s*2,duration:.4,ease:'power3.out'})});
el.addEventListener('pointerleave',()=>gsap.to(el,{x:0,y:0,duration:.6,ease:'elastic.out(1,.4)'}));
}
document.addEventListener('DOMContentLoaded',()=>{
document.querySelectorAll('[data-sp-scramble]').forEach(el=>scrambleOnView(el));
if(reduced)return;
document.querySelectorAll('[data-sp-reveal]').forEach(el=>gsap.from(el,{y:40,autoAlpha:0,duration:1,ease:'expo.out',
scrollTrigger:{trigger:el,start:'top 90%',once:true}}));
});
return {reduced,lenis,split,rise,scramble,scrambleOnView,count,magnet};
})();
</script>
<div class="sh-load" aria-hidden="true">
<div class="sh-load__top"><span>Spore/Labs</span><span>Germinating — Halle 3, Berlin-Wedding</span></div>
<svg class="sh-load__mark" viewBox="0 0 32 32">
<g stroke="currentColor" stroke-width="1.6" stroke-linecap="square" fill="none">
<path d="M16 2.5V7.5M16 24.5V29.5M2.5 16H7.5M24.5 16H29.5M6.45 6.45 9.99 9.99M22.01 22.01 25.55 25.55M25.55 6.45 22.01 9.99M9.99 22.01 6.45 25.55"/>
</g>
<circle class="sh-load__dot" cx="16" cy="16" r="4.2"/>
</svg>
<div class="sh-load__bot">
<div class="sh-load__row"><span class="sh-load__num"><span class="sh-load__n">000</span><span class="sh-load__pc">%</span></span><span class="sh-load__cap">Counting seeds</span></div>
<div class="sh-load__line"><i></i></div>
</div>
</div>
<header class="sh-hdr">
<div class="sh-hdr__in">
<a class="sh-logo" href="#top" aria-label="Spore Labs, back to top">
<svg class="sh-logo__mark" viewBox="0 0 32 32" aria-hidden="true">
<g class="sh-logo__rays" stroke="currentColor" stroke-width="2.2" stroke-linecap="square" fill="none">
<path d="M16 2.5V7.5M16 24.5V29.5M2.5 16H7.5M24.5 16H29.5M6.45 6.45 9.99 9.99M22.01 22.01 25.55 25.55M25.55 6.45 22.01 9.99M9.99 22.01 6.45 25.55"/>
</g>
<circle class="sh-logo__dot" cx="16" cy="16" r="4.6"/>
</svg>
<span class="sh-logo__word">Spore<i>/</i>Labs</span>
</a>
<nav class="sh-nav" aria-label="Primary">
<a href="#thesis">Thesis</a>
<a href="#programs">Programs</a>
<a href="#specimens">Portfolio</a>
<a href="#germination">Process</a>
<a href="#voices">Founders</a>
</nav>
<a class="btn sh-hdr__cta" href="#apply"><span>Apply<span class="sh-hdr__long"> — autumn cohort</span></span><svg class="arr" viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true"><path d="M2 12 12 2M4 2h8v8"/></svg></a>
<button class="sh-burger" type="button" aria-expanded="false" aria-controls="sh-menu" aria-label="Open menu"><span></span><span></span></button>
</div>
</header>
<div class="sh-menu" id="sh-menu" aria-hidden="true">
<nav class="sh-menu__nav" aria-label="Menu">
<a href="#thesis"><span class="sh-menu__i">01</span><span class="sh-menu__t">Thesis</span></a>
<a href="#programs"><span class="sh-menu__i">02</span><span class="sh-menu__t">Programs</span></a>
<a href="#specimens"><span class="sh-menu__i">03</span><span class="sh-menu__t">Portfolio</span></a>
<a href="#germination"><span class="sh-menu__i">04</span><span class="sh-menu__t">Process</span></a>
<a href="#voices"><span class="sh-menu__i">05</span><span class="sh-menu__t">Founders</span></a>
</nav>
<div class="sh-menu__foot">
<a class="btn" href="#apply">Apply — autumn cohort<svg class="arr" viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true"><path d="M2 12 12 2M4 2h8v8"/></svg></a>
<p class="mono sh-menu__note">Applications close 30 Nov 2026<br><a href="mailto:[email protected]">[email protected]</a></p>
</div>
</div>
<section id="top" class="sh-hero" aria-labelledby="sh-title">
<h1 id="sh-title" class="sh-vh">Spore Labs — venture studio for hard-tech founders</h1>
<div class="sh-frame ticks" aria-hidden="true"></div>
<div class="sh-rule" aria-hidden="true"><i class="sh-soil"></i></div>
<div class="sh-words disp" aria-hidden="true">
<span class="sh-line sh-line--a"><span class="sh-line__in">We plant</span></span>
<span class="sh-line sh-line--b"><span class="sh-line__in">Hard ideas</span></span>
</div>
<canvas class="sh-cv sh-cv--a" aria-hidden="true"></canvas>
<canvas class="sh-cv sh-cv--b" aria-hidden="true"></canvas>
<div class="sh-zone" aria-hidden="true"></div>
<p class="sh-meta sh-meta--tl kick">N°041 / Venture studio</p>
<p class="sh-meta sh-meta--tr mono"><span>52.5446° N,</span> <span>13.3669° E</span></p>
<div class="sh-band">
<p class="sh-pitch">Money, a machine shop and 16 weeks of stubborn help<br class="sh-br"> for robotics, climate and health-hardware founders.</p>
<button class="sh-hold" type="button" aria-label="Press and hold to scatter the seeds. Space bar works too.">
<span class="sh-hold__box" aria-hidden="true"><i></i></span>
<span class="sh-hold__lab" aria-hidden="true">Hold to scatter</span>
<span class="sh-hold__bar" aria-hidden="true"><i></i></span>
<span class="sh-hold__pct" aria-hidden="true">00</span>
</button>
<a class="btn btn--ink sh-cta" href="#specimens">See the portfolio<svg class="arr" viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true"><path d="M2 12 12 2M4 2h8v8"/></svg></a>
</div>
<div class="sh-cur" aria-hidden="true"><i class="sh-cur__fill"></i><span class="sh-cur__t">Hold</span></div>
</section>
<style>
/* ================= header ================= */
.sh-hdr{position:fixed;top:0;left:0;right:0;height:var(--hdr);z-index:100;background:#fff;border-bottom:1px solid var(--line);
transition:transform .55s var(--ease),background-color .35s linear}
.sh-hdr.is-scrolled{background:rgba(255,255,255,.8);-webkit-backdrop-filter:blur(14px) saturate(1.3);backdrop-filter:blur(14px) saturate(1.3)}
.sh-hdr.is-hidden{transform:translateY(-101%)}
.sh-hdr.is-open{transform:none;background:#fff;-webkit-backdrop-filter:none;backdrop-filter:none}
.sh-hdr__in{height:100%;display:flex;align-items:center;gap:20px;padding-inline:var(--gut)}
.sh-logo{display:inline-flex;align-items:center;gap:10px;color:var(--ink);text-decoration:none;flex:none}
.sh-logo__mark{width:28px;height:28px;flex:none;overflow:visible}
.sh-logo__rays{transform-origin:16px 16px;transition:transform .9s var(--ease)}
.sh-logo:hover .sh-logo__rays{transform:rotate(45deg) scale(1.08)}
.sh-logo__dot{fill:var(--spore)}
.sh-logo__word{font-family:var(--disp);font-stretch:62%;font-weight:800;font-size:25px;line-height:1;letter-spacing:.005em;text-transform:uppercase}
.sh-logo__word i{font-style:normal;color:var(--mute);margin:0 .04em}
.sh-nav{margin-left:auto;display:flex;align-items:center}
.sh-nav a{position:relative;display:inline-block;padding:10px 13px;font-family:var(--mono);font-size:12px;font-weight:500;letter-spacing:.08em;
text-transform:uppercase;text-decoration:none;color:var(--ink);white-space:nowrap}
.sh-nav a::before{content:"";position:absolute;left:13px;right:13px;bottom:5px;height:1px;background:var(--ink);transform:scaleX(0);transform-origin:100% 50%;transition:transform .5s var(--ease)}
.sh-nav a::after{content:"";position:absolute;left:3px;top:50%;width:5px;height:5px;margin-top:-3px;background:var(--spore);transform:scale(0);transition:transform .35s var(--ease)}
.sh-nav a:hover::before{transform:scaleX(1);transform-origin:0 50%}
.sh-nav a:hover::after{transform:scale(1)}
.sh-hdr__cta.btn{height:40px;padding:0 16px;margin-left:10px;flex:none}
.sh-burger{display:none;position:relative;width:44px;height:44px;margin-left:auto;box-shadow:inset 0 0 0 1px var(--ink);flex:none}
.sh-burger span{position:absolute;left:12px;right:12px;top:50%;height:1.5px;margin-top:-.75px;background:var(--ink);transition:transform .5s var(--ease)}
.sh-burger span:first-child{transform:translateY(-4px)}
.sh-burger span:last-child{transform:translateY(4px)}
.sh-burger[aria-expanded="true"] span:first-child{transform:rotate(45deg)}
.sh-burger[aria-expanded="true"] span:last-child{transform:rotate(-45deg)}
@media (max-width:1180px){.sh-hdr__long{display:none}}
@media (max-width:899px){
.sh-nav,.sh-hdr__cta.btn{display:none}
.sh-burger{display:block}
.sh-logo__word{font-size:23px}
}
html.sh-lock{overflow:hidden}
/* mobile menu: full-screen white panel under the header */
.sh-menu{position:fixed;inset:0;z-index:99;background:#fff;padding:calc(var(--hdr) + 14px) var(--gut) 24px;display:flex;flex-direction:column;
visibility:hidden;clip-path:inset(0 0 100% 0);overflow-y:auto;overscroll-behavior:contain}
.sh-menu.is-open{visibility:visible}
.sh-menu__nav{display:flex;flex-direction:column}
.sh-menu__nav a{display:flex;align-items:flex-end;gap:14px;padding:12px 0 8px;border-top:1px solid var(--line);text-decoration:none;color:var(--ink);overflow:hidden}
.sh-menu__nav a:last-child{border-bottom:1px solid var(--line)}
.sh-menu__i{display:inline-block;width:26px;flex:none;font-family:var(--mono);font-size:12px;color:var(--mute);padding-bottom:.6em}
.sh-menu__t{display:inline-block;font-family:var(--disp);font-stretch:62%;font-weight:800;text-transform:uppercase;font-size:clamp(52px,15vw,112px);line-height:.86}
.sh-menu__nav a:active .sh-menu__t,.sh-menu__nav a:hover .sh-menu__t{color:var(--spore-deep)}
.sh-menu__foot{margin-top:auto;padding-top:28px;display:flex;flex-direction:column;align-items:flex-start;gap:16px}
.sh-menu__note{margin:0;color:var(--mute);line-height:1.6}
.sh-menu__note a{color:var(--ink)}
@media (min-width:900px){.sh-menu{display:none}}
/* ================= hero ================= */
.sh-hero{--sh-band:104px;position:relative;height:100vh;height:100svh;min-height:640px;overflow:hidden;background:var(--bg);color:var(--ink);isolation:isolate}
.sh-vh{position:absolute!important;width:1px;height:1px;margin:-1px;padding:0;border:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
.sh-frame{position:absolute;z-index:3;top:calc(var(--hdr) + 14px);bottom:14px;left:var(--gut);right:var(--gut);
border-left:1px solid var(--line);border-right:1px solid var(--line);pointer-events:none}
.sh-rule{position:absolute;z-index:3;left:0;right:0;bottom:var(--sh-band);height:1px;background:var(--line);pointer-events:none}
.sh-soil{position:absolute;left:50%;top:0;width:9px;height:9px;margin:-4px 0 0 -4.5px;background:var(--spore)}
.sh-words{position:absolute;z-index:1;left:0;right:0;top:calc(var(--hdr) + 14px);bottom:var(--sh-band);display:flex;flex-direction:column;
align-items:center;justify-content:center;font-size:var(--sh-fs,clamp(96px,19vw,340px));pointer-events:none;-webkit-user-select:none;user-select:none}
.sh-line{display:block;white-space:nowrap;line-height:.84;will-change:transform}
.sh-line--a{padding-right:.5em}
.sh-line--b{padding-left:.5em}
.sh-line__in{display:inline-block}
.sh-cv{position:absolute;inset:0;width:100%;height:100%;display:block;z-index:2;pointer-events:none}
.sh-cv--b{mix-blend-mode:difference}
.sh-zone{position:absolute;z-index:4;left:0;right:0;top:var(--hdr);bottom:var(--sh-band);
-webkit-touch-callout:none;-webkit-user-select:none;user-select:none;touch-action:pan-y;-webkit-tap-highlight-color:transparent}
.sh-meta{position:absolute;z-index:5;top:calc(var(--hdr) + 32px);margin:0}
.sh-meta--tl{left:calc(var(--gut) + 20px)}
.sh-meta--tr{right:calc(var(--gut) + 20px);text-align:right;font-size:12px;letter-spacing:.06em}
.sh-band{position:absolute;z-index:5;left:var(--gut);right:var(--gut);bottom:14px;height:calc(var(--sh-band) - 14px);
display:grid;grid-template-columns:1fr auto 1fr;align-items:center;gap:24px;padding:0 20px}
.sh-pitch{margin:0;font-size:15px;line-height:1.45;max-width:54ch}
.sh-cta{justify-self:end;grid-column:3}
/* hold-to-scatter control */
.sh-hold{display:inline-flex;align-items:center;gap:12px;height:44px;padding:0 6px;font-family:var(--mono);font-size:12px;font-weight:500;
letter-spacing:.08em;text-transform:uppercase;color:var(--ink);-webkit-touch-callout:none;-webkit-user-select:none;user-select:none;touch-action:none}
.sh-hold__box{position:relative;width:16px;height:16px;flex:none;overflow:hidden;box-shadow:inset 0 0 0 1px var(--ink)}
.sh-hold__box i{position:absolute;inset:0;background:var(--spore);transform:scaleY(0);transform-origin:50% 100%}
.sh-hold__lab{min-width:15.5ch;text-align:left}
.sh-hold__bar{position:relative;width:112px;height:8px;flex:none;box-shadow:inset 0 0 0 1px var(--ink)}
.sh-hold__bar i{position:absolute;inset:2px;background:var(--spore);transform:scaleX(0);transform-origin:0 50%}
.sh-hold__pct{width:2.2ch;text-align:right;color:var(--mute);font-variant-numeric:tabular-nums}
.sh-hero.is-holding .sh-hold__box{box-shadow:inset 0 0 0 1px var(--ink),0 0 0 3px var(--spore-soft)}
.sh-hero.sh-red .sh-hold,.sh-hero.sh-nogl .sh-hold,.sh-hero.sh-red .sh-cur,.sh-hero.sh-nogl .sh-cur{display:none}
/* pointer tag (fine pointers only) */
.sh-cur{position:absolute;z-index:6;left:0;top:0;width:64px;height:64px;margin:-32px 0 0 -32px;background:#fff;box-shadow:inset 0 0 0 1px var(--ink);
pointer-events:none;opacity:0;overflow:hidden;display:flex;align-items:flex-end;justify-content:center;padding-bottom:7px;
transition:opacity .25s linear,width .35s var(--ease),height .35s var(--ease),margin .35s var(--ease)}
.sh-cur.is-on{opacity:1}
.sh-cur.is-down{width:76px;height:76px;margin:-38px 0 0 -38px}
.sh-cur::before{content:"";position:absolute;left:50%;top:50%;width:5px;height:5px;margin:-2.5px 0 0 -2.5px;background:var(--ink);z-index:2}
.sh-cur__fill{position:absolute;inset:0;background:var(--spore);transform:scaleY(0);transform-origin:50% 100%}
.sh-cur__t{position:relative;z-index:2;font-family:var(--mono);font-size:10px;font-weight:600;letter-spacing:.1em;text-transform:uppercase}
@media (hover:hover) and (pointer:fine){.sh-hero.is-ready .sh-zone{cursor:none}}
@media (hover:none),(pointer:coarse){.sh-cur{display:none}}
/* intro loader */
.sh-load{position:fixed;inset:0;z-index:400;background:var(--bg);display:flex;flex-direction:column;justify-content:space-between;
padding:calc(var(--gut) + 4px) var(--gut);clip-path:inset(0 0 0 0);animation:sh-failsafe .01s 8s forwards}
.sh-load__top{display:flex;justify-content:space-between;gap:16px;font-family:var(--mono);font-size:12px;font-weight:500;letter-spacing:.08em;text-transform:uppercase}
.sh-load__mark{position:absolute;left:50%;top:50%;width:58px;height:58px;margin:-29px 0 0 -29px;animation:sh-spin 5s linear infinite}
.sh-load__row{display:flex;justify-content:space-between;align-items:flex-end;gap:16px}
.sh-load__num{font-family:var(--mono);font-weight:500;font-size:clamp(64px,11vw,168px);line-height:.8;letter-spacing:-.05em;font-variant-numeric:tabular-nums}
.sh-load__pc{font-size:.3em;letter-spacing:0;vertical-align:top;margin-left:.15em}
.sh-load__cap{font-family:var(--mono);font-size:12px;letter-spacing:.08em;text-transform:uppercase;color:var(--mute)}
.sh-load__line{position:relative;height:2px;margin-top:22px;background:var(--fog)}
.sh-load__dot{fill:var(--spore)}
.sh-load__line i{position:absolute;inset:0;background:var(--spore);transform:scaleX(0);transform-origin:0 50%}
@keyframes sh-spin{to{transform:rotate(360deg)}}
@keyframes sh-failsafe{to{visibility:hidden;pointer-events:none}}
@media (max-width:899px){
.sh-hero{--sh-band:178px;min-height:600px}
.sh-frame{top:calc(var(--hdr) + 10px);bottom:10px}
.sh-cv{z-index:0}
.sh-words{font-size:var(--sh-fs,22vw);top:calc(var(--hdr) + 60px);bottom:calc(var(--sh-band) + 58px)}
.sh-line--a{padding-right:.18em}
.sh-line--b{padding-left:.18em}
.sh-meta{top:calc(var(--hdr) + 24px)}
.sh-meta--tl{left:calc(var(--gut) + 12px)}
.sh-meta--tr{right:calc(var(--gut) + 12px);font-size:11px;line-height:1.4}
.sh-meta--tr span{display:block}
.sh-band{grid-template-columns:1fr;align-content:center;gap:16px;padding:0 12px;bottom:10px;height:calc(var(--sh-band) - 10px)}
.sh-pitch{font-size:14px}
.sh-br{display:none}
.sh-cta{justify-self:start;grid-column:auto}
.sh-hold{position:absolute;left:6px;bottom:calc(100% + 10px)}
.sh-hold__bar,.sh-hold__pct{display:none}
.sh-load__top span:last-child{display:none}
}
</style>
<script>
(function(){
'use strict';
var SP = window.SP || {};
var RED = !!SP.reduced;
var lenis = SP.lenis || null;
var $ = function(s, r){ return (r || document).querySelector(s); };
var $$ = function(s, r){ return Array.prototype.slice.call((r || document).querySelectorAll(s)); };
var hdr = $('.sh-hdr'), hdrIn = $('.sh-hdr__in'), burger = $('.sh-burger'), menu = $('#sh-menu');
var hero = $('#top'), words = $('.sh-words', hero), lineA = $('.sh-line--a', hero), lineB = $('.sh-line--b', hero);
var inA = $('.sh-line--a .sh-line__in', hero), inB = $('.sh-line--b .sh-line__in', hero);
var frame = $('.sh-frame', hero), rule = $('.sh-rule', hero), zone = $('.sh-zone', hero);
var cvA = $('.sh-cv--a', hero), cvB = $('.sh-cv--b', hero);
var holdBtn = $('.sh-hold', hero), holdLab = $('.sh-hold__lab', hero), holdSq = $('.sh-hold__box i', hero),
holdBar = $('.sh-hold__bar i', hero), holdPct = $('.sh-hold__pct', hero);
var cur = $('.sh-cur', hero), curFill = $('.sh-cur__fill', hero), curT = $('.sh-cur__t', hero);
var load = $('.sh-load');
if (RED) hero.classList.add('sh-red');
/* ================= header: hide on scroll down, show on scroll up ================= */
var menuOpen = false, lastY = window.scrollY;
function onScroll(){
var y = window.scrollY;
hdr.classList.toggle('is-scrolled', y > 8);
if (menuOpen){ lastY = y; return; }
if (y < 100){ hdr.classList.remove('is-hidden'); lastY = y; return; }
var dy = y - lastY;
if (Math.abs(dy) < 6) return;
hdr.classList.toggle('is-hidden', dy > 0);
lastY = y;
}
window.addEventListener('scroll', onScroll, { passive: true });
onScroll();
hdr.addEventListener('focusin', function(){ hdr.classList.remove('is-hidden'); });
$$('.sh-nav a', hdr).forEach(function(a){
var txt = a.textContent.trim(), busy = false;
a.addEventListener('pointerenter', function(){
if (RED || busy || !SP.scramble) return;
busy = true;
a.style.minWidth = a.offsetWidth + 'px';
SP.scramble(a, { text: txt, duration: .45 });
setTimeout(function(){ busy = false; a.textContent = txt; }, 540);
});
});
/* ================= mobile menu ================= */
var mLinks = $$('a', menu), mItems = $$('.sh-menu__t, .sh-menu__i', menu), mFoot = $('.sh-menu__foot', menu), mTl = null;
function setMenu(open){
if (open === menuOpen) return;
menuOpen = open;
burger.setAttribute('aria-expanded', open ? 'true' : 'false');
burger.setAttribute('aria-label', open ? 'Close menu' : 'Open menu');
menu.setAttribute('aria-hidden', open ? 'false' : 'true');
hdr.classList.toggle('is-open', open);
document.documentElement.classList.toggle('sh-lock', open);
if (lenis){ open ? lenis.stop() : lenis.start(); }
if (mTl) mTl.kill();
if (open){
hdr.classList.remove('is-hidden');
menu.classList.add('is-open');
if (RED){ gsap.set(menu, { clipPath: 'inset(0% 0% 0% 0%)' }); gsap.set(mItems, { yPercent: 0 }); gsap.set(mFoot, { autoAlpha: 1, y: 0 }); }
else {
mTl = gsap.timeline()
.fromTo(menu, { clipPath: 'inset(0% 0% 100% 0%)' }, { clipPath: 'inset(0% 0% 0% 0%)', duration: .75, ease: 'expo.inOut' })
.fromTo(mItems, { yPercent: 110 }, { yPercent: 0, duration: .95, ease: 'expo.out', stagger: .045 }, .3)
.fromTo(mFoot, { autoAlpha: 0, y: 18 }, { autoAlpha: 1, y: 0, duration: .7, ease: 'power3.out' }, .55);
}
setTimeout(function(){ if (menuOpen && mLinks[0]) mLinks[0].focus({ preventScroll: true }); }, RED ? 0 : 380);
} else {
var done = function(){ menu.classList.remove('is-open'); };
if (RED){ gsap.set(menu, { clipPath: 'inset(0% 0% 100% 0%)' }); done(); }
else mTl = gsap.timeline({ onComplete: done }).to(menu, { clipPath: 'inset(0% 0% 100% 0%)', duration: .6, ease: 'expo.inOut' });
}
}
burger.addEventListener('click', function(){ setMenu(!menuOpen); });
mLinks.forEach(function(a){ a.addEventListener('click', function(){ setMenu(false); }); });
document.addEventListener('keydown', function(e){
if (!menuOpen) return;
if (e.key === 'Escape'){ setMenu(false); burger.focus(); return; }
if (e.key === 'Tab'){
var f = [burger].concat(mLinks), i = f.indexOf(document.activeElement);
e.preventDefault();
var n = e.shiftKey ? (i <= 0 ? f.length - 1 : i - 1) : (i === -1 || i === f.length - 1 ? 0 : i + 1);
f[n].focus();
}
});
var mqDesk = matchMedia('(min-width: 900px)');
var onMq = function(e){ if (e.matches) setMenu(false); };
mqDesk.addEventListener ? mqDesk.addEventListener('change', onMq) : mqDesk.addListener(onMq);
/* ================= hero: word fitting ================= */
var WORDS = ['We plant', 'Hard ideas'], NEXT = ['Yours', 'Is next'];
function fitWords(){
words.style.removeProperty('--sh-fs');
var avail = frame.offsetWidth - (hero.clientWidth < 900 ? 8 : 48);
var mw = Math.max(lineA.offsetWidth, lineB.offsetWidth);
if (mw > avail){
var fs = parseFloat(getComputedStyle(words).fontSize);
words.style.setProperty('--sh-fs', (fs * avail / mw).toFixed(2) + 'px');
}
}
function swapWords(list, dur){
if (!SP.scramble){ inA.textContent = list[0]; inB.textContent = list[1]; return; }
SP.scramble(inA, { text: list[0], duration: dur || .7 });
SP.scramble(inB, { text: list[1], duration: dur || .7, delay: .12 });
}
/* ================= WebGL dandelion ================= */
var GL = false, rA = null, rB = null;
try {
rA = new THREE.WebGLRenderer({ canvas: cvA, alpha: true, antialias: true, powerPreference: 'high-performance' });
rB = new THREE.WebGLRenderer({ canvas: cvB, alpha: true, antialias: true, powerPreference: 'high-performance' });
rA.setClearColor(0xffffff, 0); rB.setClearColor(0xffffff, 0);
GL = true;
} catch (err){ GL = false; }
if (!GL) hero.classList.add('sh-nogl');
var MOB = matchMedia('(max-width: 899px)').matches;
var I = { stem: RED ? 1 : 0, head: RED ? 1 : 0 }; // intro drivers
var prox = { p: 0 }; // scroll progress (scrubbed)
var holding = false, hold = 0, scattered = false, scatterClock = 0, windAmt = 0, idle = 0, introDone = RED;
var visible = true, resetT = 0, labState = '';
var ptrX = 0, ptrY = 0;
var sim = null;
if (GL){ sim = buildSim(); }
function buildSim(){
var sd = 41;
function rnd(){ sd |= 0; sd = sd + 0x6D2B79F5 | 0; var t = Math.imul(sd ^ sd >>> 15, 1 | sd);
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t; return ((t ^ t >>> 14) >>> 0) / 4294967296; }
var K = MOB ? 10 : 12; // filaments per parachute
var R0 = 0.12; // core radius (head radius ~1)
var WANT = MOB ? 220 : 460;
var WX = .78, WY = .55, WZ = .3; // wind: up and to the right, a little toward camera
var wl = Math.hypot(WX, WY, WZ); WX /= wl; WY /= wl; WZ /= wl;
/* ---- Fibonacci sphere, bottom cap left bare for the stem ---- */
var dirs = [], M = Math.round(WANT / 0.94), GA = Math.PI * (3 - Math.sqrt(5));
for (var i = 0; i < M; i++){
var y = 1 - 2 * (i + .5) / M; if (y < -0.88) continue;
var r = Math.sqrt(1 - y * y), th = i * GA;
var x = r * Math.cos(th) + (rnd() - .5) * .03, z = r * Math.sin(th) + (rnd() - .5) * .03; y += (rnd() - .5) * .03;
var l = Math.hypot(x, y, z); dirs.push(x / l, y / l, z / l);
}
var N = dirs.length / 3, F = Math.round(N * 1.6), U = N + F;
var D3 = new Float32Array(dirs), E1 = new Float32Array(N * 3);
var LS = new Float32Array(N), PL = new Float32Array(N), OPEN = new Float32Array(N), TAU = new Float32Array(N),
FLEX = new Float32Array(N), PH = new Float32Array(N), ST = new Float32Array(N), SDIST = new Float32Array(N), WAVE = new Float32Array(N);
var GROW = new Float32Array(N), GDEL = new Float32Array(N), PRES = new Uint8Array(N);
var FTH = new Float32Array(N * K), FLN = new Float32Array(N * K);
for (i = 0; i < N; i++){
var dx = D3[i*3], dy = D3[i*3+1], dz = D3[i*3+2];
var ax = rnd() - .5, ay = rnd() - .5, az = rnd() - .5, dd = ax*dx + ay*dy + az*dz;
ax -= dx*dd; ay -= dy*dd; az -= dz*dd; l = Math.hypot(ax, ay, az) || 1;
E1[i*3] = ax / l; E1[i*3+1] = ay / l; E1[i*3+2] = az / l;
LS[i] = .6 + rnd() * .08; PL[i] = .29 + rnd() * .07; OPEN[i] = 1.2 + rnd() * .2;
TAU[i] = .35 + rnd() * .75; FLEX[i] = .6 + rnd() * .8; PH[i] = rnd() * 6.283;
ST[i] = rnd() < .3 ? .04 + rnd() * .62 : -1; SDIST[i] = 2.2 + rnd() * 3.2; WAVE[i] = rnd();
GROW[i] = RED ? 1 : 0; GDEL[i] = RED ? 0 : 1e9; PRES[i] = 1;
for (var k = 0; k < K; k++){ FTH[i*K+k] = k / K * 6.2832 + (rnd() - .5) * .38; FLN[i*K+k] = .8 + rnd() * .34; }
}
/* flyers (seeds that left the head) */
var FP = new Float32Array(F*3), FV = new Float32Array(F*3), FU = new Float32Array(F*3), FE = new Float32Array(F*3);
var FPHI = new Float32Array(F), FSP = new Float32Array(F), FLS = new Float32Array(F), FPL = new Float32Array(F),
FOP = new Float32Array(F), FAGE = new Float32Array(F), FSRC = new Uint32Array(F), FAL = new Uint8Array(F);
var fNext = 0;
/* ---- buffers: stalks (normal canvas) and parachutes (difference canvas) ---- */
var stPos = new Float32Array(U * 2 * 3), stCol = new Float32Array(U * 2 * 4), stHead = new Float32Array(U * 2);
var ppPos = new Float32Array(U * K * 2 * 3), ppCol = new Float32Array(U * K * 2 * 4), ppHead = new Float32Array(U * K * 2);
for (var u = 0; u < U; u++){
var o = u * 8;
stCol.set([.047, .051, .059, .92, .369, .388, .42, .42], o);
var am = .7 + rnd() * .3;
for (k = 0; k < K; k++){
var q = (u * K + k) * 8;
ppCol.set([1, 1, 1, .4 * am, 1, 1, 1, .035 * am], q);
ppHead[(u*K+k)*2] = ppHead[(u*K+k)*2+1] = u < N ? 1 : 0;
}
}
function lineGeo(pos, col, head){
var g = new THREE.BufferGeometry();
var pa = new THREE.BufferAttribute(pos, 3); pa.setUsage(THREE.DynamicDrawUsage);
g.setAttribute('position', pa);
g.setAttribute('aCol', new THREE.BufferAttribute(col, 4));
g.setAttribute('aHead', new THREE.BufferAttribute(head, 1));
return g;
}
var VS = [
'attribute vec4 aCol; attribute float aHead;',
'uniform float uCz; uniform float uCr;',
'varying vec4 vCol; varying float vHead;',
'void main(){',
' vec4 mv = modelViewMatrix * vec4(position, 1.0);',
' float k = clamp(0.5 + (uCz + mv.z) / (2.0 * uCr), 0.0, 1.0);',
' vCol = vec4(aCol.rgb, aCol.a * mix(0.3, 1.0, k));',
' vHead = aHead;',
' gl_Position = projectionMatrix * mv;',
'}'].join('\n');
var FS = [
'uniform vec3 uCore; uniform float uOp;',
'varying vec4 vCol; varying float vHead;',
'void main(){',
' float d = length(gl_FragCoord.xy - uCore.xy);',
' float m = mix(1.0, smoothstep(uCore.z * 0.8, uCore.z * 1.75, d), vHead);',
' gl_FragColor = vec4(vCol.rgb, vCol.a * m * uOp);',
'}'].join('\n');
function lineMat(){
return new THREE.ShaderMaterial({ vertexShader: VS, fragmentShader: FS, transparent: true, depthWrite: false,
uniforms: { uCz: { value: 8 }, uCr: { value: 1 }, uCore: { value: new THREE.Vector3(0, 0, 0) }, uOp: { value: 1 } } });
}
var stGeo = lineGeo(stPos, stCol, stHead), ppGeo = lineGeo(ppPos, ppCol, ppHead);
var stMat = lineMat(), ppMat = lineMat();
var stalks = new THREE.LineSegments(stGeo, stMat), paps = new THREE.LineSegments(ppGeo, ppMat);
stalks.frustumCulled = paps.frustumCulled = false;
stalks.renderOrder = 3;
/* ---- core: small pitted sphere, soft two-tone shading ---- */
var cg = new THREE.SphereGeometry(R0, MOB ? 64 : 96, MOB ? 48 : 72);
var cp = cg.attributes.position, v = new THREE.Vector3();
var sig2 = Math.pow(Math.sqrt(4 * Math.PI / N) * .34, 2);
for (var j = 0; j < cp.count; j++){
v.fromBufferAttribute(cp, j).normalize();
var best = -1;
for (i = 0; i < N; i++){ var dt = v.x*D3[i*3] + v.y*D3[i*3+1] + v.z*D3[i*3+2]; if (dt > best) best = dt; }
var pit = Math.exp(-2 * (1 - best) / sig2);
var lump = Math.sin(v.x * 7.1 + 1.3) * Math.sin(v.y * 8.3) * Math.sin(v.z * 6.7 + .4);
var rr = R0 * (1 + .05 * lump - .11 * pit);
cp.setXYZ(j, v.x * rr, v.y * rr, v.z * rr);
}
cg.computeVertexNormals();
var coreMat = new THREE.ShaderMaterial({
uniforms: { uA: { value: new THREE.Color(0x2EE06F) }, uB: { value: new THREE.Color(0x13A94C) } },
vertexShader: 'varying vec3 vN; varying vec3 vV; void main(){ vec4 mv = modelViewMatrix * vec4(position,1.0); vN = normalize(normalMatrix * normal); vV = normalize(-mv.xyz); gl_Position = projectionMatrix * mv; }',
fragmentShader: 'uniform vec3 uA; uniform vec3 uB; varying vec3 vN; varying vec3 vV; void main(){ vec3 n = normalize(vN); float l = clamp(dot(n, normalize(vec3(-.35,.8,.55))) * .5 + .5, 0.0, 1.0); float f = pow(1.0 - max(dot(n, vV), 0.0), 2.0); vec3 c = mix(uB, uA, smoothstep(.15, .95, l)); c = mix(c, uB, f * .55); gl_FragColor = vec4(c, 1.0); }'
});
var core = new THREE.Mesh(cg, coreMat);
var head = new THREE.Object3D(); head.add(core);
/* ---- stem: flat ribbon facing the camera ---- */
var SM = 48, stemPos = new Float32Array((SM + 1) * 2 * 3), idx = [];
for (i = 0; i < SM; i++){ var a = i * 2; idx.push(a, a + 1, a + 2, a + 1, a + 3, a + 2); }
var stemGeo = new THREE.BufferGeometry();
var sa = new THREE.BufferAttribute(stemPos, 3); sa.setUsage(THREE.DynamicDrawUsage);
stemGeo.setAttribute('position', sa); stemGeo.setIndex(idx);
var stem = new THREE.Mesh(stemGeo, new THREE.MeshBasicMaterial({ color: 0x0C0D0F, side: THREE.DoubleSide }));
stem.frustumCulled = false;
var sceneA = new THREE.Scene(), sceneB = new THREE.Scene();
sceneA.add(stem, head, stalks); sceneB.add(paps);
var cam = new THREE.PerspectiveCamera(30, 1, .05, 200);
var TAN = Math.tan(15 * Math.PI / 180);
var L = { W: 1, H: 1, dpr: 1, wpp: 1, Rpx: 200, D: 8, gy: -2, hy: 0, halfW: 3, halfH: 2 };
var t = 0, autoRot = .6, pxS = 0, pyS = 0, hp = new THREE.Vector3();
function layout(){
var W = hero.clientWidth, H = hero.clientHeight, dpr = Math.min(window.devicePixelRatio || 1, 1.75);
rA.setPixelRatio(dpr); rB.setPixelRatio(dpr); rA.setSize(W, H, false); rB.setSize(W, H, false);
var ruleY = rule.offsetTop, top = 64, area = Math.max(200, ruleY - top), Rpx, cy;
if (W < 900){ Rpx = Math.min(W * .4, area * .3); cy = top + area * .44; }
else { Rpx = Math.min(W * .19, area * .4); cy = top + area * .46; }
var wpp = 1 / Rpx;
cam.aspect = W / H; cam.position.set(0, 0, H * wpp / (2 * TAN)); cam.updateProjectionMatrix();
L.W = W; L.H = H; L.dpr = dpr; L.wpp = wpp; L.Rpx = Rpx; L.D = cam.position.z;
L.gy = (H / 2 - ruleY) * wpp; L.hy = (H / 2 - cy) * wpp; L.halfW = W / 2 * wpp; L.halfH = H / 2 * wpp;
}
function far(uIdx){
stPos.fill(0, uIdx * 6, uIdx * 6 + 6); stPos[uIdx * 6 + 2] = stPos[uIdx * 6 + 5] = -1e5;
var b = uIdx * K * 6;
for (var n = 0; n < K * 2; n++){ ppPos[b + n*3] = 0; ppPos[b + n*3 + 1] = 0; ppPos[b + n*3 + 2] = -1e5; }
}
for (u = 0; u < U; u++) far(u);
function writeUnit(uIdx, Cx, Cy, Cz, ux, uy, uz, ex, ey, ez, ls, pl, op, phi, src){
var o = uIdx * 6;
stPos[o] = Cx - ux * ls; stPos[o+1] = Cy - uy * ls; stPos[o+2] = Cz - uz * ls;
stPos[o+3] = Cx; stPos[o+4] = Cy; stPos[o+5] = Cz;
var fx = uy * ez - uz * ey, fy = uz * ex - ux * ez, fz = ux * ey - uy * ex; // e2 = u x e1
var ca = Math.cos(op), sa = Math.sin(op), b = uIdx * K * 6;
for (var k = 0; k < K; k++){
var th = FTH[src * K + k] + phi, c = Math.cos(th) * sa, s = Math.sin(th) * sa, len = pl * FLN[src * K + k];
var p = b + k * 6;
ppPos[p] = Cx; ppPos[p+1] = Cy; ppPos[p+2] = Cz;
ppPos[p+3] = Cx + (ux * ca + ex * c + fx * s) * len;
ppPos[p+4] = Cy + (uy * ca + ey * c + fy * s) * len;
ppPos[p+5] = Cz + (uz * ca + ez * c + fz * s) * len;
}
}
function spawn(Cx, Cy, Cz, ux, uy, uz, ex, ey, ez, ls, pl, op, phi, src, burst){
var f = fNext; fNext = (fNext + 1) % F;
FAL[f] = 1; FAGE[f] = 0; FSRC[f] = src; FLS[f] = ls; FPL[f] = pl; FOP[f] = op; FPHI[f] = phi;
FSP[f] = (rnd() - .5) * 3.4;
FP[f*3] = Cx; FP[f*3+1] = Cy; FP[f*3+2] = Cz;
FU[f*3] = ux; FU[f*3+1] = uy; FU[f*3+2] = uz;
FE[f*3] = ex; FE[f*3+1] = ey; FE[f*3+2] = ez;
var kk = .35 + .55 * rnd() + burst, out = .5 + burst * .4;
FV[f*3] = ux * out + WX * kk + (rnd() - .5) * .45;
FV[f*3+1] = uy * out + WY * kk + (rnd() - .5) * .45;
FV[f*3+2] = uz * out + WZ * kk + (rnd() - .5) * .3;
}
function regrow(all, base){
for (var i = 0; i < N; i++){
if (!all && PRES[i]) continue;
PRES[i] = 1; GROW[i] = 0;
GDEL[i] = base + (D3[i*3+1] + 1) * .5 * .3 + rnd() * .6;
}
}
function step(dt){
t += dt;
var lk = 1 - Math.exp(-dt * 3);
pxS += (ptrX - pxS) * lk; pyS += (ptrY - pyS) * lk;
/* hold / wind state */
if (holding && !scattered && introDone){
hold = Math.min(1, hold + dt / 2.3);
if (hold >= 1) fullScatter();
} else if (!scattered) hold = Math.max(0, hold - dt / .8);
if (scattered) scatterClock += dt;
var wT = scattered ? (scatterClock < 1.3 ? 1.3 : .3) : hold * hold * (3 - 2 * hold);
windAmt += (wT - windAmt) * (1 - Math.exp(-dt * (scattered ? 3.5 : 6)));
var missing = 0;
for (var i = 0; i < N; i++) if (!PRES[i]) missing++;
if (!holding && !scattered && hold === 0 && introDone){
idle += dt; if (idle > 2.2 && missing) { regrow(false, 0); idle = 0; }
} else idle = 0;
/* head transform: stem pivots at the soil line, head bends toward the pointer and with the wind */
var sp = prox.p, breath = Math.sin(t * 1.25);
var sw = pxS * .1 + Math.sin(t * .55) * .022 + windAmt * .12;
var len = (L.hy - L.gy + sp * L.H * L.wpp * .2) * I.stem;
var hx = Math.sin(sw) * len, hy = L.gy + Math.cos(sw) * len;
var s = Math.max(1e-4, I.head * (1 - .2 * sp) * (1 + .012 * breath));
autoRot += dt * (.085 + windAmt * .3);
head.position.set(hx, hy, 0); head.scale.setScalar(s);
head.rotation.set(.14 + pyS * .3, autoRot + pxS * .45, -sw * .9);
head.updateMatrixWorld(true);
var e = head.matrixWorld.elements, is = 1 / s;
var m0 = e[0]*is, m1 = e[1]*is, m2 = e[2]*is, m4 = e[4]*is, m5 = e[5]*is, m6 = e[6]*is, m8 = e[8]*is, m9 = e[9]*is, m10 = e[10]*is;
/* wind in head space */
var wlx = m0*WX + m1*WY + m2*WZ, wly = m4*WX + m5*WY + m6*WZ, wlz = m8*WX + m9*WY + m10*WZ;
var bendK = windAmt * .42, trem = windAmt * .075 + .012;
for (i = 0; i < N; i++){
if (!PRES[i]){ far(i); continue; }
if (GROW[i] < 1){
if (GDEL[i] > 0) GDEL[i] -= dt; else GROW[i] = Math.min(1, GROW[i] + dt / .95);
}
var g = GROW[i];
if (g <= 0){ far(i); continue; }
var gs = Math.min(1, g / .62), gp = Math.max(0, Math.min(1, (g - .38) / .62));
gs = 1 - Math.pow(1 - gs, 3); gp = 1 - Math.pow(1 - gp, 3);
var i3 = i * 3, dx = D3[i3], dy = D3[i3+1], dz = D3[i3+2], ph = PH[i], fl = FLEX[i];
/* local axis: rest direction + wind bend + tremble */
var lx = dx + wlx * bendK * fl + Math.sin(t * 17 + ph) * trem;
var ly = dy + wly * bendK * fl + Math.sin(t * 13 + ph * 1.7) * trem;
var lz = dz + wlz * bendK * fl + Math.sin(t * 11 + ph * 2.3) * trem;
var ll = 1 / Math.hypot(lx, ly, lz); lx *= ll; ly *= ll; lz *= ll;
/* to world */
var ar = R0 * .95;
var Ax = e[12] + s * (m0*dx + m4*dy + m8*dz) * ar, Ay = e[13] + s * (m1*dx + m5*dy + m9*dz) * ar, Az = e[14] + s * (m2*dx + m6*dy + m10*dz) * ar;
var ux = m0*lx + m4*ly + m8*lz, uy = m1*lx + m5*ly + m9*lz, uz = m2*lx + m6*ly + m10*lz;
var ex0 = E1[i3], ey0 = E1[i3+1], ez0 = E1[i3+2];
var ex = m0*ex0 + m4*ey0 + m8*ez0, ey = m1*ex0 + m5*ey0 + m9*ez0, ez = m2*ex0 + m6*ey0 + m10*ez0;
var ls = LS[i] * s * gs * (1 + .012 * breath), pl = PL[i] * s * gp;
var op = OPEN[i] * (.2 + .8 * gp) + .04 * breath, phi = 0;
var Cx = Ax + ux * ls, Cy = Ay + uy * ls, Cz = Az + uz * ls;
/* scroll release: deterministic drift so it reverses cleanly */
if (ST[i] >= 0 && sp > ST[i] && g >= 1){
var qq = Math.min(1, (sp - ST[i]) / .3); qq = qq * qq * (3 - 2 * qq);
var dist = SDIST[i] * qq;
Cx += WX * dist + Math.sin(qq * 2.6 + ph) * .35 * qq;
Cy += WY * dist + Math.cos(qq * 2.1 + ph) * .3 * qq;
Cz += WZ * dist * .5;
ux += (WX - ux) * qq; uy += (WY - uy) * qq; uz += (WZ - uz) * qq;
phi = qq * 3.5;
}
var un = 1 / Math.hypot(ux, uy, uz); ux *= un; uy *= un; uz *= un;
var de = ex*ux + ey*uy + ez*uz; ex -= ux*de; ey -= uy*de; ez -= uz*de;
var en = 1 / (Math.hypot(ex, ey, ez) || 1); ex *= en; ey *= en; ez *= en;
/* detach? */
if (g >= .95 && introDone){
var face = ux*WX + uy*WY + uz*WZ, go = false, burst = 0;
if (scattered){ if (scatterClock > (1 - face) * .2 + WAVE[i] * .14){ go = true; burst = .9; } }
else if (hold > 0 && hold > TAU[i] - .28 * face) go = true;
if (go){
spawn(Cx, Cy, Cz, ux, uy, uz, ex, ey, ez, ls, pl, op, phi, i, burst);
PRES[i] = 0; GROW[i] = 0; far(i); continue;
}
}
writeUnit(i, Cx, Cy, Cz, ux, uy, uz, ex, ey, ez, ls, pl, op, phi, i);
}
/* flyers: air = wind + curl-ish swirl + buoyancy; drag pulls velocity toward the air */
var airK = .3 + 1.6 * windAmt, kd = 1 - Math.exp(-dt * 1.15), ko = 1 - Math.exp(-dt * 2.2);
var limX = L.halfW * 1.35 + 1.4, limY = L.halfH * 1.35 + 1.4;
for (var f = 0; f < F; f++){
if (!FAL[f]) continue;
var f3 = f * 3, px = FP[f3], py = FP[f3+1], pz = FP[f3+2];
FAGE[f] += dt;
var cx = Math.sin(py * 1.7 + t * .6) + Math.cos(pz * 1.3 - t * .4) * .6;
var cy = Math.sin(pz * 1.5 + t * .5) + Math.cos(px * 1.1 + t * .7) * .6;
var cz = Math.sin(px * 1.3 - t * .45) + Math.cos(py * 1.2 + t * .3) * .6;
var vx = FV[f3], vy = FV[f3+1], vz = FV[f3+2];
vx += (WX * airK + cx * .5 - vx) * kd;
vy += (WY * airK + cy * .5 + .22 - vy) * kd;
vz += (WZ * airK * .7 + cz * .3 - vz) * kd;
FV[f3] = vx; FV[f3+1] = vy; FV[f3+2] = vz;
px += vx * dt; py += vy * dt; pz += vz * dt;
FP[f3] = px; FP[f3+1] = py; FP[f3+2] = pz;
if (px > limX || px < -limX || py > limY || py < -limY || pz > L.D * .55 || FAGE[f] > 16){ FAL[f] = 0; far(N + f); continue; }
/* parachute turns to face its direction of travel */
var vv = Math.hypot(vx, vy, vz);
var fux = FU[f3], fuy = FU[f3+1], fuz = FU[f3+2];
if (vv > 1e-4){ fux += (vx / vv - fux) * ko; fuy += (vy / vv - fuy) * ko; fuz += (vz / vv - fuz) * ko; }
var fn = 1 / Math.hypot(fux, fuy, fuz); fux *= fn; fuy *= fn; fuz *= fn;
FU[f3] = fux; FU[f3+1] = fuy; FU[f3+2] = fuz;
var fex = FE[f3], fey = FE[f3+1], fez = FE[f3+2], fd = fex*fux + fey*fuy + fez*fuz;
fex -= fux*fd; fey -= fuy*fd; fez -= fuz*fd;
var fen = 1 / (Math.hypot(fex, fey, fez) || 1); fex *= fen; fey *= fen; fez *= fen;
FE[f3] = fex; FE[f3+1] = fey; FE[f3+2] = fez;
FPHI[f] += FSP[f] * dt;
writeUnit(N + f, px, py, pz, fux, fuy, fuz, fex, fey, fez, FLS[f], FPL[f], FOP[f], FPHI[f], FSRC[f]);
}
/* stem: quadratic curve from the soil point up into the core */
var gx = 0, gy = L.gy, cxs = 0, cys = gy + len * .58, tx = hx, ty = hy;
var w0 = 1.7 * L.wpp, w1 = 1.05 * L.wpp;
for (var n = 0; n <= SM; n++){
var tt = n / SM, it = 1 - tt;
var bx = it*it*gx + 2*it*tt*cxs + tt*tt*tx, by = it*it*gy + 2*it*tt*cys + tt*tt*ty;
var dvx = 2*it*(cxs - gx) + 2*tt*(tx - cxs), dvy = 2*it*(cys - gy) + 2*tt*(ty - cys);
var dl = Math.hypot(dvx, dvy) || 1, nx = -dvy / dl, ny = dvx / dl, hw = (w0 + (w1 - w0) * tt) * (len > 1e-3 ? 1 : 0);
var o6 = n * 6;
stemPos[o6] = bx + nx * hw; stemPos[o6+1] = by + ny * hw; stemPos[o6+2] = 0;
stemPos[o6+3] = bx - nx * hw; stemPos[o6+4] = by - ny * hw; stemPos[o6+5] = 0;
}
/* uniforms */
hp.set(hx, hy, 0).project(cam);
ppMat.uniforms.uCore.value.set((hp.x * .5 + .5) * L.W * L.dpr, (hp.y * .5 + .5) * L.H * L.dpr, R0 * s * L.Rpx * L.dpr);
stMat.uniforms.uCz.value = ppMat.uniforms.uCz.value = L.D;
stMat.uniforms.uCr.value = ppMat.uniforms.uCr.value = Math.max(.2, s);
}
function draw(){
stGeo.attributes.position.needsUpdate = true;
ppGeo.attributes.position.needsUpdate = true;
stemGeo.attributes.position.needsUpdate = true;
rA.render(sceneA, cam); rB.render(sceneB, cam);
}
return { layout: layout, step: step, draw: draw, regrow: regrow, N: N };
}
/* ================= hold to scatter ================= */
function setLabel(st){
if (st === labState) return; labState = st;
holdLab.textContent = { idle: 'Hold to scatter', hold: 'Keep holding', gone: 'Scattered', grow: 'Regrowing' }[st];
curT.textContent = { idle: 'Hold', hold: 'Hold', gone: 'Gone', grow: 'Wait' }[st];
}
function holdStart(){
if (!GL || RED || !introDone || scattered) return;
holding = true; hero.classList.add('is-holding'); cur.classList.add('is-down');
}
function holdEnd(){
holding = false; hero.classList.remove('is-holding'); cur.classList.remove('is-down');
}
function fullScatter(){
scattered = true; scatterClock = 0; hold = 1; holdEnd();
setLabel('gone');
swapWords(NEXT, .75);
clearTimeout(resetT);
resetT = setTimeout(function(){
sim.regrow(true, 0);
scattered = false; hold = 0; windAmt = Math.min(windAmt, .3);
setLabel('grow');
swapWords(WORDS, .8);
setTimeout(function(){ if (!scattered) setLabel('idle'); }, 1600);
}, 2500);
}
var lastPct = -1;
function drawUI(){
var hv = scattered ? 1 : hold;
holdSq.style.transform = 'scaleY(' + hv.toFixed(3) + ')';
holdBar.style.transform = 'scaleX(' + hv.toFixed(3) + ')';
curFill.style.transform = 'scaleY(' + hv.toFixed(3) + ')';
var pc = Math.min(99, Math.round(hv * 99));
if (pc !== lastPct){ lastPct = pc; holdPct.textContent = (pc < 10 ? '0' : '') + pc; }
if (!scattered && labState !== 'grow') setLabel(holding && hold > .02 ? 'hold' : 'idle');
}
zone.addEventListener('pointerdown', function(e){
if (e.button !== 0) return;
if (e.pointerType === 'mouse'){ e.preventDefault(); try { zone.setPointerCapture(e.pointerId); } catch (x){} }
holdStart();
});
holdBtn.addEventListener('pointerdown', function(e){ if (e.button !== 0) return; e.preventDefault(); holdStart(); });
['pointerup', 'pointercancel'].forEach(function(ev){ window.addEventListener(ev, holdEnd); });
window.addEventListener('blur', holdEnd);
zone.addEventListener('contextmenu', function(e){ e.preventDefault(); });
holdBtn.addEventListener('contextmenu', function(e){ e.preventDefault(); });
function heroActive(){
if (!visible || !introDone || menuOpen) return false;
var r = hero.getBoundingClientRect();
return r.bottom > window.innerHeight * .5 && r.top < window.innerHeight * .5;
}
window.addEventListener('keydown', function(e){
var isSpace = e.code === 'Space' || e.key === ' ';
var isEnterOnBtn = e.key === 'Enter' && e.target === holdBtn;
if (!isSpace && !isEnterOnBtn) return;
if (e.repeat){ if (holding) e.preventDefault(); return; }
var tg = e.target;
if (tg !== holdBtn && tg && tg.closest && tg.closest('a,button,input,textarea,select,summary,[contenteditable],[role="button"]')) return;
if (!heroActive()) return;
e.preventDefault(); holdStart();
});
window.addEventListener('keyup', function(e){
if ((e.code === 'Space' || e.key === ' ' || e.key === 'Enter') && holding){ e.preventDefault(); holdEnd(); }
});
window.addEventListener('pointermove', function(e){
ptrX = e.clientX / window.innerWidth * 2 - 1;
ptrY = e.clientY / window.innerHeight * 2 - 1;
}, { passive: true });
/* pointer tag */
var fine = matchMedia('(hover: hover) and (pointer: fine)').matches;
if (fine && !RED && GL){
var qx = gsap.quickTo(cur, 'x', { duration: .35, ease: 'power3.out' }), qy = gsap.quickTo(cur, 'y', { duration: .35, ease: 'power3.out' });
var inZone = false;
zone.addEventListener('pointerenter', function(){ inZone = true; });
zone.addEventListener('pointerleave', function(){ inZone = false; cur.classList.remove('is-on'); });
zone.addEventListener('pointermove', function(e){
var r = hero.getBoundingClientRect(), x = e.clientX - r.left, y = e.clientY - r.top;
if (!cur.classList.contains('is-on')){ gsap.set(cur, { x: x, y: y }); }
qx(x); qy(y);
cur.classList.toggle('is-on', inZone && introDone);
});
}
/* ================= loop, visibility, resize ================= */
if (GL){
new IntersectionObserver(function(en){ visible = en[0].isIntersecting; }, { threshold: 0 }).observe(hero);
var last = performance.now();
var tick = function(){
var now = performance.now(), dt = Math.min(.05, (now - last) / 1000); last = now;
if (!visible || document.hidden) return;
if (parseFloat(cvA.style.opacity === '' ? 1 : cvA.style.opacity) < .01) return;
sim.step(dt); sim.draw(); drawUI();
};
if (!RED) gsap.ticker.add(tick);
var rz = 0;
var relayout = function(){
fitWords(); sim.layout();
if (RED){ sim.step(0); sim.draw(); }
};
window.addEventListener('resize', function(){ cancelAnimationFrame(rz); rz = requestAnimationFrame(relayout); });
relayout();
} else {
fitWords();
window.addEventListener('resize', fitWords);
}
if (document.fonts && document.fonts.ready) document.fonts.ready.then(function(){ fitWords(); if (GL && RED){ sim.step(0); sim.draw(); } });
/* ================= scroll: pin, drift, words slide apart ================= */
if (!RED){
var mm = gsap.matchMedia();
var build = function(pin){
var tl = gsap.timeline({ scrollTrigger: pin
? { trigger: hero, start: 'top top', end: '+=80%', pin: true, scrub: .8, anticipatePin: 1, invalidateOnRefresh: true }
: { trigger: hero, start: 'top top', end: 'bottom top', scrub: .6, invalidateOnRefresh: true } });
tl.to(prox, { p: 1, ease: 'none', duration: 1 }, 0)
.to(lineA, { x: '-15vw', skewX: -7, ease: 'none', duration: 1 }, 0)
.to(lineB, { x: '15vw', skewX: 7, ease: 'none', duration: 1 }, 0)
.to([cvA, cvB], { opacity: 0, ease: 'power1.in', duration: .24 }, .76);
return function(){ prox.p = 0; };
};
mm.add('(min-width: 900px)', function(){ return build(true); });
mm.add('(max-width: 899px)', function(){ return build(false); });
}
/* ================= intro ================= */
var wordSpans = [];
function finishIntro(){ introDone = true; gsap.set(frame, { clearProps: 'clipPath' }); hero.classList.add('is-ready'); setLabel('idle'); }
if (RED){
if (load) load.remove();
finishIntro();
} else {
if ('scrollRestoration' in history) history.scrollRestoration = 'manual';
window.scrollTo(0, 0);
if (lenis) lenis.stop();
wordSpans = SP.split ? SP.split(inA).concat(SP.split(inB)) : [inA, inB];
var fadeEls = $$('.sh-meta, .sh-pitch, .sh-hold, .sh-cta', hero);
gsap.set(wordSpans, { yPercent: 115 });
gsap.set(hdrIn, { yPercent: -100 });
gsap.set(frame, { clipPath: 'inset(0% 0% 100% 0%)' });
gsap.set(rule, { scaleX: 0 });
gsap.set(fadeEls, { autoAlpha: 0, y: 14 });
setLabel('idle');
var num = $('.sh-load__n', load), bar = $('.sh-load__line i', load), cnt = { v: 0 };
var paint = function(){ num.textContent = String(Math.round(cnt.v * 100)).padStart(3, '0'); bar.style.transform = 'scaleX(' + cnt.v.toFixed(4) + ')'; };
var fontsOk = false, timeOk = false, fired = false;
var fontP = (document.fonts && document.fonts.ready) ? document.fonts.ready : Promise.resolve();
Promise.race([fontP, new Promise(function(r){ setTimeout(r, 3000); })]).then(function(){ fontsOk = true; maybeGo(); });
gsap.to(cnt, { v: .86, duration: 1.2, ease: 'power2.inOut', onUpdate: paint, onComplete: function(){ timeOk = true; maybeGo(); } });
var maybeGo = function(){
if (!fontsOk || !timeOk || fired) return; fired = true;
gsap.to(cnt, { v: 1, duration: .3, ease: 'power2.out', onUpdate: paint, onComplete: wipe });
};
var wipe = function(){
fitWords(); if (GL) sim.layout();
gsap.to(load, { clipPath: 'inset(0% 0% 100% 0%)', duration: 1.05, ease: 'expo.inOut',
onComplete: function(){ load.remove(); if (lenis) lenis.start(); ScrollTrigger.refresh(); } });
var tl = gsap.timeline({ delay: .32 });
tl.to(frame, { clipPath: 'inset(0% 0% 0% 0%)', duration: 1.4, ease: 'expo.inOut' }, 0)
.to(rule, { scaleX: 1, duration: 1.3, ease: 'expo.inOut' }, .05)
.to(wordSpans, { yPercent: 0, duration: 1.5, ease: 'expo.out', stagger: .075 }, .12)
.to(hdrIn, { yPercent: 0, duration: 1.1, ease: 'expo.out' }, .4)
.to(I, { stem: 1, duration: 1.3, ease: 'power3.out' }, .15)
.to(I, { head: 1, duration: 1.5, ease: 'back.out(1.9)' }, .55)
.add(function(){ if (GL) sim.regrow(true, 0); }, .6)
.to(fadeEls, { autoAlpha: 1, y: 0, duration: 1, ease: 'power3.out', stagger: .07 }, .75)
.add(finishIntro, 2.1);
};
}
})();
</script>
<main>
<section id="thesis" class="st" aria-labelledby="st-title">
<style>
.st{padding-block:clamp(72px,10vw,160px);background:var(--bg);color:var(--ink)}
.st-sheet{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:1px;background:var(--line);border:1px solid var(--line)}
.st-sheet>*{background:var(--bg);min-width:0;position:relative}
/* column ruler: makes the 12-col framework readable */
.st-rule{height:26px;display:flex;align-items:center;padding-inline:8px;font-family:var(--mono);font-size:10px;letter-spacing:.06em;color:var(--mute)}
.st-rule--on{color:var(--ink)}
.st-rule--on::after{content:"";position:absolute;right:8px;top:50%;width:5px;height:5px;margin-top:-2.5px;background:var(--spore)}
/* row 1: kicker + statement */
.st-meta{grid-column:span 3;padding:clamp(20px,2.2vw,32px);display:flex;flex-direction:column;justify-content:space-between;gap:28px}
.st-meta .kick{margin:0}
.st-log{margin:0;font-family:var(--mono);font-size:11.5px;line-height:1.7;letter-spacing:.03em;color:var(--mute);text-transform:uppercase}
.st-log b{font-weight:500;color:var(--ink)}
.st-copy{grid-column:span 9;padding:clamp(28px,4vw,72px) clamp(20px,4vw,72px) clamp(36px,4.5vw,80px)}
.st-say{margin:0;font-family:var(--body);font-weight:400;font-size:clamp(28px,3.6vw,56px);line-height:1.1;letter-spacing:-.022em;max-width:20.5em}
.st-say .w{padding-bottom:.12em;margin-bottom:-.12em}
.st-hl{background-image:linear-gradient(var(--spore),var(--spore));background-repeat:no-repeat;background-position:0 55%;background-size:100% 88%;color:var(--ink)}
.st-sign{margin:clamp(24px,3vw,44px) 0 0;display:flex;flex-wrap:wrap;gap:8px 22px;font-family:var(--mono);font-size:12px;letter-spacing:.04em;color:var(--mute);text-transform:uppercase}
.st-sign span::before{content:"/ ";color:var(--line)}
/* row 2: sectors */
.st-sec{grid-column:span 3;padding:clamp(18px,1.8vw,28px);min-height:clamp(260px,24vw,340px);display:flex;flex-direction:column;isolation:isolate;overflow:hidden}
.st-sec::before{content:"";position:absolute;inset:0;background:var(--fog);transform:scaleY(0);transform-origin:50% 100%;transition:transform .55s var(--ease);z-index:-1}
.st-sec:hover::before{transform:none}
.st-top{display:flex;justify-content:space-between;align-items:center;font-family:var(--mono);font-size:12px;letter-spacing:.06em;color:var(--mute)}
.st-chk{width:18px;height:18px;background:var(--spore);display:grid;place-items:center;opacity:0;transform:scale(.4) rotate(-45deg);transition:opacity .3s var(--ease),transform .45s var(--ease)}
.st-chk svg{width:12px;height:12px}
.st-sec:hover .st-chk{opacity:1;transform:none}
.st-ico{width:clamp(56px,4.6vw,68px);height:auto;margin:clamp(22px,3vw,44px) 0 auto;overflow:visible}
.st-ico [pathLength]{fill:none;stroke:var(--ink);stroke-width:1.5;stroke-linecap:square;stroke-linejoin:miter;stroke-dasharray:1;stroke-dashoffset:0;vector-effect:non-scaling-stroke}
.st-ico .st-acc{stroke:var(--spore-deep)}
.st-ico .st-dot{fill:var(--spore);stroke:none}
.st-name{margin:28px 0 0;font-size:clamp(34px,2.9vw,42px)}
.st-desc{margin:12px 0 0;font-family:var(--mono);font-size:12.5px;line-height:1.55;color:var(--mute);max-width:30ch}
/* row 3: stats */
.st-stat{grid-column:span 3;padding:clamp(18px,1.8vw,28px) clamp(18px,1.8vw,28px) clamp(22px,2.2vw,32px);display:flex;flex-direction:column;gap:14px}
.st-stat-id{font-family:var(--mono);font-size:11px;letter-spacing:.08em;color:var(--mute);text-transform:uppercase;display:flex;align-items:center;gap:8px}
.st-stat-id::before{content:"";width:6px;height:6px;background:var(--spore)}
.st-num{display:block;font-size:clamp(64px,9vw,150px);white-space:nowrap;font-variant-numeric:tabular-nums;margin-top:clamp(8px,2vw,28px)}
.st-lab{margin:0;font-family:var(--mono);font-size:12.5px;line-height:1.5;letter-spacing:.02em;color:var(--mute);max-width:24ch}
/* row 4: photo + note */
.st-photo{grid-column:span 8;padding:clamp(10px,1vw,14px)}
.st-frame{margin:0;position:relative;overflow:hidden;aspect-ratio:1600/1066;background:var(--fog)}
.st-frame img{width:100%;height:100%;object-fit:cover;will-change:transform}
.st-chip{position:absolute;left:clamp(10px,1.4vw,20px);bottom:clamp(10px,1.4vw,20px);background:var(--ink);color:#fff;font-family:var(--mono);font-size:11px;letter-spacing:.06em;text-transform:uppercase;padding:7px 10px 7px 9px;display:flex;align-items:center;gap:8px}
.st-chip::before{content:"";width:6px;height:6px;background:var(--spore);animation:st-blink 1.6s steps(2,jump-none) infinite}
@keyframes st-blink{50%{opacity:.15}}
.st-note{grid-column:span 4;padding:clamp(20px,2.2vw,32px);display:flex;flex-direction:column;gap:24px}
.st-note-k{font-family:var(--mono);font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:var(--mute)}
.st-note p{margin:0;font-size:clamp(17px,1.35vw,20px);line-height:1.45;letter-spacing:-.005em}
.st-spec{margin:auto 0 0;display:grid;border-top:1px solid var(--line)}
.st-spec div{display:grid;grid-template-columns:9ch 1fr;gap:12px;padding:10px 0;border-bottom:1px solid var(--line);font-family:var(--mono);font-size:12px;line-height:1.45}
.st-spec dt{color:var(--mute);text-transform:uppercase;letter-spacing:.06em}
.st-spec dd{margin:0}
@media (max-width:1100px){
.st-meta{grid-column:span 12;flex-direction:row;align-items:flex-start}
.st-copy{grid-column:span 12}
.st-sec{grid-column:span 6}
.st-stat{grid-column:span 6}
.st-photo{grid-column:span 12}
.st-note{grid-column:span 12}
.st-spec{margin-top:0}
}
@media (max-width:600px){
.st-rule{padding-inline:0;justify-content:center;font-size:9px}
.st-rule--on::after{display:none}
.st-meta{flex-direction:column;gap:18px}
.st-copy{padding:26px 18px 32px}
.st-sec{grid-column:span 12;min-height:0}
.st-ico{margin:22px 0 0}
.st-name{margin-top:22px}
.st-stat{padding:16px 14px 20px}
.st-lab{font-size:11.5px}
.st-photo{padding:8px}
.st-note{padding:22px 18px}
}
@media (prefers-reduced-motion: reduce) and (update: none){
.st-sec::before,.st-chk{transition:none}
.st-chip::before{animation:none}
}
</style>
<div class="wrap">
<div class="st-sheet">
<span class="st-rule st-rule--on" aria-hidden="true">01</span><span class="st-rule" aria-hidden="true">02</span><span class="st-rule" aria-hidden="true">03</span><span class="st-rule" aria-hidden="true">04</span><span class="st-rule" aria-hidden="true">05</span><span class="st-rule" aria-hidden="true">06</span><span class="st-rule" aria-hidden="true">07</span><span class="st-rule" aria-hidden="true">08</span><span class="st-rule" aria-hidden="true">09</span><span class="st-rule" aria-hidden="true">10</span><span class="st-rule" aria-hidden="true">11</span><span class="st-rule" aria-hidden="true">12</span>
<div class="st-meta">
<h2 class="kick" id="st-title">001 — Thesis</h2>
<p class="st-log"><b>Entry</b> 2019-03-04<br>Berlin-Wedding<br>Halle 3, bench 1</p>
</div>
<div class="st-copy">
<p class="st-say">Most funds <span class="st-hl">won't touch hardware.</span> It's slow, it's messy, and every good idea needs a workshop nobody wants to pay for. So <span class="st-hl">we built the workshop</span> — then put the money, the machinists and an unreasonable amount of <span class="st-hl">patience</span> under one roof.</p>
<p class="st-sign"><span>Seeds</span><span>Soil</span><span>Root</span><span>Sprout</span><span>Bloom</span></p>
</div>
<article class="st-sec">
<div class="st-top"><span>01</span><span class="st-chk" aria-hidden="true"><svg viewBox="0 0 12 12" fill="none" stroke="#0C0D0F" stroke-width="1.6"><path d="M2 6.2 4.8 9 10 3"/></svg></span></div>
<svg class="st-ico" viewBox="0 0 48 48" aria-hidden="true">
<path pathLength="1" d="M5 43h18"/>
<path pathLength="1" d="M14 43V31"/>
<circle pathLength="1" cx="14" cy="28.5" r="2.5"/>
<path pathLength="1" d="M15.8 26.7 27.7 14.8"/>
<circle pathLength="1" cx="29.5" cy="13" r="2.5"/>
<path pathLength="1" class="st-acc" d="M31.5 11.2 38 7l4 4-5 2"/>
<path pathLength="1" class="st-acc" d="M31.3 15.2 35 22l5-1.5-2.5-4.5"/>
</svg>
<h3 class="st-name disp">Robotics</h3>
<p class="st-desc">Arms, grippers, and machines that learn a task by watching once.</p>
</article>
<article class="st-sec">
<div class="st-top"><span>02</span><span class="st-chk" aria-hidden="true"><svg viewBox="0 0 12 12" fill="none" stroke="#0C0D0F" stroke-width="1.6"><path d="M2 6.2 4.8 9 10 3"/></svg></span></div>
<svg class="st-ico" viewBox="0 0 48 48" aria-hidden="true">
<path pathLength="1" d="M7 41C6 22 18 9 42 6c1 22-11 35-35 35Z"/>
<path pathLength="1" d="M7 41 24 24"/>
<path pathLength="1" class="st-acc" d="M30 13l-6 10h7l-5 10"/>
</svg>
<h3 class="st-name disp">Climate hardware</h3>
<p class="st-desc">Sensors, storage and field kit that has to survive actual weather.</p>
</article>
<article class="st-sec">
<div class="st-top"><span>03</span><span class="st-chk" aria-hidden="true"><svg viewBox="0 0 12 12" fill="none" stroke="#0C0D0F" stroke-width="1.6"><path d="M2 6.2 4.8 9 10 3"/></svg></span></div>
<svg class="st-ico" viewBox="0 0 48 48" aria-hidden="true">
<path pathLength="1" d="M5 9h38v30H5Z"/>
<path pathLength="1" class="st-acc" d="M5 26h9l3-7 5 14 5-19 4 12h12"/>
<path pathLength="1" d="M18 43h12"/>
</svg>
<h3 class="st-name disp">Health devices</h3>
<p class="st-desc">Wearables and rehab tools built to pass a clinic, not just a demo.</p>
</article>
<article class="st-sec">
<div class="st-top"><span>04</span><span class="st-chk" aria-hidden="true"><svg viewBox="0 0 12 12" fill="none" stroke="#0C0D0F" stroke-width="1.6"><path d="M2 6.2 4.8 9 10 3"/></svg></span></div>
<svg class="st-ico" viewBox="0 0 48 48" aria-hidden="true">
<path pathLength="1" d="M13 13h22v22H13Z"/>
<path pathLength="1" class="st-acc" d="M19 19h10v10H19Z"/>
<path pathLength="1" d="M18 13V6M24 13V6M30 13V6M18 42v-7M24 42v-7M30 42v-7M13 18H6M13 24H6M13 30H6M42 18h-7M42 24h-7M42 30h-7"/>
</svg>
<h3 class="st-name disp">Edge AI</h3>
<p class="st-desc">Models squeezed onto chips that run far from any data centre.</p>
</article>
<div class="st-stat">
<span class="st-stat-id">S.01 / portfolio</span>
<span class="st-num disp" data-to="41">41</span>
<p class="st-lab">Companies backed since 2019</p>
</div>
<div class="st-stat">
<span class="st-stat-id">S.02 / capital</span>
<span class="st-num disp" data-to="58" data-prefix="€" data-suffix="M">€58M</span>
<p class="st-lab">Raised by the portfolio after residency</p>
</div>
<div class="st-stat">
<span class="st-stat-id">S.03 / survival</span>
<span class="st-num disp" data-to="74" data-suffix="%">74%</span>
<p class="st-lab">Closed a follow-on round within 18 months</p>
</div>
<div class="st-stat">
<span class="st-stat-id">S.04 / output</span>
<span class="st-num disp" data-to="312">312</span>
<p class="st-lab">Prototypes built in our own shop</p>
</div>
<div class="st-photo">
<figure class="st-frame">
<img src="assets/lab.jpg" width="1600" height="1066" loading="lazy" alt="A technician solders components onto a circuit board at a workbench">
<figcaption class="st-chip">Halle 3 · machine shop, 06:40</figcaption>
</figure>
</div>
<aside class="st-note">
<span class="st-note-k">Site note — Halle 3</span>
<p>2,400 m² of former tram depot. A wet lab, a machine shop, an RF room, and an espresso machine that has been pulling shots longer than most of our founders have been alive.</p>
<dl class="st-spec">
<div><dt>Floor</dt><dd>2,400 m²</dd></div>
<div><dt>Rooms</dt><dd>Wet lab · Machine shop · RF room</dd></div>
<div><dt>Where</dt><dd>Berlin-Wedding, since 2019</dd></div>
</dl>
</aside>
</div>
</div>
<script>
(function(){
var root=document.getElementById('thesis');
if(!root||!window.SP)return;
root.querySelectorAll('.st-num').forEach(function(el){SP.count(el)});
if(SP.reduced)return;
var css=getComputedStyle(document.documentElement);
var LINE=css.getPropertyValue('--line').trim()||'#D6D9DE';
var INK=css.getPropertyValue('--ink').trim()||'#0C0D0F';
/* statement: words scrub grey -> ink, highlight bars wipe in as each phrase is reached */
var say=root.querySelector('.st-say');
var words=SP.split(say);
var hls=[].slice.call(say.querySelectorAll('.st-hl'));
var STEP=.3;
gsap.set(words,{color:LINE});
gsap.set(hls,{backgroundSize:'0% 88%'});
var tl=gsap.timeline({scrollTrigger:{trigger:say,start:'top 82%',end:'bottom 42%',scrub:.6}});
tl.to(words,{color:INK,duration:1,ease:'none',stagger:STEP},0);
hls.forEach(function(h){
var inner=[].slice.call(h.querySelectorAll('.w>span'));
var i=words.indexOf(inner[0]);
tl.to(h,{backgroundSize:'100% 88%',duration:inner.length*STEP+.5,ease:'power1.inOut'},i*STEP+.5);
});
/* sectors: line-art draws in on enter, redraws on hover */
var secs=[].slice.call(root.querySelectorAll('.st-sec'));
var draw=function(paths,delay){
return gsap.fromTo(paths,{strokeDashoffset:1},{strokeDashoffset:0,duration:1.1,ease:'power2.inOut',stagger:.09,delay:delay||0,overwrite:true});
};
secs.forEach(function(s){gsap.set(s.querySelectorAll('.st-ico [pathLength]'),{strokeDashoffset:1})});
gsap.set(secs.map(function(s){return s.querySelectorAll('.st-top,.st-name,.st-desc')}),{autoAlpha:0,y:18});
ScrollTrigger.batch(secs,{start:'top 86%',once:true,onEnter:function(b){
b.forEach(function(s,k){
draw(s.querySelectorAll('.st-ico [pathLength]'),k*.12);
gsap.to(s.querySelectorAll('.st-top,.st-name,.st-desc'),{autoAlpha:1,y:0,duration:.9,ease:'expo.out',stagger:.07,delay:k*.12+.15});
});
}});
if(!matchMedia('(hover:none)').matches){
secs.forEach(function(s){
s.addEventListener('pointerenter',function(){draw(s.querySelectorAll('.st-ico [pathLength]'),0)});
});
}
/* stats: labels lift in alongside the counters */
gsap.from(root.querySelectorAll('.st-stat-id,.st-lab'),{autoAlpha:0,y:12,duration:.9,ease:'expo.out',stagger:.05,
scrollTrigger:{trigger:root.querySelector('.st-stat'),start:'top 88%',once:true}});
/* photo: clip reveal + slow parallax inside the frame */
var frame=root.querySelector('.st-frame'),img=frame.querySelector('img'),chip=frame.querySelector('.st-chip');
gsap.set(img,{scale:1.15,transformOrigin:'50% 50%'});
gsap.fromTo(frame,{clipPath:'inset(100% 0% 0% 0%)'},{clipPath:'inset(0% 0% 0% 0%)',duration:1.5,ease:'expo.inOut',
scrollTrigger:{trigger:frame,start:'top 85%',once:true}});
gsap.from(chip,{autoAlpha:0,x:-14,duration:.8,ease:'expo.out',delay:1.1,scrollTrigger:{trigger:frame,start:'top 85%',once:true}});
gsap.fromTo(img,{yPercent:-6},{yPercent:6,ease:'none',scrollTrigger:{trigger:frame,start:'top bottom',end:'bottom top',scrub:true}});
gsap.from(root.querySelectorAll('.st-note>*'),{autoAlpha:0,y:16,duration:.9,ease:'expo.out',stagger:.08,
scrollTrigger:{trigger:root.querySelector('.st-note'),start:'top 86%',once:true}});
})();
</script>
</section>
<section id="programs" class="sp" aria-labelledby="sp-title">
<style>
.sp{padding-block:clamp(96px,12vw,176px);background:var(--bg);color:var(--ink);overflow-x:clip}
.sp *{min-width:0}
/* ---------- header row ---------- */
.sp-head{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,380px);gap:clamp(24px,4vw,64px);align-items:end;
padding-bottom:clamp(28px,3vw,44px);border-bottom:1px solid var(--line)}
.sp-head .kick{margin-bottom:clamp(18px,2vw,28px)}
.sp-h2{font-size:clamp(64px,10vw,180px)}
.sp-intro{margin:0;color:var(--mute);line-height:1.6;max-width:38ch}
.sp-intro b{color:var(--ink);font-weight:500}
/* ---------- program cards ---------- */
.sp-cards{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:clamp(16px,2vw,28px);margin-top:clamp(40px,5vw,80px)}
.sp-card{--notch:24px;height:100%;transition:transform .6s var(--ease)}
.sp-card__edge,.sp-card__body{clip-path:polygon(0 0,calc(100% - var(--notch)) 0,100% var(--notch),100% 100%,0 100%)}
.sp-card__edge{position:absolute;inset:0;background:var(--line);transition:background-color .4s var(--ease)}
.sp-card__body{position:relative;margin:1px;height:calc(100% - 2px);background:#fff;display:flex;flex-direction:column;
padding:clamp(24px,3vw,44px)}
/* edge layer peeks out 1px around the body, including along the notch diagonal */
.sp-card__bar{position:absolute;top:0;left:0;right:0;height:5px;background:var(--spore);transform:scaleX(0);transform-origin:0 50%;
transition:transform .7s var(--ease)}
.sp-card__top{display:flex;align-items:center;justify-content:space-between;gap:16px;padding-right:calc(var(--notch) + 4px)}
.sp-card__idx{display:flex;gap:14px;color:var(--mute)}
.sp-card__idx b{color:var(--ink);font-weight:600}
.sp-seed{width:44px;height:44px;flex:none;color:var(--ink);transition:transform .9s var(--ease)}
.sp-seed .sp-seed__dot{fill:var(--spore);stroke:none}
.sp-card__name{font-size:clamp(60px,8.4vw,136px);margin-top:clamp(28px,4vw,56px)}
.sp-card__sub{margin:clamp(14px,1.6vw,20px) 0 0;font-family:var(--mono);font-size:13px;letter-spacing:.02em;color:var(--mute);max-width:44ch}
.sp-facts{list-style:none;margin:clamp(28px,3vw,40px) 0 0;padding:0;border-top:1px solid var(--line)}
.sp-facts li{display:flex;align-items:baseline;gap:14px;padding:14px 0;border-bottom:1px solid var(--line);font-size:16px;line-height:1.4}
.sp-facts li::before{content:"";width:8px;height:8px;background:var(--spore);flex:none;transform:translateY(-1px)}
.sp-facts li>span{flex:1 1 auto}
.sp-facts em{flex:none;font-style:normal;font-family:var(--mono);font-size:12px;color:var(--mute);margin-left:auto;padding-left:12px;white-space:nowrap}
.sp-card__foot{margin-top:auto;padding-top:clamp(28px,3vw,40px);display:flex;flex-wrap:wrap;align-items:center;gap:14px 22px}
.sp-card__meta{color:var(--mute);font-size:12px}
@media (hover:hover){
.sp-card:hover{transform:translateY(-6px)}
.sp-card:hover .sp-card__edge{background:var(--ink)}
.sp-card:hover .sp-card__bar{transform:scaleX(1)}
.sp-card:hover .sp-seed{transform:rotate(200deg)}
}
.sp-card:focus-within .sp-card__edge{background:var(--ink)}
.sp-card:focus-within .sp-card__bar{transform:scaleX(1)}
/* ---------- what grows here ---------- */
.sp-grow-head{display:flex;flex-wrap:wrap;justify-content:space-between;align-items:end;gap:20px 40px;
margin-top:clamp(96px,11vw,176px);padding-bottom:clamp(24px,2.6vw,36px);border-bottom:1px solid var(--ink)}
.sp-grow-head .kick{margin-bottom:18px}
.sp-h3{font-size:clamp(48px,6.4vw,112px)}
.sp-grow-note{margin:0;color:var(--mute);max-width:40ch}
.sp-grow{display:grid;grid-template-columns:minmax(0,5fr) minmax(0,7fr);gap:clamp(32px,5vw,96px);margin-top:clamp(28px,3vw,40px)}
.sp-stick{position:sticky;top:calc(var(--hdr) + 24px);height:calc(100vh - var(--hdr) - 48px);min-height:520px;max-height:900px;
display:grid;grid-template-columns:4px minmax(0,1fr);grid-template-rows:minmax(0,1fr) auto;gap:18px 16px}
.sp-prog{grid-row:1;display:flex;flex-direction:column;gap:3px}
.sp-prog i{flex:1;background:var(--line);position:relative;overflow:hidden}
.sp-prog i::after{content:"";position:absolute;inset:0;background:var(--spore);transform:scaleY(0);transform-origin:50% 0;
transition:transform .45s var(--ease)}
.sp-prog i.is-lit::after{transform:none}
.sp-frame{grid-row:1;grid-column:2;border:1px solid var(--line);padding:10px;background:#fff}
.sp-frame__in{position:relative;width:100%;height:100%;overflow:hidden;background:var(--fog)}
.sp-frame__in img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;z-index:1}
.sp-frame__in img.is-top{z-index:3}
.sp-cap{position:absolute;left:12px;bottom:12px;z-index:9;background:var(--ink);color:#fff;padding:7px 10px;font-size:11px;
text-transform:uppercase;letter-spacing:.08em;max-width:calc(100% - 24px)}
.sp-count{grid-column:1 / -1;display:flex;align-items:end;justify-content:space-between;gap:16px;border-top:1px solid var(--line);padding-top:14px}
.sp-count__n{font-family:var(--mono);font-weight:500;font-size:clamp(44px,4.6vw,76px);line-height:.9;letter-spacing:-.04em;white-space:nowrap}
.sp-count__n span{color:var(--mute)}
.sp-count__lbl{color:var(--mute);text-align:right;max-width:24ch;font-size:12px;text-transform:uppercase;letter-spacing:.06em}
.sp-list{list-style:none;margin:0;padding:0}
.sp-list-wrap{padding-bottom:30vh}
.sp-row{border-bottom:1px solid var(--line)}
.sp-row:first-child{border-top:1px solid var(--line)}
.sp-row__btn{display:block;width:100%;text-align:left;padding:clamp(24px,2.6vw,34px) 0;color:var(--mute);transition:color .4s var(--ease)}
.sp-row__in{display:grid;grid-template-columns:72px minmax(0,1fr) auto;align-items:baseline;width:calc(100% - 12px);
transition:transform .5s var(--ease)}
.sp-row__num{display:inline-flex;align-items:center;gap:10px;font-family:var(--mono);font-size:13px}
.sp-row__num::before{content:"";width:8px;height:8px;background:var(--spore);transform:scale(0);transition:transform .4s var(--ease)}
.sp-row__t{font-family:var(--body);font-weight:500;font-size:clamp(21px,2vw,28px);line-height:1.2;letter-spacing:-.01em}
.sp-row__t small{font-family:var(--mono);font-size:12px;font-weight:400;letter-spacing:.04em;margin-left:8px;color:var(--mute)}
.sp-row__pm{font-family:var(--mono);font-size:16px;width:20px;text-align:center;transition:transform .4s var(--ease)}
.sp-row__d{display:grid;grid-template-rows:0fr;transition:grid-template-rows .55s var(--ease)}
.sp-row__d>div{overflow:hidden}
.sp-row__d p{margin:0;padding:0 12px clamp(24px,2.6vw,34px) 84px;max-width:calc(56ch + 84px);color:var(--ink);font-size:16px;line-height:1.55;
opacity:0;transition:opacity .4s var(--ease)}
.sp-row.is-on .sp-row__btn{color:var(--ink)}
.sp-row.is-on .sp-row__in{transform:translateX(12px)}
.sp-row.is-on .sp-row__num::before{transform:none}
.sp-row.is-on .sp-row__t small{color:var(--ink)}
.sp-row.is-on .sp-row__pm{transform:rotate(45deg)}
.sp-row.is-on .sp-row__d{grid-template-rows:1fr}
.sp-row.is-on .sp-row__d p{opacity:1;transition-delay:.12s}
.sp-row__btn:hover{color:var(--ink)}
.sp-end{margin:28px 0 0;color:var(--mute);font-size:12px}
/* reduced motion: final states, no transitions */
.sp-rm *,.sp-rm *::before,.sp-rm *::after{transition:none !important}
/* ---------- < 900: no pin, framed image on top, tap accordion ---------- */
@media (max-width:899px){
.sp-head{grid-template-columns:minmax(0,1fr)}
.sp-cards{grid-template-columns:minmax(0,1fr)}
.sp-card{--notch:18px}
.sp-facts li{font-size:15px}
.sp-grow{grid-template-columns:minmax(0,1fr)}
.sp-stick{position:relative;top:auto;height:auto;min-height:0;max-height:none;grid-template-columns:minmax(0,1fr);grid-template-rows:auto auto}
.sp-prog{display:none}
.sp-frame{grid-column:1;padding:8px}
.sp-frame__in{height:auto;aspect-ratio:4/3}
.sp-list-wrap{padding-bottom:0}
.sp-row__in{grid-template-columns:44px minmax(0,1fr) auto}
.sp-row__num::before{display:none}
.sp-row.is-on .sp-row__in{transform:translateX(6px)}
.sp-row__d p{padding-left:44px;max-width:none;font-size:15px}
.sp-row__t small{display:block;margin:4px 0 0}
}
</style>
<div class="wrap">
<header class="sp-head">
<div>
<p class="kick">002 — Programs</p>
<h2 class="disp sp-h2" id="sp-title">Two ways in</h2>
</div>
<p class="mono sp-intro">Pick by the state of your bench. <b>Germinate</b> if the idea still has wires hanging out of it. <b>Canopy</b> if it already works and now has to survive contact with a factory.</p>
</header>
<div class="sp-cards">
<div data-sp-reveal>
<article class="sp-card ticks" aria-labelledby="sp-germ">
<div class="sp-card__edge" aria-hidden="true"></div>
<div class="sp-card__body">
<span class="sp-card__bar" aria-hidden="true"></span>
<div class="sp-card__top">
<p class="mono sp-card__idx" style="margin:0"><b>01</b><span>Residency</span></p>
<svg class="sp-seed" viewBox="0 0 48 48" fill="none" stroke="currentColor" stroke-width="1.4" aria-hidden="true">
<path d="M24 6c7 6 11 13 11 21a11 11 0 0 1-22 0c0-8 4-15 11-21Z"/>
<path d="M24 12v26M24 22l-5-4M24 29l6-5"/>
<circle class="sp-seed__dot" cx="24" cy="42" r="2.2"/>
</svg>
</div>
<h3 class="disp sp-card__name" id="sp-germ">Germinate</h3>
<p class="sp-card__sub">Sixteen weeks inside Halle 3 with the tools, the money and the people who have shipped hardware before.</p>
<ul class="sp-facts">
<li><span>16-week residency</span> <em>Wk 01–16</em></li>
<li><span>2 cohorts a year</span> <em>Spring · Autumn</em></li>
<li><span>8 teams per cohort</span> <em>16 / yr</em></li>
<li><span>Desk, shop access, €150k on day one</span> <em>Day 01</em></li>
</ul>
<div class="sp-card__foot">
<a class="btn" href="#apply">Apply to Germinate <svg class="arr" viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M2 12 12 2M4 2h8v8"/></svg></a>
<span class="mono sp-card__meta">Autumn closes 30 Nov 2026</span>
</div>
</div>
</article>
</div>
<div data-sp-reveal>
<article class="sp-card ticks" aria-labelledby="sp-canopy">
<div class="sp-card__edge" aria-hidden="true"></div>
<div class="sp-card__body">
<span class="sp-card__bar" aria-hidden="true"></span>
<div class="sp-card__top">
<p class="mono sp-card__idx" style="margin:0"><b>02</b><span>Investment</span></p>
<svg class="sp-seed" viewBox="0 0 48 48" fill="none" stroke="currentColor" stroke-width="1.4" aria-hidden="true">
<path d="M24 44V20"/>
<path d="M24 30c-6 0-10-4-10-10 6 0 10 4 10 10ZM24 24c0-7 4-12 11-12 0 7-4 12-11 12Z"/>
<path d="M16 44h16"/>
<circle class="sp-seed__dot" cx="24" cy="8" r="2.2"/>
</svg>
</div>
<h3 class="disp sp-card__name" id="sp-canopy">Canopy</h3>
<p class="sp-card__sub">Seed money for teams whose prototype already does the thing, and who now need to make a thousand of it.</p>
<ul class="sp-facts">
<li><span>Seed investment</span> <em>Equity</em></li>
<li><span>First cheques €150k–€600k</span> <em>Lead or co</em></li>
<li><span>Follow-on reserved</span> <em>Pro rata</em></li>
<li><span>For teams already past a working prototype</span> <em>TRL 5+</em></li>
</ul>
<div class="sp-card__foot">
<a class="btn btn--line" href="#apply">Pitch Canopy <svg class="arr" viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M2 12 12 2M4 2h8v8"/></svg></a>
<span class="mono sp-card__meta">Rolling intake, no cohort</span>
</div>
</div>
</article>
</div>
</div>
<div class="sp-grow-head">
<div>
<p class="kick">Inventory — 10 items</p>
<h3 class="disp sp-h3">What grows here</h3>
</div>
<p class="mono sp-grow-note">What founders actually get, itemised. No "ecosystem", no "network effects" — just the list.</p>
</div>
<div class="sp-grow">
<aside class="sp-aside" aria-label="Illustration for the active item">
<div class="sp-stick">
<div class="sp-prog" aria-hidden="true"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></div>
<figure class="sp-frame ticks" style="margin:0">
<div class="sp-frame__in">
<img class="is-top" src="assets/proto.jpg" width="1600" height="1067" loading="lazy" alt="A row of 3D printers building prototype parts in the workshop">
<img src="assets/studio.jpg" width="2000" height="1324" loading="lazy" alt="Bright loft studio with long desks and tall windows">
<img src="assets/team.jpg" width="1600" height="1066" loading="lazy" alt="A founding team planning at a whiteboard">
<figcaption class="mono sp-cap">Fig. 01 — Print bay, Halle 3</figcaption>
</div>
</figure>
<div class="sp-count">
<p class="sp-count__n" style="margin:0" aria-live="polite"><b class="sp-count__cur" style="font-weight:inherit">01</b><span>/10</span></p>
<p class="mono sp-count__lbl" style="margin:0">Machine shop & wet lab</p>
</div>
</div>
</aside>
<div class="sp-list-wrap">
<ol class="sp-list">
<li class="sp-row" data-img="0"><button class="sp-row__btn" type="button" aria-expanded="false" aria-controls="sp-d1"><span class="sp-row__in"><span class="sp-row__num">01</span><span class="sp-row__t">Machine shop & wet lab access</span><span class="sp-row__pm" aria-hidden="true">+</span></span></button>
<div class="sp-row__d" id="sp-d1"><div><p>Five-axis mill, laser cutter, pick-and-place and a fume-hooded bench, unlocked 7am to midnight.</p></div></div></li>
<li class="sp-row" data-img="0"><button class="sp-row__btn" type="button" aria-expanded="false" aria-controls="sp-d2"><span class="sp-row__in"><span class="sp-row__num">02</span><span class="sp-row__t">Prototype budget</span><span class="sp-row__pm" aria-hidden="true">+</span></span></button>
<div class="sp-row__d" id="sp-d2"><div><p>A parts budget kept apart from the cheque, so salaries never lose a fight with stepper motors.</p></div></div></li>
<li class="sp-row" data-img="0"><button class="sp-row__btn" type="button" aria-expanded="false" aria-controls="sp-d3"><span class="sp-row__in"><span class="sp-row__num">03</span><span class="sp-row__t">Supply-chain intros <small>EU contract manufacturers</small></span><span class="sp-row__pm" aria-hidden="true">+</span></span></button>
<div class="sp-row__d" id="sp-d3"><div><p>Warm handshakes with European factories that quote batches of fifty and actually answer email.</p></div></div></li>
<li class="sp-row" data-img="1"><button class="sp-row__btn" type="button" aria-expanded="false" aria-controls="sp-d4"><span class="sp-row__in"><span class="sp-row__num">04</span><span class="sp-row__t">Regulatory path <small>CE / MDR</small></span><span class="sp-row__pm" aria-hidden="true">+</span></span></button>
<div class="sp-row__d" id="sp-d4"><div><p>Your certification route drawn up in week two, before the design paints itself into a corner.</p></div></div></li>
<li class="sp-row" data-img="1"><button class="sp-row__btn" type="button" aria-expanded="false" aria-controls="sp-d5"><span class="sp-row__in"><span class="sp-row__num">05</span><span class="sp-row__t">Fundraise prep & investor room</span><span class="sp-row__pm" aria-hidden="true">+</span></span></button>
<div class="sp-row__d" id="sp-d5"><div><p>Deck teardowns, a data-room checklist and a closed room of investors who know what a BOM is.</p></div></div></li>
<li class="sp-row" data-img="1"><button class="sp-row__btn" type="button" aria-expanded="false" aria-controls="sp-d6"><span class="sp-row__in"><span class="sp-row__num">06</span><span class="sp-row__t">Hiring your first 5 engineers</span><span class="sp-row__pm" aria-hidden="true">+</span></span></button>
<div class="sp-row__d" id="sp-d6"><div><p>We source, screen and sit in on interviews until the first five desks have names on them.</p></div></div></li>
<li class="sp-row" data-img="1"><button class="sp-row__btn" type="button" aria-expanded="false" aria-controls="sp-d7"><span class="sp-row__in"><span class="sp-row__num">07</span><span class="sp-row__t">Pilot customers</span><span class="sp-row__pm" aria-hidden="true">+</span></span></button>
<div class="sp-row__d" id="sp-d7"><div><p>Farms, clinics and depots that have agreed to host a pilot before the enclosure is pretty.</p></div></div></li>
<li class="sp-row" data-img="2"><button class="sp-row__btn" type="button" aria-expanded="false" aria-controls="sp-d8"><span class="sp-row__in"><span class="sp-row__num">08</span><span class="sp-row__t">IP & patent strategy</span><span class="sp-row__pm" aria-hidden="true">+</span></span></button>
<div class="sp-row__d" id="sp-d8"><div><p>What to file, what to keep as a trade secret, and what is not worth the attorney hours.</p></div></div></li>
<li class="sp-row" data-img="2"><button class="sp-row__btn" type="button" aria-expanded="false" aria-controls="sp-d9"><span class="sp-row__in"><span class="sp-row__num">09</span><span class="sp-row__t">Grant writing <small>EU / national</small></span><span class="sp-row__pm" aria-hidden="true">+</span></span></button>
<div class="sp-row__d" id="sp-d9"><div><p>Applications drafted alongside you, because non-dilutive money spends exactly like the other kind.</p></div></div></li>
<li class="sp-row" data-img="2"><button class="sp-row__btn" type="button" aria-expanded="false" aria-controls="sp-d10"><span class="sp-row__in"><span class="sp-row__num">10</span><span class="sp-row__t">Demo Day in Halle 3</span><span class="sp-row__pm" aria-hidden="true">+</span></span></button>
<div class="sp-row__d" id="sp-d10"><div><p>Week sixteen ends on the shop floor: live hardware, a full room, and no slide-only demos allowed.</p></div></div></li>
</ol>
<p class="mono sp-end">— End of inventory. All ten come with Germinate; Canopy teams book them as needed.</p>
</div>
</div>
</div>
<script>
(function(){
const S=window.SP,root=document.getElementById('programs');
if(!root||!S)return;
const R=S.reduced;
if(R)root.classList.add('sp-rm');
S.rise(root.querySelector('.sp-h2'));
S.rise(root.querySelector('.sp-h3'));
const rows=[...root.querySelectorAll('.sp-row')];
const imgs=[...root.querySelectorAll('.sp-frame__in img')];
const segs=[...root.querySelectorAll('.sp-prog i')];
const cap=root.querySelector('.sp-cap');
const cur=root.querySelector('.sp-count__cur');
const lbl=root.querySelector('.sp-count__lbl');
const caps=['Fig. 01 — Print bay, Halle 3','Fig. 02 — Studio floor, north light','Fig. 03 — Tuesday whiteboard'];
const short=['Machine shop & wet lab','Prototype budget','Supply-chain intros','Regulatory path','Fundraise prep','First 5 engineers','Pilot customers','IP & patents','Grant writing','Demo Day'];
const pad=n=>String(n).padStart(2,'0');
let active=-1,shown=0,z=3,desk=false;
function showImg(n,dir){
if(n===shown)return;
const el=imgs[n];
imgs.forEach(i=>i.classList.remove('is-top'));
el.style.zIndex=++z;
shown=n;
if(R){cap.textContent=caps[n];return}
gsap.killTweensOf(el);
gsap.fromTo(el,{clipPath:dir>0?'inset(100% 0% 0% 0%)':'inset(0% 0% 100% 0%)',scale:1.14},
{clipPath:'inset(0% 0% 0% 0%)',duration:1,ease:'expo.inOut',overwrite:true});
gsap.to(el,{scale:1,duration:1.6,ease:'expo.out',delay:.05});
S.scramble(cap,{duration:.5,text:caps[n]});
}
function setActive(i){
if(i===active)return;
const dir=i>active?1:-1;
rows.forEach((r,k)=>{const on=k===i;r.classList.toggle('is-on',on);r.firstElementChild.setAttribute('aria-expanded',on)});
segs.forEach((s,k)=>s.classList.toggle('is-lit',k<=i));
active=i;
if(R){cur.textContent=pad(i+1);lbl.textContent=short[i]}
else{rollDigits(pad(i+1));S.scramble(lbl,{duration:.45,text:short[i]})}
showImg(+rows[i].dataset.img,dir);
}
/* counter: flick through random digits for a beat, then land on the real number */
let rollT=null;
function rollDigits(final){
clearInterval(rollT);let n=0;
rollT=setInterval(()=>{
if(++n>=6){clearInterval(rollT);cur.textContent=final;return}
cur.textContent=final.split('').map((c,k)=>k<Math.floor(n/3)?c:(Math.random()*10|0)).join('');
},45);
}
function closeAll(){
rows.forEach(r=>{r.classList.remove('is-on');r.firstElementChild.setAttribute('aria-expanded','false')});
active=-1;
}
/* desktop: the row crossing the viewport centre is active */
function pick(){
const cy=innerHeight/2;
let idx=-1;
for(let k=0;k<rows.length;k++){const b=rows[k].getBoundingClientRect();if(b.top<=cy&&b.bottom>cy){idx=k;break}}
if(idx<0){
const first=rows[0].getBoundingClientRect(),last=rows[rows.length-1].getBoundingClientRect();
idx=cy<first.top?0:cy>=last.bottom?rows.length-1:active;
}
if(idx>=0)setActive(idx);
}
rows.forEach((r,k)=>{
r.firstElementChild.addEventListener('click',()=>{
if(desk){
const b=r.getBoundingClientRect();
const y=b.top+scrollY-innerHeight/2+Math.min(b.height,120)/2;
if(S.lenis)S.lenis.scrollTo(y,{duration:1.1});else scrollTo({top:y,behavior:'auto'});
}else if(k===active){closeAll()}
else setActive(k);
});
});
setActive(0);
const mm=gsap.matchMedia();
mm.add('(min-width: 900px)',()=>{
desk=true;
const st=ScrollTrigger.create({trigger:root.querySelector('.sp-list'),start:'top bottom',end:'bottom top',
onUpdate:pick,onRefresh:pick});
pick();
return()=>{desk=false;st.kill()};
});
mm.add('(max-width: 899px)',()=>{
/* mobile: image sits on top and follows whichever row is open; open the first by default */
if(active<0)setActive(0);
});
})();
</script>
</section>
<section id="specimens" class="ss" aria-labelledby="ss-title">
<style>
.ss{padding:clamp(80px,11vw,160px) 0 0;background:var(--bg);color:var(--ink);overflow:hidden}
/* ---------- header ---------- */
.ss-head{display:grid;grid-template-columns:1fr;gap:28px;padding-bottom:clamp(32px,4vw,56px);border-bottom:1px solid var(--ink)}
.ss-title{font-size:clamp(76px,15vw,236px);margin-top:18px}
.ss-side{display:flex;flex-direction:column;gap:22px;align-items:flex-start}
.ss-intro{margin:0;max-width:40ch;color:var(--mute);font-size:16px}
.ss-intro strong{color:var(--ink);font-weight:600}
.ss-filter{display:flex;flex-wrap:wrap;gap:8px;margin:0;padding:0;list-style:none}
.ss-chip{display:inline-flex;align-items:center;gap:8px;height:38px;padding:0 14px;font-family:var(--mono);font-size:12px;font-weight:500;
letter-spacing:.07em;text-transform:uppercase;box-shadow:inset 0 0 0 1px var(--ink);background:var(--bg);color:var(--ink);
transition:background-color .3s var(--ease)}
.ss-chip sup{font-size:9px;letter-spacing:.04em;color:var(--mute);transform:translateY(-2px);transition:color .3s var(--ease)}
.ss-chip:hover{background:var(--spore-soft)}
.ss-chip[aria-pressed="true"]{background:var(--spore)}
.ss-chip[aria-pressed="true"] sup{color:var(--ink)}
.ss-meta{margin:0;display:flex;align-items:center;gap:14px;width:100%;font-family:var(--mono);font-size:12px;letter-spacing:.08em;text-transform:uppercase;color:var(--mute)}
.ss-meta::before{content:"";flex:1;height:1px;background:var(--line);order:2}
.ss-count{color:var(--ink);font-weight:600;min-width:13ch;order:1}
.ss-meta-r{order:3}
@media (min-width:900px){
.ss-head{grid-template-columns:1.25fr 1fr;align-items:end;gap:48px}
.ss-side{padding-bottom:14px}
}
/* ---------- grid ---------- */
.ss-grid{list-style:none;margin:0;padding:clamp(28px,4vw,56px) 0 0;display:grid;grid-template-columns:1fr;gap:56px 28px;position:relative}
@media (min-width:600px){.ss-grid{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media (min-width:1024px){.ss-grid{grid-template-columns:repeat(3,minmax(0,1fr));gap:72px clamp(24px,2.6vw,40px)}}
.ss-card{min-width:0}
.ss-card.is-out{display:none}
.ss-card-in{display:flex;flex-direction:column;height:100%}
/* phones: horizontal snap scroller instead of one very long column */
@media (max-width:599px){
.ss-grid{display:flex;gap:16px;overflow-x:auto;overscroll-behavior-x:contain;scroll-snap-type:x mandatory;
margin-inline:calc(var(--gut) * -1);padding-inline:var(--gut);scroll-padding-inline:var(--gut);scrollbar-width:none}
.ss-grid::-webkit-scrollbar{display:none}
.ss-card{flex:0 0 84%;scroll-snap-align:start}
.ss-card:last-child{margin-right:var(--gut)}
}
.ss-swipe{display:none}
@media (max-width:599px){.ss-swipe{display:flex;justify-content:space-between;margin-top:18px;font-family:var(--mono);font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:var(--mute)}}
/* specimen frame */
.ss-frame{border:1px solid var(--line);padding:8px;background:var(--bg)}
.ss-ph{position:relative;overflow:hidden;aspect-ratio:4/5;background:var(--fog)}
.ss-ph img{width:100%;height:100%;object-fit:cover;transition:transform 1.1s var(--ease);will-change:transform}
.ss-tag{position:absolute;left:8px;top:8px;z-index:2;display:inline-flex;align-items:center;gap:7px;padding:5px 8px;background:var(--bg);
font-family:var(--mono);font-size:10.5px;font-weight:500;letter-spacing:.08em;text-transform:uppercase}
.ss-tag i{width:6px;height:6px;border-radius:50%;background:var(--spore);box-shadow:0 0 0 1px var(--ink)}
/* the ink go-square */
.ss-go{position:absolute;right:0;bottom:0;z-index:3;width:52px;height:52px;display:grid;place-items:center;background:var(--ink);color:#fff;
transform:translate(101%,0);transition:transform .55s var(--ease)}
.ss-go svg{width:16px;height:16px;transition:transform .45s var(--ease)}
/* crosshair follower */
.ss-x{position:absolute;left:0;top:0;z-index:2;pointer-events:none;opacity:0;transition:opacity .25s var(--ease)}
.ss-x-plus{position:absolute;left:-13px;top:-13px;width:26px;height:26px}
.ss-x-plus::before,.ss-x-plus::after{content:"";position:absolute;background:var(--ink)}
.ss-x-plus::before{left:12.5px;top:0;width:1px;height:26px}
.ss-x-plus::after{top:12.5px;left:0;height:1px;width:26px}
.ss-x-read{position:absolute;left:14px;top:12px;white-space:nowrap;padding:4px 7px;background:var(--ink);color:#fff;
font-family:var(--mono);font-size:10px;letter-spacing:.06em}
.ss-x-read b{color:var(--spore);font-weight:500}
@media (hover:hover) and (pointer:fine){
.ss-ph{cursor:none}
.ss-card:hover .ss-ph img{transform:scale(1.06)}
.ss-card:hover .ss-go{transform:none}
.ss-card:hover .ss-x.is-live{opacity:1}
.ss-go:hover svg{transform:translate(2px,-2px)}
}
@media (hover:none){.ss-go{transform:none;width:44px;height:44px}}
/* label below the photo */
.ss-lab{padding:18px 2px 0;display:flex;flex-direction:column;flex:1}
.ss-no{margin:0;display:flex;justify-content:space-between;font-family:var(--mono);font-size:11px;letter-spacing:.1em;text-transform:uppercase;color:var(--mute)}
.ss-no span:first-child{color:var(--ink);font-weight:600}
.ss-name{font-size:clamp(38px,3.3vw,46px);margin-top:12px;white-space:nowrap;overflow:hidden;text-overflow:clip}
.ss-what{margin:10px 0 18px;color:var(--mute);font-size:15px;line-height:1.45;flex:1}
.ss-data{display:grid;grid-template-columns:1.2fr .9fr .8fr;margin:0;border-top:1px solid var(--ink);border-bottom:1px solid var(--line)}
.ss-data div{padding:10px 10px 11px;border-left:1px solid var(--line);min-width:0}
.ss-data div:first-child{border-left:0;padding-left:0}
.ss-data dt{font-family:var(--mono);font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:var(--mute)}
.ss-data dd{margin:4px 0 0;font-family:var(--mono);font-size:13.5px;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
/* ---------- lab-log marquee ---------- */
.ss-log{margin-top:clamp(72px,9vw,128px);border-top:1px solid var(--ink);border-bottom:1px solid var(--line);display:flex;align-items:stretch;background:var(--bg)}
.ss-log-lbl{flex:none;display:flex;align-items:center;gap:9px;padding:0 18px 0 var(--gut);font-family:var(--mono);font-size:11px;font-weight:600;
letter-spacing:.1em;text-transform:uppercase;background:var(--spore);color:var(--ink);position:relative;z-index:1}
.ss-log-lbl i{width:7px;height:7px;border-radius:50%;background:var(--ink);animation:ss-blink 1.6s steps(2,start) infinite}
.ss-log-win{flex:1;min-width:0;overflow:hidden;-webkit-mask-image:linear-gradient(90deg,transparent,#000 5%,#000 95%,transparent);mask-image:linear-gradient(90deg,transparent,#000 5%,#000 95%,transparent)}
.ss-log-track{display:flex;width:max-content;animation:ss-crawl 70s linear infinite}
.ss-log:hover .ss-log-track{animation-play-state:paused}
.ss-log-set{display:flex;align-items:center;margin:0;padding:0;list-style:none}
.ss-log-set li{display:flex;align-items:center;gap:18px;padding:17px 0 17px 18px;font-family:var(--mono);font-size:13px;letter-spacing:.02em;white-space:nowrap}
.ss-log-set li b{font-weight:600}
.ss-log-set li::after{content:"";width:7px;height:7px;border-radius:50%;background:var(--spore);box-shadow:0 0 0 1px var(--ink);flex:none}
@keyframes ss-crawl{to{transform:translateX(-50%)}}
@keyframes ss-blink{to{visibility:hidden}}
.ss--static .ss-log-track,.ss--static .ss-log-lbl i{animation:none}
@media (max-width:599px){.ss-log-lbl span{display:none}.ss-log-lbl{padding-right:14px}}
@media (prefers-reduced-motion: reduce) and (update: none){
.ss-log-track,.ss-log-lbl i{animation:none}
.ss-ph img,.ss-go,.ss-go svg{transition:none}
}
</style>
<div class="wrap">
<header class="ss-head">
<div>
<p class="kick">003 — Specimens</p>
<h2 id="ss-title" class="disp ss-title">Grown here</h2>
</div>
<div class="ss-side">
<p class="ss-intro">Six of <strong>41</strong> companies we have backed. Each walked into Halle 3 with a sketch and a stubborn thesis, and walked out with hardware that works outside the lab.</p>
<div class="ss-filter" role="group" aria-label="Filter specimens by sector">
<button type="button" class="ss-chip" data-f="all" aria-pressed="true">All<sup>06</sup></button>
<button type="button" class="ss-chip" data-f="climate" aria-pressed="false">Climate<sup>03</sup></button>
<button type="button" class="ss-chip" data-f="robotics" aria-pressed="false">Robotics<sup>01</sup></button>
<button type="button" class="ss-chip" data-f="health" aria-pressed="false">Health<sup>01</sup></button>
<button type="button" class="ss-chip" data-f="mobility" aria-pressed="false">Mobility<sup>01</sup></button>
</div>
<p class="ss-meta"><span class="ss-count" aria-live="polite">06 specimens</span><span class="ss-meta-r">Sorted by SPC no.</span></p>
</div>
</header>
<ul class="ss-grid">
<li class="ss-card" data-s="climate">
<article class="ss-card-in">
<div class="ss-frame ticks">
<div class="ss-ph">
<img src="assets/p-drone.jpg" width="1200" height="1800" loading="lazy" alt="A small white quadcopter drone hovering low over a green field with a tree line behind">
<span class="ss-tag"><i></i>Climate / Agri</span>
<span class="ss-x" aria-hidden="true"><span class="ss-x-plus"></span><span class="ss-x-read">X <b>000</b> · Y <b>000</b></span></span>
<span class="ss-go" aria-hidden="true"><svg viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M2 12 12 2M4 2h8v8"/></svg></span>
</div>
</div>
<div class="ss-lab">
<p class="ss-no"><span>SPC-01</span><span>of 06</span></p>
<h3 class="disp ss-name">Kestrel Fields</h3>
<p class="ss-what">Crop-scouting drones that map disease before it is visible.</p>
<dl class="ss-data">
<div><dt>Sector</dt><dd>Climate / Agri</dd></div>
<div><dt>Stage</dt><dd>Seed</dd></div>
<div><dt>Raised</dt><dd>€2.4M</dd></div>
</dl>
</div>
</article>
</li>
<li class="ss-card" data-s="climate">
<article class="ss-card-in">
<div class="ss-frame ticks">
<div class="ss-ph">
<img src="assets/p-sensor.jpg" width="1200" height="1796" loading="lazy" alt="A white sensor box mounted on a post among rows of flowering plants inside a greenhouse">
<span class="ss-tag"><i></i>Climate</span>
<span class="ss-x" aria-hidden="true"><span class="ss-x-plus"></span><span class="ss-x-read">X <b>000</b> · Y <b>000</b></span></span>
<span class="ss-go" aria-hidden="true"><svg viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M2 12 12 2M4 2h8v8"/></svg></span>
</div>
</div>
<div class="ss-lab">
<p class="ss-no"><span>SPC-02</span><span>of 06</span></p>
<h3 class="disp ss-name">Loam</h3>
<p class="ss-what">Greenhouse climate sensors that run five years on one cell.</p>
<dl class="ss-data">
<div><dt>Sector</dt><dd>Climate</dd></div>
<div><dt>Stage</dt><dd>Pre-seed</dd></div>
<div><dt>Raised</dt><dd>€1.1M</dd></div>
</dl>
</div>
</article>
</li>
<li class="ss-card" data-s="health">
<article class="ss-card-in">
<div class="ss-frame ticks">
<div class="ss-ph">
<img src="assets/p-hand.jpg" width="1200" height="1800" loading="lazy" alt="A raised hand wearing a black articulated exoskeleton glove against a plain white wall">
<span class="ss-tag"><i></i>Health</span>
<span class="ss-x" aria-hidden="true"><span class="ss-x-plus"></span><span class="ss-x-read">X <b>000</b> · Y <b>000</b></span></span>
<span class="ss-go" aria-hidden="true"><svg viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M2 12 12 2M4 2h8v8"/></svg></span>
</div>
</div>
<div class="ss-lab">
<p class="ss-no"><span>SPC-03</span><span>of 06</span></p>
<h3 class="disp ss-name">Tendon</h3>
<p class="ss-what">A haptic rehab glove for stroke patients.</p>
<dl class="ss-data">
<div><dt>Sector</dt><dd>Health</dd></div>
<div><dt>Stage</dt><dd>Seed</dd></div>
<div><dt>Raised</dt><dd>€3.8M</dd></div>
</dl>
</div>
</article>
</li>
<li class="ss-card" data-s="mobility">
<article class="ss-card-in">
<div class="ss-frame ticks">
<div class="ss-ph">
<img src="assets/p-bike.jpg" width="1200" height="1800" loading="lazy" alt="A grey electric cargo bike with a front basket and rear rack parked on a rooftop under a cloudy sky">
<span class="ss-tag"><i></i>Mobility</span>
<span class="ss-x" aria-hidden="true"><span class="ss-x-plus"></span><span class="ss-x-read">X <b>000</b> · Y <b>000</b></span></span>
<span class="ss-go" aria-hidden="true"><svg viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M2 12 12 2M4 2h8v8"/></svg></span>
</div>
</div>
<div class="ss-lab">
<p class="ss-no"><span>SPC-04</span><span>of 06</span></p>
<h3 class="disp ss-name">Pedal Freight</h3>
<p class="ss-what">Modular cargo e-bikes for last-mile logistics.</p>
<dl class="ss-data">
<div><dt>Sector</dt><dd>Mobility</dd></div>
<div><dt>Stage</dt><dd>Seed</dd></div>
<div><dt>Raised</dt><dd>€1.6M</dd></div>
</dl>
</div>
</article>
</li>
<li class="ss-card" data-s="climate">
<article class="ss-card-in">
<div class="ss-frame ticks">
<div class="ss-ph">
<img src="assets/p-solar.jpg" width="1200" height="1600" loading="lazy" alt="A compact solar panel on top of an amber signal beacon fixed to a railing">
<span class="ss-tag"><i></i>Climate</span>
<span class="ss-x" aria-hidden="true"><span class="ss-x-plus"></span><span class="ss-x-read">X <b>000</b> · Y <b>000</b></span></span>
<span class="ss-go" aria-hidden="true"><svg viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M2 12 12 2M4 2h8v8"/></svg></span>
</div>
</div>
<div class="ss-lab">
<p class="ss-no"><span>SPC-05</span><span>of 06</span></p>
<h3 class="disp ss-name">Heliotile</h3>
<p class="ss-what">Off-grid solar beacons for remote infrastructure.</p>
<dl class="ss-data">
<div><dt>Sector</dt><dd>Climate</dd></div>
<div><dt>Stage</dt><dd>Seed</dd></div>
<div><dt>Raised</dt><dd>€2.9M</dd></div>
</dl>
</div>
</article>
</li>
<li class="ss-card" data-s="robotics">
<article class="ss-card-in">
<div class="ss-frame ticks">
<div class="ss-ph">
<img src="assets/p-arm.jpg" width="1200" height="1798" loading="lazy" alt="A dark six-axis robot arm with a gripper mounted on a white mobile base against a grey backdrop">
<span class="ss-tag"><i></i>Robotics</span>
<span class="ss-x" aria-hidden="true"><span class="ss-x-plus"></span><span class="ss-x-read">X <b>000</b> · Y <b>000</b></span></span>
<span class="ss-go" aria-hidden="true"><svg viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M2 12 12 2M4 2h8v8"/></svg></span>
</div>
</div>
<div class="ss-lab">
<p class="ss-no"><span>SPC-06</span><span>of 06</span></p>
<h3 class="disp ss-name">Gripwise</h3>
<p class="ss-what">A €9k cobot arm that learns a task from one demonstration.</p>
<dl class="ss-data">
<div><dt>Sector</dt><dd>Robotics</dd></div>
<div><dt>Stage</dt><dd>Series A</dd></div>
<div><dt>Raised</dt><dd>€7.2M</dd></div>
</dl>
</div>
</article>
</li>
</ul>
<p class="ss-swipe" aria-hidden="true"><span>Swipe the tray</span><span>→</span></p>
</div>
<div class="ss-log" aria-label="Lab log">
<p class="ss-log-lbl" style="margin:0"><i></i><span>Lab log</span></p>
<div class="ss-log-win">
<div class="ss-log-track">
<ul class="ss-log-set">
<li><span><b>SPC-01</b> · flight 212 flagged leaf blight on row 14, nine days before the eye could</span></li>
<li><span><b>SPC-02</b> · node 0031 still running on its first cell, day 1,140</span></li>
<li><span><b>SPC-03</b> · glove v7 cleared 10,000 flex cycles, knuckle seam intact</span></li>
<li><span><b>SPC-04</b> · cargo module swap now 40 s with one hex key</span></li>
<li><span><b>SPC-05</b> · beacon logged a −31 °C night, uptime unbroken</span></li>
<li><span><b>SPC-06</b> · arm learned box folding from one demo, two retries</span></li>
<li><span><b>HALLE 3</b> · reflow oven recalibrated, profile B locked in</span></li>
<li><span><b>SHOP</b> · prototype no. 312 off the printer, still warm</span></li>
</ul>
<ul class="ss-log-set" aria-hidden="true">
<li><span><b>SPC-01</b> · flight 212 flagged leaf blight on row 14, nine days before the eye could</span></li>
<li><span><b>SPC-02</b> · node 0031 still running on its first cell, day 1,140</span></li>
<li><span><b>SPC-03</b> · glove v7 cleared 10,000 flex cycles, knuckle seam intact</span></li>
<li><span><b>SPC-04</b> · cargo module swap now 40 s with one hex key</span></li>
<li><span><b>SPC-05</b> · beacon logged a −31 °C night, uptime unbroken</span></li>
<li><span><b>SPC-06</b> · arm learned box folding from one demo, two retries</span></li>
<li><span><b>HALLE 3</b> · reflow oven recalibrated, profile B locked in</span></li>
<li><span><b>SHOP</b> · prototype no. 312 off the printer, still warm</span></li>
</ul>
</div>
</div>
</div>
<script>
(function(){
const root=document.getElementById('specimens');if(!root)return;
const SPx=window.SP||{reduced:true,rise(){},scramble(el,o){if(o&&o.text!=null)el.textContent=o.text}};
const R=SPx.reduced;
if(R)root.classList.add('ss--static');
const grid=root.querySelector('.ss-grid');
const cards=[...root.querySelectorAll('.ss-card')];
const chips=[...root.querySelectorAll('.ss-chip')];
const countEl=root.querySelector('.ss-count');
SPx.rise(root.querySelector('.ss-title'));
/* --- cards: staggered clip-path reveal from the bottom --- */
if(!R){
gsap.set(cards,{clipPath:'inset(100% 0% 0% 0%)'});
ScrollTrigger.batch(cards,{start:'top 92%',once:true,onEnter:batch=>{
gsap.to(batch,{clipPath:'inset(0% 0% 0% 0%)',duration:1.15,ease:'expo.out',stagger:.12,clearProps:'clipPath'});
gsap.from(batch.map(c=>c.querySelector('.ss-ph img')),{scale:1.22,duration:1.6,ease:'expo.out',stagger:.12,clearProps:'transform'});
}});
}
/* --- filtering with Flip --- */
let busy=null;
function setFilter(f){
chips.forEach(c=>c.setAttribute('aria-pressed',String(c.dataset.f===f)));
const keep=cards.filter(c=>f==='all'||c.dataset.s===f);
const n=keep.length;
const txt=String(n).padStart(2,'0')+(n===1?' specimen':' specimens');
if(R){cards.forEach(c=>c.classList.toggle('is-out',!keep.includes(c)));countEl.textContent=txt;return}
SPx.scramble(countEl,{text:txt,duration:.55});
if(busy)busy.progress(1);
// make sure nothing is still mid-reveal before measuring
cards.forEach(c=>{gsap.set(c,{clearProps:'clipPath'})});
const state=Flip.getState(cards,{props:'opacity'});
cards.forEach(c=>c.classList.toggle('is-out',!keep.includes(c)));
if(grid.scrollLeft)grid.scrollLeft=0;
busy=Flip.from(state,{
duration:.8,ease:'expo.inOut',absolute:true,stagger:.035,
onEnter:els=>gsap.fromTo(els,{opacity:0,scale:.9},{opacity:1,scale:1,duration:.6,ease:'expo.out',delay:.25,clearProps:'transform,opacity'}),
onLeave:els=>gsap.to(els,{opacity:0,scale:.9,duration:.4,ease:'power2.in'}),
onComplete:()=>{busy=null;ScrollTrigger.refresh()}
});
}
chips.forEach(c=>c.addEventListener('click',()=>{if(c.getAttribute('aria-pressed')==='true')return;setFilter(c.dataset.f)}));
/* --- hover: crosshair follower, name scramble --- */
const fine=matchMedia('(hover:hover) and (pointer:fine)').matches;
cards.forEach(card=>{
const ph=card.querySelector('.ss-ph'),x=card.querySelector('.ss-x'),read=card.querySelector('.ss-x-read'),
name=card.querySelector('.ss-name');
name.dataset.final=name.textContent;
let scr=false;
card.addEventListener('pointerenter',e=>{
if(e.pointerType!=='mouse'||scr)return;scr=true;
SPx.scramble(name,{duration:.6});setTimeout(()=>scr=false,700);
});
if(!fine)return;
const qx=R?(v=>gsap.set(x,{x:v})):gsap.quickTo(x,'x',{duration:.25,ease:'power3.out'});
const qy=R?(v=>gsap.set(x,{y:v})):gsap.quickTo(x,'y',{duration:.25,ease:'power3.out'});
const bs=read.querySelectorAll('b');
ph.addEventListener('pointerenter',e=>{
const r=ph.getBoundingClientRect();gsap.set(x,{x:e.clientX-r.left,y:e.clientY-r.top});x.classList.add('is-live');
});
ph.addEventListener('pointermove',e=>{
const r=ph.getBoundingClientRect(),px=e.clientX-r.left,py=e.clientY-r.top;
qx(px);qy(py);
// flip the readout to the left near the right edge so it never clips
read.style.transform=px>r.width-120?'translateX(calc(-100% - 28px))':'';
bs[0].textContent=String(Math.round(px/r.width*1000)).padStart(3,'0');
bs[1].textContent=String(Math.round(py/r.height*1000)).padStart(3,'0');
});
ph.addEventListener('pointerleave',()=>x.classList.remove('is-live'));
});
})();
</script>
</section>
<section id="germination" class="sg" aria-labelledby="sg-title">
<style>
/* ---------- 05 germination: the 16-week residency as a growing stem ---------- */
.sg{position:relative;background:var(--bg);color:var(--ink);border-top:1px solid var(--line)}
.sg-pin{position:relative}
.sg-head{display:flex;align-items:flex-end;justify-content:space-between;gap:24px;padding-top:clamp(72px,10vw,120px)}
.sg-title{font-size:clamp(52px,13vw,84px);margin-top:18px;max-width:10ch}
.sg-read{display:none}
.sg-ruler{display:none}
.sg-stage{position:relative}
/* ---- flow layout (mobile, reduced motion, no JS): vertical stem on the left ---- */
.sg-track{position:relative;max-width:1440px;margin-inline:auto;padding:48px var(--gut) clamp(72px,10vw,120px)}
.sg-svg{position:absolute;left:0;top:0;width:100%;height:100%;overflow:visible;pointer-events:none}
.sg-ghost{fill:none;stroke:var(--line);stroke-width:1.5;stroke-dasharray:2 7;stroke-linecap:round}
.sg-stem{fill:none;stroke:var(--ink);stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round}
.sg-lf{fill:none;stroke:var(--ink);stroke-width:var(--sg-sw,1.25);stroke-linecap:round;stroke-linejoin:round}
.sg-core{fill:var(--spore);stroke:var(--ink);stroke-width:var(--sg-sw,1);transform-box:fill-box;transform-origin:center}
.sg-dot{fill:var(--spore);stroke:var(--ink);stroke-width:1.25}
.sg-halo{fill:none;stroke:var(--spore);stroke-width:1.5;transform-box:fill-box;transform-origin:center;animation:sg-pulse 1.8s var(--ease) infinite}
@keyframes sg-pulse{0%{transform:scale(.6);opacity:.9}100%{transform:scale(1.9);opacity:0}}
@media (prefers-reduced-motion: reduce) and (update: none){.sg-halo{animation:none;opacity:0}}
.sg-intro{position:relative;margin:0 0 72px;padding-left:52px;color:var(--mute);font-size:12px;line-height:1.7;text-transform:uppercase;letter-spacing:.06em;max-width:40ch}
.sg-intro b{color:var(--ink);font-weight:500}
.sg-phase{position:relative;padding-left:52px;margin-bottom:96px}
.sg-phase:last-child{margin-bottom:0}
.sg-idx{margin:0 0 10px;font-size:11px;color:var(--mute);text-transform:uppercase;letter-spacing:.08em}
.sg-word{display:inline-block;font-size:clamp(80px,23vw,176px);line-height:.84;padding:.08em 0 .04em;color:transparent;
-webkit-text-stroke:1px var(--ink);
background-image:linear-gradient(var(--ink),var(--ink));background-repeat:no-repeat;background-position:0 0;background-size:100% 100%;
-webkit-background-clip:text;background-clip:text;will-change:transform}
.sg--js .sg-word{background-size:0% 100%}
.sg-low{display:grid;gap:22px;margin-top:22px}
.sg-range{margin:0 0 14px;display:flex;flex-wrap:wrap;align-items:center;gap:10px;text-transform:uppercase;letter-spacing:.06em}
.sg-verb{background:var(--spore-soft);padding:3px 8px;font-size:11px;font-weight:500}
.sg-list{list-style:none;margin:0;padding:0;border-top:1px solid var(--line)}
.sg-list li{position:relative;padding:9px 0 9px 24px;border-bottom:1px solid var(--line);font-size:15px;line-height:1.4}
.sg-list li::before{content:"";position:absolute;left:0;top:18px;width:12px;height:1px;background:var(--ink)}
.sg-note{margin:0;padding:14px 16px 16px;border:1px solid var(--line);background:var(--bg);max-width:420px}
.sg-note figcaption{font-size:11px;color:var(--mute);text-transform:uppercase;letter-spacing:.08em;margin-bottom:8px;display:flex;align-items:center;gap:8px}
.sg-note figcaption::before{content:"";width:6px;height:6px;background:var(--spore);flex:none}
.sg-note blockquote{margin:0;font-size:15px;line-height:1.45}
/* ---- horizontal pinned layout (desktop, motion on) ---- */
.sg--h .sg-pin{height:100vh;height:100svh;display:grid;grid-template-rows:auto minmax(0,1fr) auto;overflow:hidden;padding-top:var(--hdr)}
.sg--h .sg-head{width:100%;padding-top:clamp(20px,3.4vh,36px)}
.sg--h .sg-title{font-size:clamp(48px,min(5.4vw,9vh),92px);max-width:none;margin-top:12px}
.sg--h .sg-read{display:block;text-align:right;flex:none}
.sg-read-l{margin:0 0 6px;font-size:11px;color:var(--mute);text-transform:uppercase;letter-spacing:.08em}
.sg-read-n{margin:0;font-family:var(--mono);font-weight:500;font-size:clamp(36px,min(4.6vw,7.5vh),68px);line-height:1;letter-spacing:-.03em;white-space:nowrap}
.sg-read-n b{font-weight:500;display:inline-block;min-width:2ch;text-align:right;background:var(--spore);padding:0 .08em;margin-left:.12em;line-height:.92}
.sg-read-n i{font-style:normal;font-size:.34em;color:var(--mute);letter-spacing:0;margin-left:.3em;vertical-align:top}
.sg--h .sg-stage{overflow:hidden}
.sg--h .sg-track{position:absolute;left:0;top:0;height:100%;max-width:none;margin:0;padding:0;will-change:transform}
.sg--h .sg-intro{position:absolute;left:var(--gut);top:calc(var(--sg-y) + 44px);margin:0;padding:0;width:var(--sg-iw,340px)}
.sg--h .sg-phase{position:absolute;top:0;height:100%;margin:0;padding:0;width:var(--sg-pw,560px);display:grid;grid-template-rows:var(--sg-y) minmax(0,1fr)}
.sg--h .sg-up{align-self:end;padding-bottom:40px}
.sg--h .sg-word{font-size:min(14vw,21vh,220px)}
.sg--h .sg-low{align-self:start;margin:0;padding-top:clamp(28px,4.4vh,42px);grid-template-columns:minmax(0,1fr) minmax(0,200px);gap:26px;align-items:start}
@media (max-height:760px){.sg--h .sg-list li{padding-block:7px}.sg--h .sg-list li::before{top:16px}.sg--h .sg-range{margin-bottom:10px}}
.sg--h .sg-note{max-width:none}
.sg--h .sg-note blockquote,.sg--h .sg-list li{font-size:14px}
.sg--h .sg-ruler{display:block;position:relative;border-top:1px solid var(--line);padding-top:10px;padding-bottom:14px}
.sg-rw{position:relative}
.sg-rbar{position:absolute;left:var(--gut);right:var(--gut);top:-12px;height:3px}
.sg-rbar i{position:absolute;inset:0;background:var(--spore);transform-origin:0 50%;transform:scaleX(0)}
.sg-ruler ol{list-style:none;margin:0;padding:0;display:grid;grid-template-columns:repeat(16,minmax(0,1fr))}
.sg-ruler li{position:relative;padding-top:12px;font-family:var(--mono);font-size:11px;color:var(--mute);letter-spacing:.04em}
.sg-ruler li::before{content:"";position:absolute;left:0;top:0;width:1px;height:8px;background:var(--line)}
.sg-ruler li.sg-ph::before{height:12px;top:-4px}
.sg-ruler li span{display:inline-block;padding:1px 4px;margin-left:-4px}
.sg-ruler li.sg-past{color:var(--ink)}
.sg-ruler li.sg-past::before{background:var(--ink)}
.sg-ruler li.sg-now span{background:var(--spore);color:var(--ink)}
.sg-ruler li em{font-style:normal;color:var(--mute);margin-left:6px;text-transform:uppercase}
@media (max-width:1279px){.sg-ruler li em{display:none}}
@media (max-height:760px){.sg--h .sg-idx{display:none}}
@media (min-width:600px){
.sg-low{grid-template-columns:minmax(0,1fr) minmax(0,260px);gap:32px;align-items:start;max-width:900px}
.sg-intro,.sg-phase{padding-left:72px}
}
</style>
<div class="sg-pin">
<div class="sg-head wrap">
<div>
<p class="kick">004 — Germination</p>
<h2 class="sg-title disp" id="sg-title">16 weeks, soil to bloom</h2>
</div>
<div class="sg-read" aria-hidden="true">
<p class="sg-read-l mono" id="sg-read-l">Soil · Diagnose</p>
<p class="sg-read-n">Week<b id="sg-read-n">01</b><i>/16</i></p>
</div>
</div>
<div class="sg-stage">
<div class="sg-track">
<svg class="sg-svg" aria-hidden="true" focusable="false">
<path class="sg-ghost" d=""/>
<path class="sg-stem" d=""/>
<!-- soil: a first root and a hooked shoot -->
<g class="sg-node">
<path class="sg-lf" pathLength="1" d="M0 0C-2 7-7 11-9 18"/>
<path class="sg-lf" pathLength="1" d="M0 0C3 6 2 12 7 17"/>
<path class="sg-lf" pathLength="1" d="M-5 10C-9 11-12 10-15 13"/>
<path class="sg-lf" pathLength="1" d="M0 0C1-9-2-15 3-22C7-27 12-24 10-19"/>
</g>
<!-- root: two seed leaves -->
<g class="sg-node">
<path class="sg-lf" pathLength="1" d="M0 0C-3 8-1 15-5 23"/>
<path class="sg-lf" pathLength="1" d="M0 0C2-12-2-22 0-34"/>
<path class="sg-lf" pathLength="1" d="M0-30C-6-40-18-43-27-36C-18-28-7-27 0-30Z"/>
<path class="sg-lf" pathLength="1" d="M0-33C5-42 16-45 23-39C16-31 6-30 0-33Z"/>
</g>
<!-- sprout: true leaves -->
<g class="sg-node">
<path class="sg-lf" pathLength="1" d="M0 0C-3-18 4-34 0-54"/>
<path class="sg-lf" pathLength="1" d="M-1-17C-12-22-23-18-29-7C-17-5-7-9-1-17Z"/>
<path class="sg-lf" pathLength="1" d="M1-31C12-38 25-36 31-25C19-21 8-24 1-31Z"/>
<path class="sg-lf" pathLength="1" d="M0-54C-8-62-8-73-2-81C6-73 6-62 0-54Z"/>
<path class="sg-lf" pathLength="1" d="M-1-67L-1-60"/>
</g>
<!-- bloom: flower head -->
<g class="sg-node">
<path class="sg-lf" pathLength="1" d="M0 0C3-20-4-40 0-62"/>
<path class="sg-lf" pathLength="1" d="M0-21C10-29 22-28 29-17C17-13 7-15 0-21Z"/>
<path class="sg-lf" pathLength="1" d="M-1-36C-12-43-25-40-31-30C-19-26-8-29-1-36Z"/>
<path class="sg-lf" pathLength="1" d="M0-66C-7-73-6-83 0-86C6-83 7-73 0-66"/>
<path class="sg-lf" pathLength="1" transform="rotate(72 0 -66)" d="M0-66C-7-73-6-83 0-86C6-83 7-73 0-66"/>
<path class="sg-lf" pathLength="1" transform="rotate(144 0 -66)" d="M0-66C-7-73-6-83 0-86C6-83 7-73 0-66"/>
<path class="sg-lf" pathLength="1" transform="rotate(216 0 -66)" d="M0-66C-7-73-6-83 0-86C6-83 7-73 0-66"/>
<path class="sg-lf" pathLength="1" transform="rotate(288 0 -66)" d="M0-66C-7-73-6-83 0-86C6-83 7-73 0-66"/>
<circle class="sg-core" cx="0" cy="-66" r="4.5"/>
</g>
<circle class="sg-halo" r="11" cx="0" cy="0"/>
<circle class="sg-dot" r="5.5" cx="0" cy="0"/>
</svg>
<p class="sg-intro mono"><b>Germinate residency</b><br>2 cohorts a year · 8 teams each<br>One stem, four phases. Scroll and it grows.</p>
<article class="sg-phase">
<div class="sg-up">
<p class="sg-idx mono">Phase 01 / 04</p>
<h3 class="sg-word disp">Soil</h3>
</div>
<div class="sg-low">
<div>
<p class="sg-range mono">Weeks 01–03 <span class="sg-verb">Diagnose</span></p>
<ul class="sg-list">
<li>Tech risk audit: which law of physics could still say no</li>
<li>Twenty customer interviews before anyone opens CAD</li>
<li>Shop induction: CNC, reflow oven, wet bench, RF room</li>
</ul>
</div>
<figure class="sg-note ticks">
<figcaption class="mono">Field note · Wk 02</figcaption>
<blockquote>Cut two features after one interview. Cheapest prototype we'll ever build.</blockquote>
</figure>
</div>
</article>
<article class="sg-phase">
<div class="sg-up">
<p class="sg-idx mono">Phase 02 / 04</p>
<h3 class="sg-word disp">Root</h3>
</div>
<div class="sg-low">
<div>
<p class="sg-range mono">Weeks 04–08 <span class="sg-verb">Build</span></p>
<ul class="sg-list">
<li>Prototype sprint: one revision a week, built in Halle 3</li>
<li>Supply-chain map down to the second-tier vendor</li>
<li>First hires, with a partner sitting in on the interviews</li>
</ul>
</div>
<figure class="sg-note ticks">
<figcaption class="mono">Field note · Wk 06</figcaption>
<blockquote>Rev C survived the drop test. Rev B now props the workshop door open.</blockquote>
</figure>
</div>
</article>
<article class="sg-phase">
<div class="sg-up">
<p class="sg-idx mono">Phase 03 / 04</p>
<h3 class="sg-word disp">Sprout</h3>
</div>
<div class="sg-low">
<div>
<p class="sg-range mono">Weeks 09–13 <span class="sg-verb">Prove</span></p>
<ul class="sg-list">
<li>Pilot with a customer who pays. Letters of intent don't count</li>
<li>Regulatory path drafted: CE, MDR or FCC, whichever bites first</li>
<li>Data room assembled while the numbers are still boring</li>
</ul>
</div>
<figure class="sg-note ticks">
<figcaption class="mono">Field note · Wk 11</figcaption>
<blockquote>First invoice went out on a Tuesday. We printed it twice: one to file, one to pin up.</blockquote>
</figure>
</div>
</article>
<article class="sg-phase">
<div class="sg-up">
<p class="sg-idx mono">Phase 04 / 04</p>
<h3 class="sg-word disp">Bloom</h3>
</div>
<div class="sg-low">
<div>
<p class="sg-range mono">Weeks 14–16 <span class="sg-verb">Raise</span></p>
<ul class="sg-list">
<li>Investor room: warm one-to-ones with funds that back hardware</li>
<li>Demo Day in Halle 3: working devices on the bench, ten slides max</li>
<li>Canopy follow-on held in reserve for the round after</li>
</ul>
</div>
<figure class="sg-note ticks">
<figcaption class="mono">Field note · Wk 16</figcaption>
<blockquote>Nobody asked for the deck. Everybody asked to hold the device.</blockquote>
</figure>
</div>
</article>
</div>
</div>
<div class="sg-ruler" aria-hidden="true">
<div class="wrap sg-rw"><div class="sg-rbar"><i></i></div><ol></ol></div>
</div>
</div>
<script>
(function(){
const sec=document.getElementById('germination');
if(!sec||!window.gsap)return;
const SPR=window.SP||{reduced:false};
const stage=sec.querySelector('.sg-stage'),track=sec.querySelector('.sg-track'),svg=sec.querySelector('.sg-svg');
const stem=svg.querySelector('.sg-stem'),ghost=svg.querySelector('.sg-ghost'),dot=svg.querySelector('.sg-dot'),halo=svg.querySelector('.sg-halo');
const phases=[...sec.querySelectorAll('.sg-phase')],words=phases.map(p=>p.querySelector('.sg-word'));
const nodes=[...svg.querySelectorAll('.sg-node')];
const nodeParts=nodes.map(n=>[...n.querySelectorAll('.sg-lf')]);
const cores=nodes.map(n=>n.querySelector('.sg-core'));
const intro=sec.querySelector('.sg-intro');
const readN=document.getElementById('sg-read-n'),readL=document.getElementById('sg-read-l');
const rbar=sec.querySelector('.sg-rbar i'),ol=sec.querySelector('.sg-ruler ol');
const START=[1,4,9,14];
const PHASE=['Soil · Diagnose','Root · Build','Sprout · Prove','Bloom · Raise'];
/* hand-set wobble of the stem: lateral offsets (x amplitude) and uneven spacing between control points */
const WOB=[0,-.55,.35,-.15,.8,.2,-.7,-.05,.5,-.4,.25,.9,-.45,.05,-.8,.4,.12,-.6,.7,-.25,.3,-.9,.45,0,-.3,.65,-.5,.2,-.1,.55];
const GAP=[1,.8,1.15,.9,1.25,.85,1,1.1,.75,1.2,.95,1.05,.8,1.3,.9,1];
/* build the ruler ticks */
const ticks=[];
for(let w=1;w<=16;w++){
const li=document.createElement('li');
const s=document.createElement('span');s.textContent=String(w).padStart(2,'0');li.appendChild(s);
const pi=START.indexOf(w);
if(pi>-1){li.classList.add('sg-ph');const e=document.createElement('em');e.textContent=PHASE[pi].split(' ')[0];li.appendChild(e)}
ol.appendChild(li);ticks.push(li);
}
const clamp=(v,a=0,b=1)=>v<a?a:v>b?b:v;
const lerp=(a,b,t)=>a+(b-a)*t;
let geo=null;
/* Catmull-Rom through the points, emitted as cubic beziers */
function curve(p){
let d='M'+p[0][0].toFixed(1)+' '+p[0][1].toFixed(1);
for(let i=0;i<p.length-1;i++){
const a=p[i-1]||p[i],b=p[i],c=p[i+1],e=p[i+2]||c;
d+='C'+(b[0]+(c[0]-a[0])/6).toFixed(1)+' '+(b[1]+(c[1]-a[1])/6).toFixed(1)+' '
+(c[0]-(e[0]-b[0])/6).toFixed(1)+' '+(c[1]-(e[1]-b[1])/6).toFixed(1)+' '
+c[0].toFixed(1)+' '+c[1].toFixed(1);
}
return d;
}
/* sample the path so a coordinate along the growth axis maps back to a path length */
function sample(axis){
const L=stem.getTotalLength(),cs=[],ls=[];
for(let l=0;l<=L;l+=6){const q=stem.getPointAtLength(l);cs.push(axis?q.y:q.x);ls.push(l)}
const q=stem.getPointAtLength(L);cs.push(axis?q.y:q.x);ls.push(L);
return {L,cs,ls};
}
function lenAt(c){
const cs=geo.cs,ls=geo.ls;
if(c<=cs[0])return 0;if(c>=cs[cs.length-1])return geo.L;
let lo=0,hi=cs.length-1;
while(hi-lo>1){const m=(lo+hi)>>1;if(cs[m]<c)lo=m;else hi=m}
return lerp(ls[lo],ls[hi],(c-cs[lo])/((cs[hi]-cs[lo])||1));
}
function placeNodes(rot,scale){
geo.nodeC.forEach((c,i)=>{
const q=stem.getPointAtLength(lenAt(c));
nodes[i].setAttribute('transform','translate('+q.x.toFixed(1)+' '+q.y.toFixed(1)+') rotate('+rot+') scale('+scale+')');
nodes[i].style.setProperty('--sg-sw',(1.3/scale).toFixed(3));
});
}
/* horizontal geometry: weeks are evenly spaced along the track so the readout matches what you see */
function buildH(){
const vw=stage.clientWidth,sh=stage.clientHeight;
const u=Math.max(vw*.2,250),W0=Math.max(vw*.36,330);
const xEnd=W0+16*u,trackW=Math.round(xEnd+vw*.04+40);
track.style.width=trackW+'px';
track.style.setProperty('--sg-pw',Math.round(Math.min(600,vw*.56,3*u-120))+'px');
/* stem height: under the tallest phase word, with the spare stage height split above and below */
const upH=Math.max(...phases.map(p=>p.querySelector('.sg-up').offsetHeight));
const lowH=Math.max(...phases.map(p=>p.querySelector('.sg-low').offsetHeight));
const y=Math.round(upH+Math.max(4,(sh-upH-lowH)*.45));
track.style.setProperty('--sg-y',y+'px');
track.style.setProperty('--sg-iw',Math.round(Math.min(360,W0-80))+'px');
const lefts=START.map(w=>W0+(w-1)*u+44);
phases.forEach((p,i)=>p.style.left=lefts[i]+'px');
const amp=Math.min(34,sh*.05),base=Math.max(120,u*.5),pts=[];
let x=Math.max(20,vw*.03),k=0;
while(x<xEnd){pts.push([x,y+WOB[k%WOB.length]*amp]);x+=base*GAP[k%GAP.length];k++}
pts.push([xEnd,y+WOB[k%WOB.length]*amp*.5]);
const d=curve(pts);stem.setAttribute('d',d);ghost.setAttribute('d',d);
geo=Object.assign({mode:'h',vw,sh,u,W0,xEnd,trackW,dist:trackW-vw,y,lefts,nodeC:START.map(w=>W0+(w-1)*u),reach:Math.max(140,u*.7),ww:words.map(w=>w.offsetWidth)},sample(0));
stem.style.strokeDasharray=geo.L+' '+(geo.L+10);
placeNodes(0,Math.min(1.35,Math.max(1,sh/480)));
}
/* vertical geometry for the stacked timeline */
function buildV(){
const cs=getComputedStyle(track),pl=parseFloat(cs.paddingLeft)||16;
const H=track.offsetHeight,W=track.offsetWidth,wide=W>=600;
const x0=pl+(wide?26:18),y0=24,yEnd=H-parseFloat(cs.paddingBottom)+8;
const amp=wide?9:6,pts=[];let y=y0,k=0;
while(y<yEnd){pts.push([x0+WOB[k%WOB.length]*amp,y]);y+=110*GAP[k%GAP.length];k++}
pts.push([x0,yEnd]);
const d=curve(pts);stem.setAttribute('d',d);ghost.setAttribute('d',d);
const nodeC=phases.map(p=>p.offsetTop-40);
geo=Object.assign({mode:'v',y0,yEnd,nodeC,reach:150},sample(1));
stem.style.strokeDasharray=geo.L+' '+(geo.L+10);
placeNodes(90,wide?.85:.7);
}
/* grow the stem to coordinate c on its growth axis; leaves draw once the tip has passed their node */
function drawTo(c){
const len=lenAt(c);
stem.style.strokeDashoffset=(geo.L-len).toFixed(1);
const q=stem.getPointAtLength(len);
dot.setAttribute('cx',q.x.toFixed(1));dot.setAttribute('cy',q.y.toFixed(1));
halo.setAttribute('cx',q.x.toFixed(1));halo.setAttribute('cy',q.y.toFixed(1));
const vis=len>2?1:0;dot.style.opacity=vis;halo.style.visibility=vis?'visible':'hidden';
geo.nodeC.forEach((nc,i)=>{
const lp=clamp((c-nc)/geo.reach),parts=nodeParts[i],n=parts.length;
parts.forEach((pa,j)=>{const s=clamp((lp-j/n*.55)/.45);pa.style.strokeDashoffset=(1-s).toFixed(3)});
if(cores[i]){const s=clamp((lp-.85)/.15);cores[i].style.transform='scale('+s.toFixed(3)+')'}
});
}
function initParts(){nodeParts.forEach(ps=>ps.forEach(p=>{p.style.strokeDasharray='1 1.01';p.style.strokeDashoffset='1'}))}
let lastWeek=0;
function renderH(p){
if(!geo||geo.mode!=='h')return;
const g=geo,tx=-p*g.dist;
track.style.transform='translate3d('+tx.toFixed(1)+'px,0,0)';
/* tip rides near the middle of the screen, easing in from the left at the start and out to the end */
const s0=g.vw*.3,s1=g.vw*.5,s2=g.xEnd-g.dist;
const ts=p<.08?lerp(s0,s1,p/.08):p<.88?s1:lerp(s1,s2,(p-.88)/.12);
const tip=ts+p*g.dist;
drawTo(tip);
phases.forEach((ph,i)=>{
const left=g.lefts[i]+tx;
/* ink fills the outlined word in step with the growing tip (which rides the screen centre) */
/* the phase word lags behind the bullets a little */
const px=Math.min(140,Math.max(0,g.vw*.95-left)*.09);
const f=clamp((tip-g.lefts[i]-px+12)/(g.ww[i]+24));
words[i].style.backgroundSize=(f*100).toFixed(1)+'% 100%';
words[i].style.transform='translate3d('+px.toFixed(1)+'px,0,0)';
});
intro.style.transform='translate3d('+Math.min(60,p*g.dist*.08).toFixed(1)+'px,0,0)';
const wf=1+(tip-g.W0)/g.u,wk=clamp(Math.floor(wf),1,16);
rbar.style.transform='scaleX('+clamp((wf-1)/16).toFixed(4)+')';
if(wk!==lastWeek){
lastWeek=wk;
readN.textContent=String(wk).padStart(2,'0');
let pi=0;START.forEach((s,i)=>{if(wk>=s)pi=i});
readL.textContent=PHASE[pi];
ticks.forEach((t,i)=>{t.classList.toggle('sg-past',i+1<=wk);t.classList.toggle('sg-now',i+1===wk)});
gsap.fromTo(readN,{yPercent:35,opacity:.2},{yPercent:0,opacity:1,duration:.35,ease:'power3.out',overwrite:true});
}
}
function renderV(p){
if(!geo||geo.mode!=='v')return;
drawTo(lerp(geo.y0,geo.yEnd,p));
}
function clearInline(){
track.style.cssText='';phases.forEach(p=>p.style.left='');
words.forEach(w=>{w.style.transform='';w.style.backgroundSize=''});
intro.style.transform='';
}
/* reduced motion: stacked layout, everything drawn and filled, no pin */
if(SPR.reduced){
const full=()=>{buildV();drawTo(geo.yEnd+999)};
full();
window.addEventListener('load',full);
let rt;window.addEventListener('resize',()=>{clearTimeout(rt);rt=setTimeout(full,150)});
return;
}
sec.classList.add('sg--js');
initParts();
const mm=gsap.matchMedia();
mm.add('(min-width: 900px)',()=>{
sec.classList.add('sg--h');
lastWeek=0;
const proxy={p:0};
const rebuild=()=>{track.style.transform='';buildH()};
rebuild();
const tw=gsap.to(proxy,{p:1,ease:'none',onUpdate:()=>renderH(proxy.p),
scrollTrigger:{trigger:sec,start:'top top',end:()=>'+='+Math.round(geo.dist*1.15),pin:true,scrub:.8,anticipatePin:1,invalidateOnRefresh:true}});
const after=()=>renderH(proxy.p);
ScrollTrigger.addEventListener('refreshInit',rebuild);
ScrollTrigger.addEventListener('refresh',after);
renderH(0);
return ()=>{
ScrollTrigger.removeEventListener('refreshInit',rebuild);
ScrollTrigger.removeEventListener('refresh',after);
tw.scrollTrigger&&tw.scrollTrigger.kill();
sec.classList.remove('sg--h');clearInline();
};
});
mm.add('(max-width: 899.98px)',()=>{
const proxy={p:0};
const rebuild=()=>buildV();
rebuild();
gsap.to(proxy,{p:1,ease:'none',onUpdate:()=>renderV(proxy.p),
scrollTrigger:{trigger:track,start:'top 68%',end:'bottom 68%',scrub:.6,invalidateOnRefresh:true}});
words.forEach(w=>gsap.fromTo(w,{backgroundSize:'0% 100%'},{backgroundSize:'100% 100%',ease:'none',
scrollTrigger:{trigger:w,start:'top 88%',end:'top 42%',scrub:.4}}));
const after=()=>renderV(proxy.p);
ScrollTrigger.addEventListener('refreshInit',rebuild);
ScrollTrigger.addEventListener('refresh',after);
renderV(0);
return ()=>{
ScrollTrigger.removeEventListener('refreshInit',rebuild);
ScrollTrigger.removeEventListener('refresh',after);
clearInline();
};
});
})();
</script>
</section>
<section id="voices" class="sv" aria-labelledby="sv-title">
<style>
.sv{padding:clamp(88px,10vw,168px) 0 clamp(88px,10vw,160px);background:var(--bg);position:relative}
.sv-sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}
/* ---------- head ---------- */
.sv-head{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,360px);gap:24px 48px;align-items:end;
padding-bottom:clamp(28px,3.5vw,48px);border-bottom:1px solid var(--line);margin-bottom:clamp(32px,4vw,64px)}
.sv-title{font-size:clamp(56px,9.4vw,164px);margin-top:22px}
.sv-title .w,.sv-fh .w{padding-top:.14em;margin-top:-.14em}
.sv-title em{font-style:normal;color:var(--spore-deep)}
.sv-lede{margin:0;color:var(--mute);font-size:15px;max-width:36ch}
.sv-lede b{color:var(--ink);font-weight:500}
/* ---------- testimonial stage ---------- */
.sv-stage{display:grid;grid-template-columns:minmax(0,5fr) minmax(0,7fr);gap:clamp(32px,5vw,96px);align-items:stretch;outline-offset:8px}
.sv-frame{border:1px solid var(--line);padding:10px;align-self:start;background:var(--bg)}
.sv-pic{position:relative;aspect-ratio:1/1;overflow:hidden;background:var(--fog)}
.sv-pic img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;opacity:0}
.sv-pic img.is-on{opacity:1}
.sv-cells{position:absolute;inset:0;pointer-events:none;z-index:2}
.sv-cell{position:absolute;width:calc(12.5% + 1px);height:calc(12.5% + 1px);background:var(--bg);transform:scale(0);will-change:transform}
.sv-tag{position:absolute;left:10px;top:10px;z-index:3;background:var(--ink);color:#fff;font-family:var(--mono);font-size:11px;
letter-spacing:.08em;text-transform:uppercase;padding:6px 9px;display:inline-flex;gap:8px;align-items:center}
.sv-tag::before{content:"";width:6px;height:6px;background:var(--spore)}
.sv-cap{display:flex;justify-content:space-between;gap:12px;padding-top:10px;font-family:var(--mono);font-size:11px;
letter-spacing:.08em;text-transform:uppercase;color:var(--mute)}
.sv-cap span:last-child{color:var(--ink);text-align:right}
.sv-copy{display:flex;flex-direction:column;min-width:0}
.sv-mark{width:52px;height:52px;background:var(--spore);display:grid;place-items:center;flex:none;margin-bottom:clamp(20px,2.4vw,36px)}
.sv-mark svg{width:22px;height:22px}
.sv-slides{display:grid;flex:1}
.sv-slide{grid-area:1/1;visibility:hidden;display:flex;flex-direction:column;justify-content:space-between;gap:32px}
.sv-slide.is-on{visibility:visible}
.sv-quote{margin:0;font-family:var(--body);font-weight:400;font-size:clamp(24px,2.6vw,40px);line-height:1.18;letter-spacing:-.022em;text-wrap:pretty}
.sv-quote mark{background:none;color:inherit}
.sv-quote mark .w>span,.sv-quote mark:not(:has(.w)){background:linear-gradient(var(--spore-soft),var(--spore-soft)) 0 88%/100% .42em no-repeat}
.sv-quote mark .w+.w>span,.sv-quote mark .w:not(:last-child)>span{padding-right:.28em;margin-right:-.28em}
.sv-who{display:grid;grid-template-columns:auto 1fr;gap:4px 20px;align-items:end;padding-top:22px;border-top:1px solid var(--line)}
.sv-name{font-size:clamp(34px,3.4vw,54px);grid-row:span 2}
.sv-name .w{padding-top:.16em;margin-top:-.16em}
.sv-role{font-family:var(--mono);font-size:12px;letter-spacing:.08em;text-transform:uppercase;color:var(--ink)}
.sv-co{font-family:var(--mono);font-size:12px;letter-spacing:.02em;color:var(--mute)}
.sv-ctrl{display:flex;align-items:center;gap:20px;margin-top:clamp(28px,3vw,44px)}
.sv-btns{display:flex;gap:8px}
.sv-nav{width:56px;height:56px;display:grid;place-items:center;box-shadow:inset 0 0 0 1px var(--ink);background:var(--bg);color:var(--ink);
transition:background-color .3s var(--ease)}
.sv-nav svg{width:18px;height:18px;transition:transform .35s var(--ease)}
.sv-nav:hover{background:var(--spore)}
.sv-nav--prev:hover svg{transform:translateX(-3px)}
.sv-nav--next:hover svg{transform:translateX(3px)}
.sv-count{font-family:var(--mono);font-size:13px;letter-spacing:.06em;white-space:nowrap;min-width:6.5ch}
.sv-count b{font-weight:600}
.sv-count span{color:var(--mute)}
.sv-bar{flex:1;display:flex;gap:6px;min-width:60px}
.sv-seg{flex:1;height:3px;background:var(--line);position:relative;overflow:hidden}
.sv-seg i{position:absolute;inset:0;background:var(--spore);transform-origin:0 50%;transform:scaleX(0)}
.sv-state{font-family:var(--mono);font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:var(--mute);white-space:nowrap;min-width:6ch;text-align:right}
/* ---------- FAQ ---------- */
.sv-faq{display:grid;grid-template-columns:minmax(0,4fr) minmax(0,8fr);gap:clamp(32px,5vw,96px);
margin-top:clamp(96px,11vw,176px);padding-top:clamp(32px,4vw,56px);border-top:1px solid var(--ink)}
.sv-stick{position:sticky;top:calc(var(--hdr) + 32px)}
.sv-fh{font-size:clamp(56px,7.4vw,124px);margin:18px 0 22px}
.sv-still{margin:0;color:var(--mute);line-height:1.7}
.sv-still a{color:var(--ink);text-decoration:none;background:linear-gradient(var(--spore),var(--spore)) 0 100%/100% 2px no-repeat;
transition:background-size .35s var(--ease);padding:1px 0}
.sv-still a:hover{background-size:100% 100%}
.sv-list{border-top:1px solid var(--line)}
.sv-row{border-bottom:1px solid var(--line)}
.sv-qh{margin:0}
.sv-q{width:100%;display:grid;grid-template-columns:44px minmax(0,1fr) 44px;gap:16px;align-items:center;text-align:left;padding:22px 0}
.sv-qn{font-family:var(--mono);font-size:12px;letter-spacing:.06em;color:var(--mute);transition:color .3s var(--ease)}
.sv-qt{font-family:var(--body);font-weight:500;font-size:clamp(18px,1.45vw,21px);line-height:1.3;letter-spacing:-.012em;transition:transform .45s var(--ease)}
.sv-ic{width:44px;height:44px;background:var(--spore);position:relative;transition:background-color .3s var(--ease)}
.sv-ic::before,.sv-ic::after{content:"";position:absolute;left:50%;top:50%;width:16px;height:1.5px;margin:-.75px 0 0 -8px;background:var(--ink);
transition:transform .5s var(--ease),background-color .3s var(--ease)}
.sv-ic::after{transform:rotate(90deg)}
.sv-q[aria-expanded="true"] .sv-ic::before{transform:rotate(135deg)}
.sv-q[aria-expanded="true"] .sv-ic::after{transform:rotate(225deg)}
.sv-q:hover .sv-qt{transform:translateX(6px)}
.sv-q:hover .sv-ic{background:var(--ink)}
.sv-q:hover .sv-ic::before,.sv-q:hover .sv-ic::after{background:var(--spore)}
.sv-q[aria-expanded="true"] .sv-qn{color:var(--ink)}
.sv-q:focus-visible{outline-offset:-2px}
.sv-a{overflow:hidden}
.sv-ai{padding:0 60px 28px 60px;color:var(--mute);max-width:68ch}
.sv-ai p{margin:0}
.sv-ai b{color:var(--ink);font-weight:500}
@media (max-width:899px){
.sv-head{grid-template-columns:1fr}
.sv-stage{grid-template-columns:1fr;gap:28px}
.sv-frame{max-width:560px;width:100%}
.sv-faq{grid-template-columns:1fr;gap:28px}
.sv-stick{position:static}
}
@media (max-width:560px){
.sv-who{grid-template-columns:1fr}
.sv-name{grid-row:auto;margin-bottom:6px}
.sv-ctrl{flex-wrap:wrap;gap:14px 16px}
.sv-bar{order:3;flex-basis:100%}
.sv-state{margin-left:auto}
.sv-nav{width:48px;height:48px}
.sv-q{grid-template-columns:minmax(0,1fr) 40px;gap:14px;padding:18px 0}
.sv-qn{display:none}
.sv-ic{width:40px;height:40px}
.sv-ai{padding:0 0 24px}
}
@media (prefers-reduced-motion: reduce) and (update: none){
.sv-nav,.sv-nav svg,.sv-qt,.sv-ic,.sv-ic::before,.sv-ic::after,.sv-still a{transition:none}
}
</style>
<div class="wrap">
<header class="sv-head">
<div>
<span class="kick">005 — Founders</span>
<h2 class="disp sv-title" id="sv-title">Ask the ones<br>who <em>grew</em></h2>
</div>
<p class="sv-lede" data-sp-reveal>Three founders from past cohorts, lightly edited for swearing. We asked one question: <b>what actually helped?</b></p>
</header>
<div class="sv-stage" tabindex="0" role="region" aria-roledescription="carousel" aria-label="Founder testimonials. Use the left and right arrow keys to switch.">
<figure class="sv-frame ticks" style="margin:0">
<div class="sv-pic">
<img class="is-on" src="assets/f1.jpg" width="900" height="900" loading="lazy" alt="Amara Okafor, CEO of Loam, smiling in front of a whiteboard covered in handwritten notes">
<img src="assets/f2.jpg" width="900" height="900" loading="lazy" alt="Tomás Ferreira, CTO of Gripwise, seated at a white desk in front of a painted brick wall">
<img src="assets/f3.jpg" width="900" height="900" loading="lazy" alt="Lena Voss, co-founder of Tendon, pointing towards the camera in a white shirt">
<span class="sv-tag" aria-hidden="true">Specimen <span class="sv-tagn">Loam</span></span>
<div class="sv-cells" aria-hidden="true"></div>
</div>
<figcaption class="sv-cap" aria-hidden="true"><span class="sv-capl">Fig. 05.1</span><span class="sv-capr">Climate · Pre-seed</span></figcaption>
</figure>
<div class="sv-copy">
<div class="sv-mark" aria-hidden="true"><svg viewBox="0 0 22 22" fill="var(--ink)"><path d="M0 22V12L5 0h4L6 10h4v12zM12 22V12L17 0h4l-3 10h4v12z"/></svg></div>
<div class="sv-slides">
<article class="sv-slide is-on" role="group" aria-roledescription="slide" aria-label="1 of 3" data-co="Loam" data-meta="Climate · Pre-seed">
<blockquote class="sv-quote">Week nine, 3am, our sensor board browned out at minus four in the cold room. Nobody told us to go home. A shop tech talked me through a <mark>full respin</mark>, and fresh boards were on the reflow bench before the first U-Bahn.</blockquote>
<div class="sv-who">
<p class="disp sv-name" style="margin:0">Amara Okafor</p>
<span class="sv-role">CEO</span>
<span class="sv-co">Loam — greenhouse sensors, five years on one cell</span>
</div>
</article>
<article class="sv-slide" role="group" aria-roledescription="slide" aria-label="2 of 3" aria-hidden="true" data-co="Gripwise" data-meta="Robotics · Series A">
<blockquote class="sv-quote">Every investor called a €9k arm a toy. Spore gave us bench time and one intro to a Neukölln bakery that became our <mark>first paying pilot</mark>. The arm learned to box pretzels from a single demo. Nobody says toy anymore.</blockquote>
<div class="sv-who">
<p class="disp sv-name" style="margin:0">Tomás Ferreira</p>
<span class="sv-role">CTO</span>
<span class="sv-co">Gripwise — cobot arm that learns from one demonstration</span>
</div>
</article>
<article class="sv-slide" role="group" aria-roledescription="slide" aria-label="3 of 3" aria-hidden="true" data-co="Tendon" data-meta="Health · Seed">
<blockquote class="sv-quote">CE marking under the MDR is where medical hardware goes to die. They sat with us through the technical file, found a notified body with an actual slot, and made us rewrite our clinical evaluation twice. <mark>Both times they were right.</mark></blockquote>
<div class="sv-who">
<p class="disp sv-name" style="margin:0">Lena Voss</p>
<span class="sv-role">Co-founder</span>
<span class="sv-co">Tendon — haptic rehab glove for stroke patients</span>
</div>
</article>
</div>
<div class="sv-ctrl">
<div class="sv-btns">
<button class="sv-nav sv-nav--prev" type="button" aria-label="Previous founder"><svg viewBox="0 0 18 18" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M16 9H2M8 3 2 9l6 6"/></svg></button>
<button class="sv-nav sv-nav--next" type="button" aria-label="Next founder"><svg viewBox="0 0 18 18" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M2 9h14M10 3l6 6-6 6"/></svg></button>
</div>
<p class="sv-count" style="margin:0" aria-hidden="true"><b class="sv-cur">01</b> <span>/ 03</span></p>
<div class="sv-bar" aria-hidden="true"><span class="sv-seg"><i></i></span><span class="sv-seg"><i></i></span><span class="sv-seg"><i></i></span></div>
<span class="sv-state" aria-hidden="true">Auto</span>
</div>
<p class="sv-sr" aria-live="polite" aria-atomic="true"></p>
</div>
</div>
<div class="sv-faq">
<div class="sv-side">
<div class="sv-stick">
<span class="kick">006 — FAQ</span>
<h2 class="disp sv-fh" id="sv-faq-title">Questions</h2>
<p class="sv-still mono">Still stuck?<br><a href="mailto:[email protected]">[email protected]</a></p>
</div>
</div>
<div class="sv-list" aria-labelledby="sv-faq-title" role="list">
<div class="sv-row" role="listitem">
<h3 class="sv-qh"><button class="sv-q" type="button" id="sv-q1" aria-expanded="true" aria-controls="sv-a1"><span class="sv-qn">01</span><span class="sv-qt">Who is Germinate for?</span><span class="sv-ic" aria-hidden="true"></span></button></h3>
<div class="sv-a" id="sv-a1" role="region" aria-labelledby="sv-q1"><div class="sv-ai"><p>Technical teams of two to four building something you could drop on your foot: robots, climate hardware, medical devices, sensors with a brain. Bring a working bench prototype, however ugly, and a reason to think someone will pay for the next version. <b>Eight teams per cohort</b>, so we read every application twice.</p></div></div>
</div>
<div class="sv-row" role="listitem">
<h3 class="sv-qh"><button class="sv-q" type="button" id="sv-q2" aria-expanded="false" aria-controls="sv-a2"><span class="sv-qn">02</span><span class="sv-qt">Do you take equity?</span><span class="sv-ic" aria-hidden="true"></span></button></h3>
<div class="sv-a" id="sv-a2" role="region" aria-labelledby="sv-q2" hidden><div class="sv-ai"><p>Yes. Germinate comes with a <b>€150k cheque for roughly 7%</b>, on terms we publish before you apply. That covers the money, 16 weeks in Halle 3, machine shop and wet lab time, and the people who run them. No service fees, no invoices disguised as "studio credits".</p></div></div>
</div>
<div class="sv-row" role="listitem">
<h3 class="sv-qh"><button class="sv-q" type="button" id="sv-q3" aria-expanded="false" aria-controls="sv-a3"><span class="sv-qn">03</span><span class="sv-qt">Can we join from outside Berlin?</span><span class="sv-ic" aria-hidden="true"></span></button></h3>
<div class="sv-a" id="sv-a3" role="region" aria-labelledby="sv-q3" hidden><div class="sv-ai"><p>Most of our teams do. We ask you to <b>relocate to Berlin for the 16 weeks</b>: hardware is a contact sport and the reflow oven will not come to you. We write the visa letters, help you find a flat near Wedding, and know which Bürgeramt appointments are worth fighting for.</p></div></div>
</div>
<div class="sv-row" role="listitem">
<h3 class="sv-qh"><button class="sv-q" type="button" id="sv-q4" aria-expanded="false" aria-controls="sv-a4"><span class="sv-qn">04</span><span class="sv-qt">What stage should we be at?</span><span class="sv-ic" aria-hidden="true"></span></button></h3>
<div class="sv-a" id="sv-a4" role="region" aria-labelledby="sv-q4" hidden><div class="sv-ai"><p>Somewhere between <b>"it works on the bench"</b> and <b>"it works for one customer"</b>. Usually pre-seed, sometimes early seed. Still choosing which problem to solve? Come back in six months. Holding a Series A term sheet? You probably don't need us, but we'll happily buy the coffee.</p></div></div>
</div>
<div class="sv-row" role="listitem">
<h3 class="sv-qh"><button class="sv-q" type="button" id="sv-q5" aria-expanded="false" aria-controls="sv-a5"><span class="sv-qn">05</span><span class="sv-qt">Do you invest in software-only companies?</span><span class="sv-ic" aria-hidden="true"></span></button></h3>
<div class="sv-a" id="sv-a5" role="region" aria-labelledby="sv-q5" hidden><div class="sv-ai"><p>Rarely. The exception is <b>edge AI that lives or dies on its hardware</b>: models squeezed onto a sensor, a drone or a robot arm, where the silicon and the software get designed together. If your product ships as an app-store link, we are the wrong soil.</p></div></div>
</div>
<div class="sv-row" role="listitem">
<h3 class="sv-qh"><button class="sv-q" type="button" id="sv-q6" aria-expanded="false" aria-controls="sv-a6"><span class="sv-qn">06</span><span class="sv-qt">What happens after Demo Day?</span><span class="sv-ic" aria-hidden="true"></span></button></h3>
<div class="sv-a" id="sv-a6" role="region" aria-labelledby="sv-q6" hidden><div class="sv-ai"><p>You keep your badge. Alumni keep <b>access to the shop</b> in Halle 3, and our investment program <b>Canopy</b> holds follow-on capital in reserve for residency companies. It seems to work: 74% of our teams raised a follow-on round within 18 months.</p></div></div>
</div>
<div class="sv-row" role="listitem">
<h3 class="sv-qh"><button class="sv-q" type="button" id="sv-q7" aria-expanded="false" aria-controls="sv-a7"><span class="sv-qn">07</span><span class="sv-qt">When is the next intake?</span><span class="sv-ic" aria-hidden="true"></span></button></h3>
<div class="sv-a" id="sv-a7" role="region" aria-labelledby="sv-q7" hidden><div class="sv-ai"><p>The <b>Autumn cohort</b>. Applications close on <b>30 Nov 2026</b>. Every team hears back from a human, and shortlisted teams spend a day with us in Halle 3 before either side decides anything.</p></div></div>
</div>
</div>
</div>
</div>
<script>
(function(){
const SP=window.SP, root=document.getElementById('voices');
if(!root||!SP)return;
const R=SP.reduced;
/* ---------- heading ---------- */
SP.rise(root.querySelector('.sv-title'));
SP.rise(root.querySelector('.sv-fh'));
/* ---------- testimonial stage ---------- */
const stage=root.querySelector('.sv-stage');
const slides=[...root.querySelectorAll('.sv-slide')];
const imgs=[...root.querySelectorAll('.sv-pic img')];
const fills=[...root.querySelectorAll('.sv-seg i')];
const curEl=root.querySelector('.sv-cur'), stateEl=root.querySelector('.sv-state');
const tagN=root.querySelector('.sv-tagn'), capL=root.querySelector('.sv-capl'), capR=root.querySelector('.sv-capr');
const live=root.querySelector('.sv-sr');
const N=slides.length, DUR=7;
let cur=0, tl=null, timer=null;
// split each slide's quote + name into words once
const words=slides.map(s=>({q:SP.split(s.querySelector('.sv-quote')),n:SP.split(s.querySelector('.sv-name')),
m:[...s.querySelectorAll('.sv-role,.sv-co')]}));
// 8x8 white cell grid over the portrait
const G=8, cellBox=root.querySelector('.sv-cells'), cells=[];
for(let r=0;r<G;r++)for(let c=0;c<G;c++){
const d=document.createElement('span');d.className='sv-cell';
d.style.left=(c*100/G)+'%';d.style.top=(r*100/G)+'%';
d._r=r;d._c=c;d._j=Math.random();cellBox.appendChild(d);cells.push(d);
}
// diagonal wave with a little jitter; dir flips the corner it starts from
const order=dir=>(i)=>{const c=cells[i];const k=dir>0?(c._r+c._c):((G-1-c._r)+(G-1-c._c));return k*.032+c._j*.14};
function setMeta(i){
const s=slides[i];
const lbl='Fig. 05.'+(i+1);
SP.scramble(curEl,{text:String(i+1).padStart(2,'0'),duration:.4});
SP.scramble(capL,{text:lbl,duration:.5});SP.scramble(capR,{text:s.dataset.meta,duration:.6});
}
function paint(i){ // progress segments: done / current / todo
fills.forEach((f,k)=>gsap.set(f,{scaleX:k<i?1:0}));
}
function show(i,dir,manual){
if(i===cur)return;
if(tl){const old=tl;tl=null;old.progress(1).kill()}
const from=cur, a=slides[from], b=slides[i], wa=words[from], wb=words[i];
cur=i;
a.setAttribute('aria-hidden','true');b.removeAttribute('aria-hidden');
if(manual)live.textContent='Founder '+(i+1)+' of '+N+': '+b.querySelector('.sv-name').textContent;
setMeta(i);
if(R){
a.classList.remove('is-on');b.classList.add('is-on');
imgs[from].classList.remove('is-on');imgs[i].classList.add('is-on');
tagN.textContent=b.dataset.co;
restart();return;
}
gsap.set(wb.q.concat(wb.n),{yPercent:110});gsap.set(wb.m,{autoAlpha:0,y:10});
tl=gsap.timeline({onComplete:()=>{tl=null}});
// words rise out
tl.to(wa.q,{yPercent:-110,duration:.42,ease:'power3.in',stagger:{amount:.22}},0)
.to(wa.n,{yPercent:-110,duration:.4,ease:'power3.in',stagger:.04},0.05)
.to(wa.m,{autoAlpha:0,y:-8,duration:.3,ease:'power2.in'},0)
.add(()=>{a.classList.remove('is-on');b.classList.add('is-on')},.66)
.add(()=>{gsap.set(wa.q.concat(wa.n),{yPercent:0});gsap.set(wa.m,{autoAlpha:1,y:0})},.67)
// words rise in
.to(wb.q,{yPercent:0,duration:1,ease:'expo.out',stagger:{amount:.38}},.68)
.to(wb.n,{yPercent:0,duration:.9,ease:'expo.out',stagger:.05},.72)
.to(wb.m,{autoAlpha:1,y:0,duration:.6,ease:'power3.out',stagger:.06},.9);
// pixel cover -> swap photo -> pixel uncover
tl.to(cells,{scale:1,duration:.26,ease:'power2.in',stagger:order(dir)},0)
.add(()=>{imgs[from].classList.remove('is-on');imgs[i].classList.add('is-on');tagN.textContent=b.dataset.co},.78)
.to(cells,{scale:0,duration:.34,ease:'power2.out',stagger:order(dir)},.8);
restart();
}
/* ---------- auto-advance with pause reasons ---------- */
const paused={hover:false,focus:false,view:true,user:R};
function isPaused(){return paused.hover||paused.focus||paused.view||paused.user}
function restart(){
if(timer){timer.kill();timer=null}
paint(cur);
if(R){gsap.set(fills[cur],{scaleX:1});stateEl.textContent='Manual';return}
timer=gsap.fromTo(fills[cur],{scaleX:0},{scaleX:1,duration:DUR,ease:'none',
onComplete:()=>{if(cur===N-1){show(0,1,false)}else show(cur+1,1,false)}});
sync();
}
function sync(){
if(!timer)return;
const p=isPaused();
p?timer.pause():timer.resume();
stateEl.textContent=paused.user?'Manual':(p?'Paused':'Auto');
}
function pause(key,v){paused[key]=v;sync()}
const go=d=>show((cur+d+N)%N,d,true);
root.querySelector('.sv-nav--prev').addEventListener('click',()=>go(-1));
root.querySelector('.sv-nav--next').addEventListener('click',()=>go(1));
stage.addEventListener('keydown',e=>{
if(e.key==='ArrowRight'){e.preventDefault();go(1)}
else if(e.key==='ArrowLeft'){e.preventDefault();go(-1)}
});
stage.addEventListener('pointerenter',e=>{if(e.pointerType==='mouse')pause('hover',true)});
stage.addEventListener('pointerleave',e=>{if(e.pointerType==='mouse')pause('hover',false)});
stage.addEventListener('focusin',e=>{if(e.target.matches(':focus-visible'))pause('focus',true)});
stage.addEventListener('focusout',e=>{if(!stage.contains(e.relatedTarget))pause('focus',false)});
// swipe
let sx=0,sy=0,st=0;
stage.addEventListener('touchstart',e=>{const t=e.touches[0];sx=t.clientX;sy=t.clientY;st=Date.now()},{passive:true});
stage.addEventListener('touchend',e=>{
const t=e.changedTouches[0],dx=t.clientX-sx,dy=t.clientY-sy;
if(Math.abs(dx)>44&&Math.abs(dx)>Math.abs(dy)*1.3&&Date.now()-st<800)go(dx<0?1:-1);
},{passive:true});
// only tick while the stage is on screen
ScrollTrigger.create({trigger:stage,start:'top 85%',end:'bottom 10%',
onToggle:self=>pause('view',!self.isActive)});
// first entrance: portrait assembles out of white cells
if(!R){
gsap.set(cells,{scale:1});
ScrollTrigger.create({trigger:stage,start:'top 75%',once:true,
onEnter:()=>gsap.to(cells,{scale:0,duration:.4,ease:'power2.out',stagger:order(1)})});
}
restart();
/* ---------- FAQ accordion ---------- */
const qs=[...root.querySelectorAll('.sv-q')];
let refreshT=0;
const refresh=()=>{clearTimeout(refreshT);refreshT=setTimeout(()=>ScrollTrigger.refresh(),60)};
function setOpen(btn,open){
const panel=document.getElementById(btn.getAttribute('aria-controls'));
if((btn.getAttribute('aria-expanded')==='true')===open)return;
btn.setAttribute('aria-expanded',String(open));
gsap.killTweensOf(panel);
if(open){
panel.hidden=false;
if(R){gsap.set(panel,{height:'auto'});refresh();return}
gsap.fromTo(panel,{height:0},{height:'auto',duration:.65,ease:'expo.out',onComplete:refresh});
gsap.fromTo(panel.firstElementChild,{y:14,autoAlpha:0},{y:0,autoAlpha:1,duration:.6,ease:'power3.out',delay:.08});
}else{
if(R){panel.hidden=true;refresh();return}
gsap.to(panel,{height:0,duration:.45,ease:'power3.inOut',onComplete:()=>{panel.hidden=true;gsap.set(panel,{clearProps:'height'});refresh()}});
}
}
qs.forEach(btn=>btn.addEventListener('click',()=>{
const willOpen=btn.getAttribute('aria-expanded')!=='true';
if(willOpen)qs.forEach(o=>{if(o!==btn)setOpen(o,false)});
setOpen(btn,willOpen);
}));
if(!R){
gsap.from(root.querySelectorAll('.sv-row'),{y:24,autoAlpha:0,duration:.8,ease:'expo.out',stagger:.06,
scrollTrigger:{trigger:root.querySelector('.sv-list'),start:'top 85%',once:true}});
}
})();
</script>
</section>
</main>
<section id="apply" class="sf-apply" aria-labelledby="sf-title">
<style>
/* ================= 07 — APPLY ================= */
.sf-apply{background:var(--bg);padding-block:clamp(88px,11vw,168px) clamp(72px,9vw,128px);position:relative}
.sf-apply__top{display:flex;justify-content:space-between;align-items:center;gap:16px;flex-wrap:wrap;padding-bottom:18px;border-bottom:1px solid var(--line);margin-bottom:clamp(28px,4vw,56px)}
.sf-apply__top .mono{color:var(--mute)}
.sf-apply__grid{display:grid;grid-template-columns:minmax(0,1fr);gap:clamp(40px,5vw,88px)}
.sf-apply__left{position:relative;min-width:0}
.sf-apply__h{font-size:clamp(104px,15.5vw,248px);position:relative;z-index:2;margin-bottom:-.2em;pointer-events:none}
.sf-apply__h .sf-dot{display:inline-block;width:.13em;height:.13em;background:var(--spore);margin-left:.04em;vertical-align:baseline}
/* specimen frame */
.sf-fig{margin:0;border:1px solid var(--line);padding:clamp(8px,1vw,14px);background:var(--bg)}
.sf-fig__clip{position:relative;overflow:hidden;aspect-ratio:3/2;background:var(--fog)}
.sf-fig__clip img{width:100%;height:100%;object-fit:cover;transform-origin:50% 45%}
.sf-fig__cap{display:flex;justify-content:space-between;gap:12px;padding-top:12px;color:var(--mute)}
.sf-fig__cap b{font-weight:500;color:var(--ink)}
.sf-fig__mark{position:absolute;right:14px;bottom:14px;display:flex;align-items:center;gap:8px;background:var(--bg);padding:6px 9px;font-family:var(--mono);font-size:11px;letter-spacing:.06em;text-transform:uppercase;border:1px solid var(--line)}
.sf-fig__mark i{width:7px;height:7px;background:var(--spore);display:block}
/* right column */
.sf-apply__right{min-width:0;container-type:inline-size;display:flex;flex-direction:column;gap:clamp(28px,3vw,40px)}
.sf-apply__lede{font-size:clamp(17px,1.35vw,19px);max-width:34ch;margin:14px 0 0;color:var(--ink)}
.sf-apply__close{display:block;margin-top:6px}
/* countdown */
.sf-cd{--sf-cdf:clamp(24px,calc((100cqi - 84px) / 9.6),52px);display:flex;align-items:flex-start;gap:10px;padding:18px 0;border-top:1px solid var(--ink);border-bottom:1px solid var(--line)}
.sf-cd__g{display:flex;flex-direction:column;gap:8px}
.sf-cd__n{display:flex;gap:3px}
.sf-cd__d{display:block;overflow:hidden;background:var(--fog);width:.95em;text-align:center;font-family:var(--mono);font-weight:500;font-size:var(--sf-cdf);line-height:1.3;font-variant-numeric:tabular-nums}
.sf-cd__d>span{display:block}
.sf-cd__sep{font-family:var(--mono);font-size:var(--sf-cdf);width:.6em;text-align:center;line-height:1.3;color:var(--line)}
.sf-cd__l{font-family:var(--mono);font-size:11px;letter-spacing:.1em;text-transform:uppercase;color:var(--mute)}
.sf-cd.is-closed .sf-cd__d{background:var(--bg);box-shadow:inset 0 0 0 1px var(--line)}
.sf-sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
/* form */
.sf-formwrap{position:relative}
.sf-form{display:grid;grid-template-columns:minmax(0,1fr);column-gap:28px;row-gap:6px}
.sf-field{position:relative;padding-top:20px;min-width:0}
.sf-field input,.sf-field select,.sf-field textarea{display:block;width:100%;margin:0;border:0;border-bottom:1px solid var(--line);border-radius:0;background:transparent;
padding:10px 0 11px;font-size:17px;line-height:1.4;outline:none;-webkit-appearance:none;appearance:none;transition:border-color .3s}
.sf-field textarea{resize:none;min-height:50px;overflow:hidden}
.sf-field select{padding-right:28px;cursor:pointer}
.sf-field select:invalid{color:var(--mute)}
.sf-field label{position:absolute;left:0;top:13px;font-family:var(--mono);font-size:13px;letter-spacing:.05em;text-transform:uppercase;color:var(--mute);
transform-origin:0 0;transition:transform .4s var(--ease),color .3s;pointer-events:none;white-space:nowrap;max-width:100%;overflow:hidden;text-overflow:ellipsis}
.sf-field:focus-within label,.sf-field.is-filled label,.sf-field--sel label,
.sf-field input:not(:placeholder-shown)~label,.sf-field textarea:not(:placeholder-shown)~label{transform:translateY(-30px) scale(.82);color:var(--ink)}
.sf-field--sel label{transform:translateY(-30px) scale(.82)}
.sf-field__bar{position:absolute;left:0;right:0;height:2px;background:var(--spore);transform:scaleX(0);transform-origin:0 50%;transition:transform .55s var(--ease)}
.sf-field__line{position:relative;display:block}
.sf-field__line .sf-field__bar{bottom:0}
.sf-field:focus-within .sf-field__bar{transform:scaleX(1)}
.sf-field :focus-visible{outline:none;box-shadow:none}
.sf-field__chev{position:absolute;right:2px;bottom:18px;width:12px;height:12px;pointer-events:none;transition:transform .35s var(--ease)}
.sf-field:focus-within .sf-field__chev{transform:rotate(180deg)}
.sf-field__err{display:flex;align-items:center;gap:8px;min-height:22px;padding-top:6px;font-family:var(--mono);font-size:11px;letter-spacing:.04em;color:var(--ink)}
.sf-field.is-err input,.sf-field.is-err select,.sf-field.is-err textarea{border-bottom-color:var(--ink)}
.sf-field.is-err .sf-field__err::before{content:"";width:6px;height:6px;flex:none;background:var(--ink)}
.sf-field.is-err label{color:var(--ink)}
.sf-form__foot{display:flex;flex-wrap:wrap;align-items:center;gap:14px 22px;padding-top:14px}
.sf-form__note{color:var(--mute);font-size:12px;max-width:28ch}
.sf-form .btn[disabled]{pointer-events:none}
.sf-form[hidden]{display:none}
.sf-done h3:focus{outline:none;box-shadow:none}
/* success state */
.sf-done{padding:8px 0 0}
.sf-done[hidden]{display:none}
.sf-done__row{display:flex;align-items:flex-end;gap:clamp(18px,3vw,32px);border-top:1px solid var(--ink);padding-top:22px}
.sf-done svg{width:clamp(92px,10vw,124px);height:auto;flex:none;overflow:visible}
.sf-done svg path,.sf-done svg line{fill:none;stroke:var(--ink);stroke-width:1.6;stroke-linecap:square;stroke-linejoin:miter}
.sf-done svg .sf-g{stroke:var(--line)}
.sf-done svg .sf-leaf{fill:var(--spore);fill-opacity:1}
.sf-done h3{font-family:var(--disp);font-stretch:62%;font-weight:800;text-transform:uppercase;line-height:.86;font-size:clamp(48px,5vw,76px)}
.sf-done p{margin:14px 0 0;font-size:18px;max-width:30ch}
.sf-done .mono{color:var(--mute);display:block;margin-top:18px}
@media (min-width:640px) and (max-width:899px),(min-width:1200px){
.sf-form{grid-template-columns:repeat(2,minmax(0,1fr))}
.sf-field--wide{grid-column:1/-1}
}
@media (min-width:900px){
.sf-apply__grid{grid-template-columns:minmax(0,7fr) minmax(0,5fr);align-items:end}
}
@media (max-width:520px){
.sf-fig__mark{display:none}
.sf-cd__sep{display:none}
.sf-cd{justify-content:space-between}
}
</style>
<div class="wrap">
<div class="sf-apply__top">
<span class="kick">007 — Apply</span>
<span class="mono">Germinate · autumn cohort · 8 places</span>
</div>
<div class="sf-apply__grid">
<div class="sf-apply__left">
<h2 class="disp sf-apply__h" id="sf-title">Plant<br>yours<span class="sf-dot" aria-hidden="true"></span></h2>
<figure class="sf-fig ticks">
<div class="sf-fig__clip">
<img src="assets/seeds.jpg" width="2000" height="1333" loading="lazy" alt="A single dandelion seed head against a pale sky, every seed still attached.">
<span class="sf-fig__mark" aria-hidden="true"><i></i>Ready to disperse</span>
</div>
<figcaption class="sf-fig__cap mono"><b data-sp-scramble>Specimen 042 — yours?</b><span>Fig. 07 · 1:1</span></figcaption>
</figure>
</div>
<div class="sf-apply__right">
<div>
<span class="kick">Autumn cohort</span>
<span class="mono sf-apply__close" id="sf-close">Applications close 30 Nov 2026 · 23:59 CET</span>
<p class="sf-apply__lede">Eight teams. Sixteen weeks in Halle 3. A first cheque from €150k. Bring a prototype — or a very convincing sketch of one.</p>
</div>
<div class="sf-cd" id="sf-cd" aria-describedby="sf-close">
<span class="sf-sr" id="sf-cd-sr">Applications close on 30 November 2026.</span>
<div class="sf-cd__g" aria-hidden="true"><div class="sf-cd__n" data-u="d"></div><span class="sf-cd__l">Days</span></div>
<span class="sf-cd__sep" aria-hidden="true">:</span>
<div class="sf-cd__g" aria-hidden="true"><div class="sf-cd__n" data-u="h"></div><span class="sf-cd__l">Hrs</span></div>
<span class="sf-cd__sep" aria-hidden="true">:</span>
<div class="sf-cd__g" aria-hidden="true"><div class="sf-cd__n" data-u="m"></div><span class="sf-cd__l">Min</span></div>
<span class="sf-cd__sep" aria-hidden="true">:</span>
<div class="sf-cd__g" aria-hidden="true"><div class="sf-cd__n" data-u="s"></div><span class="sf-cd__l">Sec</span></div>
</div>
<div class="sf-formwrap">
<form class="sf-form" data-atom-form novalidate aria-label="Germinate application">
<div class="sf-field">
<span class="sf-field__line">
<input id="sf-team" name="team" type="text" required placeholder=" " autocomplete="organization" aria-describedby="sf-team-e" data-req="Your team needs a name — a working title is fine.">
<label for="sf-team">Team name</label>
<span class="sf-field__bar"></span>
</span>
<span class="sf-field__err" id="sf-team-e" aria-live="polite"></span>
</div>
<div class="sf-field">
<span class="sf-field__line">
<input id="sf-mail" name="email" type="email" required placeholder=" " autocomplete="email" inputmode="email" aria-describedby="sf-mail-e" data-req="We need somewhere to send the reply.">
<label for="sf-mail">Email</label>
<span class="sf-field__bar"></span>
</span>
<span class="sf-field__err" id="sf-mail-e" aria-live="polite"></span>
</div>
<div class="sf-field sf-field--wide sf-field--sel">
<span class="sf-field__line">
<select id="sf-sector" name="sector" required aria-describedby="sf-sector-e" data-req="Pick the closest fit — we'll sort out nuance later.">
<option value="" selected disabled>Choose one</option>
<option>Robotics</option>
<option>Climate hardware</option>
<option>Health devices</option>
<option>Edge AI</option>
</select>
<label for="sf-sector">Sector</label>
<svg class="sf-field__chev" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true"><path d="M1.5 4 6 8.5 10.5 4"/></svg>
<span class="sf-field__bar"></span>
</span>
<span class="sf-field__err" id="sf-sector-e" aria-live="polite"></span>
</div>
<div class="sf-field sf-field--wide">
<span class="sf-field__line">
<textarea id="sf-what" name="building" rows="1" required placeholder=" " maxlength="240" aria-describedby="sf-what-e" data-req="One sentence. The rest can wait for the call."></textarea>
<label for="sf-what">What are you building? (one sentence)</label>
<span class="sf-field__bar"></span>
</span>
<span class="sf-field__err" id="sf-what-e" aria-live="polite"></span>
</div>
<div class="sf-form__foot sf-field--wide">
<button class="btn" type="submit">Send application <svg class="arr" viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true"><path d="M2 12 12 2M4 2h8v8"/></svg></button>
<span class="mono sf-form__note">Four fields. No deck needed at this stage.</span>
</div>
</form>
<div class="sf-done" hidden>
<div class="sf-done__row">
<svg viewBox="0 0 120 120" role="img" aria-label="A seed sprouting">
<line class="sf-g" x1="6" y1="92" x2="114" y2="92"/>
<path class="sf-s" d="M52 97c0-3.6 3.6-6 8-6s8 2.4 8 6-3.6 6-8 6-8-2.4-8-6z"/>
<path class="sf-s" d="M57 102c-2 6-6 10-11 13M63 102c1 6 4 10 9 12M60 103v13"/>
<path class="sf-s" d="M60 91C60 79 58 67 61 50"/>
<path class="sf-s sf-leaf" d="M60 66C49 66 39 60 35 48c11 0 21 5 25 18z"/>
<path class="sf-s sf-leaf" d="M61 57c4-13 14-19 27-19-2 13-13 19-27 19z"/>
</svg>
<h3 tabindex="-1">Received.</h3>
</div>
<p>We read every one — expect a reply within 10 days.</p>
<span class="mono sf-done__ref">Ref. <span class="sf-ref">SPR-26A-0000</span> · kept on this page only</span>
</div>
</div>
</div>
</div>
</div>
</section>
<footer class="sf-foot">
<style>
/* ================= FOOTER ================= */
.sf-foot{background:var(--bg);padding-top:clamp(24px,3vw,40px);position:relative}
.sf-cols{display:grid;grid-template-columns:minmax(0,1fr);gap:1px;background:var(--line);border:1px solid var(--line)}
.sf-col{background:var(--bg);padding:clamp(22px,2.4vw,34px) clamp(18px,2vw,28px) clamp(26px,2.8vw,40px);min-width:0}
.sf-col__t{font-family:var(--mono);font-size:12px;font-weight:500;letter-spacing:.08em;text-transform:uppercase;color:var(--mute);margin-bottom:20px}
.sf-col p{margin:0 0 18px;max-width:34ch}
.sf-col address{font-style:normal}
.sf-col address span{display:block}
.sf-mail{font-size:17px;text-decoration:none;border-bottom:1px solid var(--ink);padding-bottom:2px;transition:border-color .3s}
.sf-mail:hover{border-color:var(--spore)}
.sf-live{display:inline-flex;align-items:center;gap:10px;margin-top:22px;color:var(--mute)}
.sf-live__dot{width:8px;height:8px;border-radius:50%;background:var(--spore);flex:none;position:relative}
.sf-live__dot::after{content:"";position:absolute;inset:-4px;border-radius:50%;border:1px solid var(--spore);opacity:0}
@media (min-width: 0px){
.sf-live__dot{animation:sf-blink 1.6s steps(1,end) infinite}
.sf-live__dot::after{animation:sf-ping 1.6s var(--ease) infinite}
}
@keyframes sf-blink{0%{opacity:1}70%{opacity:.25}}
@keyframes sf-ping{0%{transform:scale(.4);opacity:.9}70%,100%{transform:scale(1.6);opacity:0}}
.sf-list{list-style:none;margin:0;padding:0;display:grid;gap:10px}
.sf-link{display:inline-flex;align-items:center;font-size:clamp(18px,1.5vw,22px);text-decoration:none;line-height:1.25}
.sf-link::before{content:"";width:0;height:9px;margin-right:0;background:var(--spore);flex:none;transition:width .4s var(--ease),margin .4s var(--ease)}
.sf-link:hover::before,.sf-link:focus-visible::before{width:9px;margin-right:10px}
.sf-link small{font-family:var(--mono);font-size:11px;color:var(--mute);margin-left:10px;letter-spacing:.04em}
.sf-news{margin-top:4px}
.sf-news__row{display:flex;align-items:stretch;border-bottom:1px solid var(--ink);position:relative}
.sf-news__row::after{content:"";position:absolute;left:0;right:48px;bottom:-1px;height:2px;background:var(--spore);transform:scaleX(0);transform-origin:0 50%;transition:transform .55s var(--ease)}
.sf-news__row:focus-within::after{transform:scaleX(1)}
.sf-news input{flex:1;min-width:0;border:0;border-radius:0;background:transparent;padding:12px 12px 12px 0;font-size:16px;outline:none}
.sf-news input::placeholder{color:var(--mute);opacity:1}
.sf-news input:focus-visible{outline:none;box-shadow:none}
.sf-news__btn{width:48px;height:48px;flex:none;background:var(--spore);color:var(--ink);display:grid;place-items:center;position:relative;overflow:hidden;isolation:isolate;transition:color .35s var(--ease)}
.sf-news__btn::after{content:"";position:absolute;inset:0;background:var(--ink);transform:translateY(101%);transition:transform .45s var(--ease);z-index:-1}
.sf-news__btn:hover{color:var(--bg)}
.sf-news__btn:hover::after{transform:none}
.sf-news__btn svg{width:16px;height:16px;transition:transform .45s var(--ease)}
.sf-news__btn:hover svg{transform:translate(2px,-2px)}
.sf-news__msg{display:flex;align-items:center;gap:8px;min-height:22px;margin-top:10px;font-family:var(--mono);font-size:11px;letter-spacing:.04em;color:var(--mute)}
.sf-news__msg.is-ok::before{content:"";width:7px;height:7px;background:var(--spore);flex:none}
.sf-news__msg.is-err{color:var(--ink)}
.sf-news__msg.is-err::before{content:"";width:6px;height:6px;background:var(--ink);flex:none}
/* giant wordmark */
.sf-mark{position:relative;overflow-x:clip;padding:clamp(28px,4vw,64px) 0 clamp(8px,1vw,16px);margin:0;user-select:none}
.sf-mark__line{display:flex;width:max-content;white-space:nowrap;font-family:var(--disp);font-weight:800;text-transform:uppercase;line-height:.8;letter-spacing:0;
font-size:calc((min(100vw,1440px) - 2 * var(--gut) - 20px) * .205);transform-origin:0 50%;padding-top:.04em}
.sf-mark__c{display:block;font-variation-settings:"wdth" 62,"wght" 800;font-stretch:62%}
.sf-mark__c>span{display:block;will-change:transform}
.sf-mark__c--sl{color:var(--spore)}
/* bottom bar */
.sf-bar{display:flex;flex-wrap:wrap;justify-content:space-between;align-items:center;gap:12px 28px;border-top:1px solid var(--line);padding:18px 0 24px;margin-top:clamp(10px,1.4vw,18px);color:var(--mute)}
.sf-bar__l{display:flex;flex-wrap:wrap;gap:8px 22px;align-items:center}
.sf-bar a{text-decoration:none;transition:color .25s}
.sf-bar a:hover{color:var(--ink)}
.sf-top{display:inline-flex;align-items:center;gap:10px;font-family:var(--mono);font-size:12px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;color:var(--ink);
height:40px;padding:0 14px;box-shadow:inset 0 0 0 1px var(--ink);position:relative;overflow:hidden;isolation:isolate}
.sf-top::after{content:"";position:absolute;inset:0;background:var(--spore);transform:translateY(101%);transition:transform .45s var(--ease);z-index:-1}
.sf-top:hover::after{transform:none}
.sf-top span{display:inline-block;transition:transform .45s var(--ease)}
.sf-top:hover span{transform:translateY(-3px)}
@media (min-width:600px){
.sf-cols{grid-template-columns:repeat(2,minmax(0,1fr))}
}
@media (min-width:1024px){
.sf-cols{grid-template-columns:1.35fr 1fr 1fr 1.35fr}
}
@media (max-width:599px){
.sf-cols{grid-template-columns:repeat(2,minmax(0,1fr))}
.sf-col--about,.sf-col--news{grid-column:1/-1}
.sf-bar{flex-direction:column;align-items:flex-start}
}
</style>
<div class="wrap">
<div class="sf-cols">
<div class="sf-col sf-col--about">
<h2 class="sf-col__t">Spore Labs</h2>
<p>A venture studio for hard-tech founders. We fund, house and grow companies from first prototype to first purchase order.</p>
<address>
<span>Halle 3 · Berlin-Wedding</span>
<span class="mono" style="color:var(--mute)">Former tram depot · 2,400 m²</span>
</address>
<p style="margin:18px 0 0"><a class="sf-mail" href="mailto:[email protected]">[email protected]</a></p>
<span class="sf-live mono"><span class="sf-live__dot" aria-hidden="true"></span>Studio open · Mon–Fri 08–20</span>
</div>
<nav class="sf-col" aria-label="Programs">
<h2 class="sf-col__t">Programs</h2>
<ul class="sf-list">
<li><a class="sf-link" href="#programs">Germinate<small>16 wks</small></a></li>
<li><a class="sf-link" href="#programs">Canopy<small>€150k+</small></a></li>
<li><a class="sf-link" href="#germination">Demo Day</a></li>
</ul>
</nav>
<nav class="sf-col" aria-label="Studio">
<h2 class="sf-col__t">Studio</h2>
<ul class="sf-list">
<li><a class="sf-link" href="#thesis">Thesis</a></li>
<li><a class="sf-link" href="#specimens">Portfolio</a></li>
<li><a class="sf-link" href="#germination">Process</a></li>
<li><a class="sf-link" href="#voices">Founders</a></li>
</ul>
</nav>
<div class="sf-col sf-col--news">
<h2 class="sf-col__t">Field notes, monthly</h2>
<p>What broke in the shop, what shipped, who's hiring. One email a month, no growth hacks.</p>
<form class="sf-news" data-atom-form novalidate aria-label="Newsletter sign-up">
<label class="sf-sr" for="sf-news-mail">Email for field notes</label>
<div class="sf-news__row">
<input id="sf-news-mail" name="email" type="email" required placeholder="[email protected]" autocomplete="email" aria-describedby="sf-news-msg">
<button class="sf-news__btn" type="submit" aria-label="Subscribe"><svg viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true"><path d="M2 12 12 2M4 2h8v8"/></svg></button>
</div>
<span class="sf-news__msg" id="sf-news-msg" aria-live="polite"></span>
</form>
</div>
</div>
<p class="sf-mark">
<span class="sf-sr">Spore Labs</span>
<span class="sf-mark__line" aria-hidden="true"><span class="sf-mark__c"><span>S</span></span><span class="sf-mark__c"><span>P</span></span><span class="sf-mark__c"><span>O</span></span><span class="sf-mark__c"><span>R</span></span><span class="sf-mark__c"><span>E</span></span><span class="sf-mark__c sf-mark__c--sl"><span>/</span></span><span class="sf-mark__c"><span>L</span></span><span class="sf-mark__c"><span>A</span></span><span class="sf-mark__c"><span>B</span></span><span class="sf-mark__c"><span>S</span></span></span>
</p>
<div class="sf-bar mono">
<div class="sf-bar__l">
<span>© 2026 Spore Labs GmbH</span>
<a href="#" data-sf-legal>Imprint</a>
<a href="#" data-sf-legal>Privacy</a>
</div>
<button class="sf-top" type="button">Back to top <span aria-hidden="true">↑</span></button>
</div>
</div>
<script>
(function(){
const SP=window.SP||{reduced:true};
const R=!!SP.reduced;
const hasG=typeof gsap!=='undefined';
const apply=document.getElementById('apply');
const foot=document.querySelector('.sf-foot');
if(!apply||!foot)return;
/* ---------- heading + specimen frame ---------- */
const h=apply.querySelector('.sf-apply__h');
if(SP.rise&&h)SP.rise(h,{stagger:.09,duration:1.2});
const fig=apply.querySelector('.sf-fig');
const clip=apply.querySelector('.sf-fig__clip');
const img=clip&&clip.querySelector('img');
if(!R&&hasG&&clip&&img){
gsap.set(clip,{clipPath:'inset(16% 20% 16% 20%)'});
gsap.set(img,{scale:1.2});
gsap.timeline({scrollTrigger:{trigger:fig,start:'top 92%',end:'center 55%',scrub:.8}})
.to(clip,{clipPath:'inset(0% 0% 0% 0%)',ease:'none'},0)
.to(img,{scale:1,ease:'none'},0);
}
/* ---------- countdown ---------- */
const END=Date.parse('2026-11-30T23:59:00+01:00');
const cd=apply.querySelector('#sf-cd');
const units={};
cd.querySelectorAll('.sf-cd__n').forEach(n=>units[n.dataset.u]=n);
let cdSeen=false;
if('IntersectionObserver' in window){
new IntersectionObserver(es=>es.forEach(e=>cdSeen=e.isIntersecting)).observe(cd);
}
function flip(inner,ch){
clearInterval(inner._sfIv);
let n=0;
inner._sfIv=setInterval(()=>{
if(++n>3){clearInterval(inner._sfIv);inner.textContent=ch;return}
inner.textContent=(Math.random()*10)|0;
},45);
gsap.fromTo(inner,{yPercent:-65,opacity:0},{yPercent:0,opacity:1,duration:.42,ease:'expo.out',overwrite:true});
}
function setUnit(el,val,anim){
const str=String(val).padStart(2,'0');
while(el.children.length<str.length){
const c=document.createElement('span');c.className='sf-cd__d';
const i=document.createElement('span');i.textContent='0';i.dataset.v='';c.appendChild(i);el.prepend(c);
}
while(el.children.length>str.length)el.firstElementChild.remove();
[...el.children].forEach((cell,k)=>{
const inner=cell.firstElementChild,ch=str[k];
if(inner.dataset.v===ch)return;
inner.dataset.v=ch;
if(anim&&!R&&hasG)flip(inner,ch);else inner.textContent=ch;
});
}
function render(anim){
const ms=Math.max(0,END-Date.now());
const s=Math.floor(ms/1000);
setUnit(units.d,Math.floor(s/86400),anim);
setUnit(units.h,Math.floor(s%86400/3600),anim);
setUnit(units.m,Math.floor(s%3600/60),anim);
setUnit(units.s,s%60,anim);
if(ms<=0&&!cd.classList.contains('is-closed')){
cd.classList.add('is-closed');
const c=apply.querySelector('#sf-close');
if(c)c.textContent='Applications closed · spring cohort opens soon';
}
return ms;
}
render(false);
(function tick(){
const left=render(cdSeen);
if(left>0)setTimeout(tick,1000-(Date.now()%1000)+8);
})();
/* ---------- application form ---------- */
const EMAIL=/^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/;
const form=apply.querySelector('.sf-form');
const wrap=apply.querySelector('.sf-formwrap');
const done=apply.querySelector('.sf-done');
const fields=[...form.querySelectorAll('.sf-field')];
let tried=false;
function ctl(f){return f.querySelector('input,select,textarea')}
function syncFilled(f){f.classList.toggle('is-filled',ctl(f).value.trim()!=='')}
function check(f){
const el=ctl(f),v=el.value.trim(),err=f.querySelector('.sf-field__err');
let msg='';
if(el.required&&!v)msg=el.dataset.req||'Required.';
else if(el.type==='email'&&!EMAIL.test(v))msg='That address looks off — check the @ and the dot.';
f.classList.toggle('is-err',!!msg);
el.setAttribute('aria-invalid',msg?'true':'false');
err.textContent=msg;
return !msg;
}
function grow(t){t.style.height='auto';t.style.height=t.scrollHeight+'px'}
fields.forEach(f=>{
const el=ctl(f);
syncFilled(f);
const on=()=>{syncFilled(f);if(el.tagName==='TEXTAREA')grow(el);if(tried||f.classList.contains('is-err'))check(f)};
el.addEventListener('input',on);
el.addEventListener('change',on);
el.addEventListener('blur',()=>{if(el.value.trim())check(f)});
});
form.addEventListener('submit',e=>{
e.preventDefault(); /* nothing is sent anywhere */
tried=true;
const bad=fields.filter(f=>!check(f));
if(bad.length){
ctl(bad[0]).focus({preventScroll:false});
if(!R&&hasG)gsap.fromTo(bad.map(f=>f.querySelector('.sf-field__line')),{x:0},{keyframes:{x:[-6,5,-3,2,0]},duration:.4,ease:'none',stagger:.04});
return;
}
success();
});
function success(){
const btn=form.querySelector('button[type=submit]');
btn.disabled=true;
const ref='SPR-26A-'+String(1000+((Math.random()*9000)|0));
done.querySelector('.sf-ref').textContent=ref;
const paths=[...done.querySelectorAll('.sf-s,.sf-g')];
const heading=done.querySelector('h3');
if(R||!hasG){
form.hidden=true;form.reset();done.hidden=false;heading.focus({preventScroll:true});return;
}
const h0=wrap.offsetHeight;
wrap.style.height=h0+'px';wrap.style.overflow='hidden';
const kids=[...form.children];
gsap.timeline()
.to(kids,{y:-18,autoAlpha:0,duration:.45,ease:'power3.in',stagger:.06})
.add(()=>{
form.hidden=true;form.reset();fields.forEach(syncFilled);
done.hidden=false;
paths.forEach(p=>{const L=p.getTotalLength();p.style.strokeDasharray=L;p.style.strokeDashoffset=L});
gsap.set(done.querySelectorAll('.sf-leaf'),{fillOpacity:0});
gsap.set([heading,done.querySelector('p'),done.querySelector('.sf-done__ref')],{y:20,autoAlpha:0});
const h1=done.offsetHeight+8;
gsap.to(wrap,{height:h1,duration:.6,ease:'expo.out',onComplete:()=>{wrap.style.height='';wrap.style.overflow=''}});
heading.focus({preventScroll:true});
const tl=gsap.timeline({defaults:{ease:'power2.inOut'}});
tl.to(done.querySelector('.sf-g'),{strokeDashoffset:0,duration:.5})
.to(paths[1],{strokeDashoffset:0,duration:.5},'-=.2')
.to(paths[2],{strokeDashoffset:0,duration:.6},'-=.1')
.to(paths[3],{strokeDashoffset:0,duration:.7,ease:'power3.out'})
.to([paths[4],paths[5]],{strokeDashoffset:0,duration:.6,stagger:.15})
.to(done.querySelectorAll('.sf-leaf'),{fillOpacity:1,duration:.4,stagger:.1},'-=.2')
.to([heading,done.querySelector('p'),done.querySelector('.sf-done__ref')],{y:0,autoAlpha:1,duration:.8,ease:'expo.out',stagger:.1},.35)
.add(()=>{if(SP.scramble)SP.scramble(done.querySelector('.sf-ref'),{text:ref,duration:.7})},.7);
});
}
/* ---------- newsletter (no network) ---------- */
const news=foot.querySelector('.sf-news');
const nIn=news.querySelector('input'),nMsg=news.querySelector('.sf-news__msg');
news.addEventListener('submit',e=>{
e.preventDefault();
const v=nIn.value.trim();
nMsg.classList.remove('is-ok','is-err');
if(!EMAIL.test(v)){
nMsg.classList.add('is-err');
nMsg.textContent=v?'That address looks off.':'Add an email first.';
nIn.setAttribute('aria-invalid','true');
nIn.focus();
return;
}
nIn.setAttribute('aria-invalid','false');
nIn.value='';
nMsg.classList.add('is-ok');
nMsg.textContent="You're on the list. First notes land in October.";
if(!R&&SP.scramble)SP.scramble(nMsg,{text:nMsg.textContent,duration:.6});
});
/* ---------- legal placeholders + back to top ---------- */
foot.querySelectorAll('[data-sf-legal]').forEach(a=>a.addEventListener('click',e=>e.preventDefault()));
foot.querySelector('.sf-top').addEventListener('click',()=>{
if(SP.lenis)SP.lenis.scrollTo(0,{duration:1.8});
else window.scrollTo({top:0,behavior:R?'auto':'smooth'});
});
/* ---------- giant wordmark: fit, rise, proximity swell ---------- */
const mark=foot.querySelector('.sf-mark');
const line=mark.querySelector('.sf-mark__line');
const cells=[...line.querySelectorAll('.sf-mark__c')];
const W0=62,W1=100,G0=800,G1=900;
const cur=cells.map(()=>0),tgt=cells.map(()=>0);
let wRest=[],wMax=[],centers=[],avail=0,running=false,fitted=false;
function vary(c,t){c.style.fontVariationSettings='"wdth" '+(W0+(W1-W0)*t).toFixed(2)+',"wght" '+(G0+(G1-G0)*t).toFixed(1)}
function fit(){
line.style.transform='none';
line.style.fontSize='';
cells.forEach(c=>vary(c,0));
avail=mark.clientWidth;
let w=line.getBoundingClientRect().width;
if(!w||!avail)return;
const fs=parseFloat(getComputedStyle(line).fontSize)*avail/w;
line.style.fontSize=fs.toFixed(2)+'px';
wRest=cells.map(c=>c.getBoundingClientRect().width);
cells.forEach(c=>vary(c,1));
wMax=cells.map(c=>c.getBoundingClientRect().width);
const total=wRest.reduce((a,b)=>a+b,0);
let acc=0;centers=wRest.map(x=>{const m=(acc+x/2)/total;acc+=x;return m});
avail=total;
cells.forEach((c,i)=>vary(c,cur[i]));
apply_();
fitted=true;
}
function apply_(){
let sum=0;
cells.forEach((c,i)=>{vary(c,cur[i]);sum+=wRest[i]+(wMax[i]-wRest[i])*cur[i]});
line.style.transform=sum>0?'scaleX('+(avail/sum).toFixed(4)+')':'none';
}
function loop(){
let moving=false;
for(let i=0;i<cells.length;i++){
const d=tgt[i]-cur[i];
if(Math.abs(d)>.002){cur[i]+=d*.14;moving=true}else cur[i]=tgt[i];
}
apply_();
if(!moving){gsap.ticker.remove(loop);running=false}
}
function kick(){if(!running&&fitted){running=true;gsap.ticker.add(loop)}}
function aimAt(x,fy){
const rad=.17;
for(let i=0;i<cells.length;i++){
let t=Math.max(0,1-Math.abs(x-centers[i])/rad);
t=t*t*(3-2*t);
tgt[i]=t*fy;
}
kick();
}
if(document.fonts&&document.fonts.ready)document.fonts.ready.then(fit);else fit();
let rz;window.addEventListener('resize',()=>{clearTimeout(rz);rz=setTimeout(fit,120)});
if(!R&&hasG){
const inners=cells.map(c=>c.firstElementChild);
gsap.set(line,{clipPath:'inset(-20% -5% 0% -5%)'});
let pointerIn=false;
gsap.from(inners,{yPercent:105,duration:1.2,ease:'expo.out',stagger:.055,
scrollTrigger:{trigger:mark,start:'top 95%',once:true},
onComplete:()=>{
line.style.clipPath='none';
if(pointerIn)return;
const p={x:-.2};
gsap.to(p,{x:1.2,duration:1.9,ease:'power1.inOut',
onUpdate:()=>{if(!pointerIn)aimAt(p.x,.75)},
onComplete:()=>{if(!pointerIn){tgt.fill(0);kick()}}});
}});
if(matchMedia('(hover:hover)').matches){
foot.addEventListener('pointermove',e=>{
if(e.pointerType!=='mouse'||!fitted)return;
pointerIn=true;
const r=mark.getBoundingClientRect();
const x=(e.clientX-r.left)/r.width;
const cy=r.top+r.height*.55,dy=Math.abs(e.clientY-cy)/r.height;
const fy=Math.max(0,Math.min(1,1-(dy-.45)/1.4));
aimAt(x,fy);
});
foot.addEventListener('pointerleave',()=>{pointerIn=false;tgt.fill(0);kick()});
}
}
})();
</script>
</footer>
<script>window.addEventListener('load',()=>ScrollTrigger.refresh())</script>
</body>
</html>
```
## `_view.html`
```html
<!doctype html><html><head><meta name="viewport" content="width=device-width, initial-scale=1"><style>html,body{margin:0;height:100%;overflow:hidden;background:#fff}iframe{border:0;width:100%;height:100%;display:block}</style></head>
<body><iframe id="f" src="index.html"></iframe></body></html>
```
## `sections/01-hero.html`
```html
<div class="sh-load" aria-hidden="true">
<div class="sh-load__top"><span>Spore/Labs</span><span>Germinating — Halle 3, Berlin-Wedding</span></div>
<svg class="sh-load__mark" viewBox="0 0 32 32">
<g stroke="currentColor" stroke-width="1.6" stroke-linecap="square" fill="none">
<path d="M16 2.5V7.5M16 24.5V29.5M2.5 16H7.5M24.5 16H29.5M6.45 6.45 9.99 9.99M22.01 22.01 25.55 25.55M25.55 6.45 22.01 9.99M9.99 22.01 6.45 25.55"/>
</g>
<circle class="sh-load__dot" cx="16" cy="16" r="4.2"/>
</svg>
<div class="sh-load__bot">
<div class="sh-load__row"><span class="sh-load__num"><span class="sh-load__n">000</span><span class="sh-load__pc">%</span></span><span class="sh-load__cap">Counting seeds</span></div>
<div class="sh-load__line"><i></i></div>
</div>
</div>
<header class="sh-hdr">
<div class="sh-hdr__in">
<a class="sh-logo" href="#top" aria-label="Spore Labs, back to top">
<svg class="sh-logo__mark" viewBox="0 0 32 32" aria-hidden="true">
<g class="sh-logo__rays" stroke="currentColor" stroke-width="2.2" stroke-linecap="square" fill="none">
<path d="M16 2.5V7.5M16 24.5V29.5M2.5 16H7.5M24.5 16H29.5M6.45 6.45 9.99 9.99M22.01 22.01 25.55 25.55M25.55 6.45 22.01 9.99M9.99 22.01 6.45 25.55"/>
</g>
<circle class="sh-logo__dot" cx="16" cy="16" r="4.6"/>
</svg>
<span class="sh-logo__word">Spore<i>/</i>Labs</span>
</a>
<nav class="sh-nav" aria-label="Primary">
<a href="#thesis">Thesis</a>
<a href="#programs">Programs</a>
<a href="#specimens">Portfolio</a>
<a href="#germination">Process</a>
<a href="#voices">Founders</a>
</nav>
<a class="btn sh-hdr__cta" href="#apply"><span>Apply<span class="sh-hdr__long"> — autumn cohort</span></span><svg class="arr" viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true"><path d="M2 12 12 2M4 2h8v8"/></svg></a>
<button class="sh-burger" type="button" aria-expanded="false" aria-controls="sh-menu" aria-label="Open menu"><span></span><span></span></button>
</div>
</header>
<div class="sh-menu" id="sh-menu" aria-hidden="true">
<nav class="sh-menu__nav" aria-label="Menu">
<a href="#thesis"><span class="sh-menu__i">01</span><span class="sh-menu__t">Thesis</span></a>
<a href="#programs"><span class="sh-menu__i">02</span><span class="sh-menu__t">Programs</span></a>
<a href="#specimens"><span class="sh-menu__i">03</span><span class="sh-menu__t">Portfolio</span></a>
<a href="#germination"><span class="sh-menu__i">04</span><span class="sh-menu__t">Process</span></a>
<a href="#voices"><span class="sh-menu__i">05</span><span class="sh-menu__t">Founders</span></a>
</nav>
<div class="sh-menu__foot">
<a class="btn" href="#apply">Apply — autumn cohort<svg class="arr" viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true"><path d="M2 12 12 2M4 2h8v8"/></svg></a>
<p class="mono sh-menu__note">Applications close 30 Nov 2026<br><a href="mailto:[email protected]">[email protected]</a></p>
</div>
</div>
<section id="top" class="sh-hero" aria-labelledby="sh-title">
<h1 id="sh-title" class="sh-vh">Spore Labs — venture studio for hard-tech founders</h1>
<div class="sh-frame ticks" aria-hidden="true"></div>
<div class="sh-rule" aria-hidden="true"><i class="sh-soil"></i></div>
<div class="sh-words disp" aria-hidden="true">
<span class="sh-line sh-line--a"><span class="sh-line__in">We plant</span></span>
<span class="sh-line sh-line--b"><span class="sh-line__in">Hard ideas</span></span>
</div>
<canvas class="sh-cv sh-cv--a" aria-hidden="true"></canvas>
<canvas class="sh-cv sh-cv--b" aria-hidden="true"></canvas>
<div class="sh-zone" aria-hidden="true"></div>
<p class="sh-meta sh-meta--tl kick">N°041 / Venture studio</p>
<p class="sh-meta sh-meta--tr mono"><span>52.5446° N,</span> <span>13.3669° E</span></p>
<div class="sh-band">
<p class="sh-pitch">Money, a machine shop and 16 weeks of stubborn help<br class="sh-br"> for robotics, climate and health-hardware founders.</p>
<button class="sh-hold" type="button" aria-label="Press and hold to scatter the seeds. Space bar works too.">
<span class="sh-hold__box" aria-hidden="true"><i></i></span>
<span class="sh-hold__lab" aria-hidden="true">Hold to scatter</span>
<span class="sh-hold__bar" aria-hidden="true"><i></i></span>
<span class="sh-hold__pct" aria-hidden="true">00</span>
</button>
<a class="btn btn--ink sh-cta" href="#specimens">See the portfolio<svg class="arr" viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true"><path d="M2 12 12 2M4 2h8v8"/></svg></a>
</div>
<div class="sh-cur" aria-hidden="true"><i class="sh-cur__fill"></i><span class="sh-cur__t">Hold</span></div>
</section>
<style>
/* ================= header ================= */
.sh-hdr{position:fixed;top:0;left:0;right:0;height:var(--hdr);z-index:100;background:#fff;border-bottom:1px solid var(--line);
transition:transform .55s var(--ease),background-color .35s linear}
.sh-hdr.is-scrolled{background:rgba(255,255,255,.8);-webkit-backdrop-filter:blur(14px) saturate(1.3);backdrop-filter:blur(14px) saturate(1.3)}
.sh-hdr.is-hidden{transform:translateY(-101%)}
.sh-hdr.is-open{transform:none;background:#fff;-webkit-backdrop-filter:none;backdrop-filter:none}
.sh-hdr__in{height:100%;display:flex;align-items:center;gap:20px;padding-inline:var(--gut)}
.sh-logo{display:inline-flex;align-items:center;gap:10px;color:var(--ink);text-decoration:none;flex:none}
.sh-logo__mark{width:28px;height:28px;flex:none;overflow:visible}
.sh-logo__rays{transform-origin:16px 16px;transition:transform .9s var(--ease)}
.sh-logo:hover .sh-logo__rays{transform:rotate(45deg) scale(1.08)}
.sh-logo__dot{fill:var(--spore)}
.sh-logo__word{font-family:var(--disp);font-stretch:62%;font-weight:800;font-size:25px;line-height:1;letter-spacing:.005em;text-transform:uppercase}
.sh-logo__word i{font-style:normal;color:var(--mute);margin:0 .04em}
.sh-nav{margin-left:auto;display:flex;align-items:center}
.sh-nav a{position:relative;display:inline-block;padding:10px 13px;font-family:var(--mono);font-size:12px;font-weight:500;letter-spacing:.08em;
text-transform:uppercase;text-decoration:none;color:var(--ink);white-space:nowrap}
.sh-nav a::before{content:"";position:absolute;left:13px;right:13px;bottom:5px;height:1px;background:var(--ink);transform:scaleX(0);transform-origin:100% 50%;transition:transform .5s var(--ease)}
.sh-nav a::after{content:"";position:absolute;left:3px;top:50%;width:5px;height:5px;margin-top:-3px;background:var(--spore);transform:scale(0);transition:transform .35s var(--ease)}
.sh-nav a:hover::before{transform:scaleX(1);transform-origin:0 50%}
.sh-nav a:hover::after{transform:scale(1)}
.sh-hdr__cta.btn{height:40px;padding:0 16px;margin-left:10px;flex:none}
.sh-burger{display:none;position:relative;width:44px;height:44px;margin-left:auto;box-shadow:inset 0 0 0 1px var(--ink);flex:none}
.sh-burger span{position:absolute;left:12px;right:12px;top:50%;height:1.5px;margin-top:-.75px;background:var(--ink);transition:transform .5s var(--ease)}
.sh-burger span:first-child{transform:translateY(-4px)}
.sh-burger span:last-child{transform:translateY(4px)}
.sh-burger[aria-expanded="true"] span:first-child{transform:rotate(45deg)}
.sh-burger[aria-expanded="true"] span:last-child{transform:rotate(-45deg)}
@media (max-width:1180px){.sh-hdr__long{display:none}}
@media (max-width:899px){
.sh-nav,.sh-hdr__cta.btn{display:none}
.sh-burger{display:block}
.sh-logo__word{font-size:23px}
}
html.sh-lock{overflow:hidden}
/* mobile menu: full-screen white panel under the header */
.sh-menu{position:fixed;inset:0;z-index:99;background:#fff;padding:calc(var(--hdr) + 14px) var(--gut) 24px;display:flex;flex-direction:column;
visibility:hidden;clip-path:inset(0 0 100% 0);overflow-y:auto;overscroll-behavior:contain}
.sh-menu.is-open{visibility:visible}
.sh-menu__nav{display:flex;flex-direction:column}
.sh-menu__nav a{display:flex;align-items:flex-end;gap:14px;padding:12px 0 8px;border-top:1px solid var(--line);text-decoration:none;color:var(--ink);overflow:hidden}
.sh-menu__nav a:last-child{border-bottom:1px solid var(--line)}
.sh-menu__i{display:inline-block;width:26px;flex:none;font-family:var(--mono);font-size:12px;color:var(--mute);padding-bottom:.6em}
.sh-menu__t{display:inline-block;font-family:var(--disp);font-stretch:62%;font-weight:800;text-transform:uppercase;font-size:clamp(52px,15vw,112px);line-height:.86}
.sh-menu__nav a:active .sh-menu__t,.sh-menu__nav a:hover .sh-menu__t{color:var(--spore-deep)}
.sh-menu__foot{margin-top:auto;padding-top:28px;display:flex;flex-direction:column;align-items:flex-start;gap:16px}
.sh-menu__note{margin:0;color:var(--mute);line-height:1.6}
.sh-menu__note a{color:var(--ink)}
@media (min-width:900px){.sh-menu{display:none}}
/* ================= hero ================= */
.sh-hero{--sh-band:104px;position:relative;height:100vh;height:100svh;min-height:640px;overflow:hidden;background:var(--bg);color:var(--ink);isolation:isolate}
.sh-vh{position:absolute!important;width:1px;height:1px;margin:-1px;padding:0;border:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
.sh-frame{position:absolute;z-index:3;top:calc(var(--hdr) + 14px);bottom:14px;left:var(--gut);right:var(--gut);
border-left:1px solid var(--line);border-right:1px solid var(--line);pointer-events:none}
.sh-rule{position:absolute;z-index:3;left:0;right:0;bottom:var(--sh-band);height:1px;background:var(--line);pointer-events:none}
.sh-soil{position:absolute;left:50%;top:0;width:9px;height:9px;margin:-4px 0 0 -4.5px;background:var(--spore)}
.sh-words{position:absolute;z-index:1;left:0;right:0;top:calc(var(--hdr) + 14px);bottom:var(--sh-band);display:flex;flex-direction:column;
align-items:center;justify-content:center;font-size:var(--sh-fs,clamp(96px,19vw,340px));pointer-events:none;-webkit-user-select:none;user-select:none}
.sh-line{display:block;white-space:nowrap;line-height:.84;will-change:transform}
.sh-line--a{padding-right:.5em}
.sh-line--b{padding-left:.5em}
.sh-line__in{display:inline-block}
.sh-cv{position:absolute;inset:0;width:100%;height:100%;display:block;z-index:2;pointer-events:none}
.sh-cv--b{mix-blend-mode:difference}
.sh-zone{position:absolute;z-index:4;left:0;right:0;top:var(--hdr);bottom:var(--sh-band);
-webkit-touch-callout:none;-webkit-user-select:none;user-select:none;touch-action:pan-y;-webkit-tap-highlight-color:transparent}
.sh-meta{position:absolute;z-index:5;top:calc(var(--hdr) + 32px);margin:0}
.sh-meta--tl{left:calc(var(--gut) + 20px)}
.sh-meta--tr{right:calc(var(--gut) + 20px);text-align:right;font-size:12px;letter-spacing:.06em}
.sh-band{position:absolute;z-index:5;left:var(--gut);right:var(--gut);bottom:14px;height:calc(var(--sh-band) - 14px);
display:grid;grid-template-columns:1fr auto 1fr;align-items:center;gap:24px;padding:0 20px}
.sh-pitch{margin:0;font-size:15px;line-height:1.45;max-width:54ch}
.sh-cta{justify-self:end;grid-column:3}
/* hold-to-scatter control */
.sh-hold{display:inline-flex;align-items:center;gap:12px;height:44px;padding:0 6px;font-family:var(--mono);font-size:12px;font-weight:500;
letter-spacing:.08em;text-transform:uppercase;color:var(--ink);-webkit-touch-callout:none;-webkit-user-select:none;user-select:none;touch-action:none}
.sh-hold__box{position:relative;width:16px;height:16px;flex:none;overflow:hidden;box-shadow:inset 0 0 0 1px var(--ink)}
.sh-hold__box i{position:absolute;inset:0;background:var(--spore);transform:scaleY(0);transform-origin:50% 100%}
.sh-hold__lab{min-width:15.5ch;text-align:left}
.sh-hold__bar{position:relative;width:112px;height:8px;flex:none;box-shadow:inset 0 0 0 1px var(--ink)}
.sh-hold__bar i{position:absolute;inset:2px;background:var(--spore);transform:scaleX(0);transform-origin:0 50%}
.sh-hold__pct{width:2.2ch;text-align:right;color:var(--mute);font-variant-numeric:tabular-nums}
.sh-hero.is-holding .sh-hold__box{box-shadow:inset 0 0 0 1px var(--ink),0 0 0 3px var(--spore-soft)}
.sh-hero.sh-red .sh-hold,.sh-hero.sh-nogl .sh-hold,.sh-hero.sh-red .sh-cur,.sh-hero.sh-nogl .sh-cur{display:none}
/* pointer tag (fine pointers only) */
.sh-cur{position:absolute;z-index:6;left:0;top:0;width:64px;height:64px;margin:-32px 0 0 -32px;background:#fff;box-shadow:inset 0 0 0 1px var(--ink);
pointer-events:none;opacity:0;overflow:hidden;display:flex;align-items:flex-end;justify-content:center;padding-bottom:7px;
transition:opacity .25s linear,width .35s var(--ease),height .35s var(--ease),margin .35s var(--ease)}
.sh-cur.is-on{opacity:1}
.sh-cur.is-down{width:76px;height:76px;margin:-38px 0 0 -38px}
.sh-cur::before{content:"";position:absolute;left:50%;top:50%;width:5px;height:5px;margin:-2.5px 0 0 -2.5px;background:var(--ink);z-index:2}
.sh-cur__fill{position:absolute;inset:0;background:var(--spore);transform:scaleY(0);transform-origin:50% 100%}
.sh-cur__t{position:relative;z-index:2;font-family:var(--mono);font-size:10px;font-weight:600;letter-spacing:.1em;text-transform:uppercase}
@media (hover:hover) and (pointer:fine){.sh-hero.is-ready .sh-zone{cursor:none}}
@media (hover:none),(pointer:coarse){.sh-cur{display:none}}
/* intro loader */
.sh-load{position:fixed;inset:0;z-index:400;background:var(--bg);display:flex;flex-direction:column;justify-content:space-between;
padding:calc(var(--gut) + 4px) var(--gut);clip-path:inset(0 0 0 0);animation:sh-failsafe .01s 8s forwards}
.sh-load__top{display:flex;justify-content:space-between;gap:16px;font-family:var(--mono);font-size:12px;font-weight:500;letter-spacing:.08em;text-transform:uppercase}
.sh-load__mark{position:absolute;left:50%;top:50%;width:58px;height:58px;margin:-29px 0 0 -29px;animation:sh-spin 5s linear infinite}
.sh-load__row{display:flex;justify-content:space-between;align-items:flex-end;gap:16px}
.sh-load__num{font-family:var(--mono);font-weight:500;font-size:clamp(64px,11vw,168px);line-height:.8;letter-spacing:-.05em;font-variant-numeric:tabular-nums}
.sh-load__pc{font-size:.3em;letter-spacing:0;vertical-align:top;margin-left:.15em}
.sh-load__cap{font-family:var(--mono);font-size:12px;letter-spacing:.08em;text-transform:uppercase;color:var(--mute)}
.sh-load__line{position:relative;height:2px;margin-top:22px;background:var(--fog)}
.sh-load__dot{fill:var(--spore)}
.sh-load__line i{position:absolute;inset:0;background:var(--spore);transform:scaleX(0);transform-origin:0 50%}
@keyframes sh-spin{to{transform:rotate(360deg)}}
@keyframes sh-failsafe{to{visibility:hidden;pointer-events:none}}
@media (max-width:899px){
.sh-hero{--sh-band:178px;min-height:600px}
.sh-frame{top:calc(var(--hdr) + 10px);bottom:10px}
.sh-cv{z-index:0}
.sh-words{font-size:var(--sh-fs,22vw);top:calc(var(--hdr) + 60px);bottom:calc(var(--sh-band) + 58px)}
.sh-line--a{padding-right:.18em}
.sh-line--b{padding-left:.18em}
.sh-meta{top:calc(var(--hdr) + 24px)}
.sh-meta--tl{left:calc(var(--gut) + 12px)}
.sh-meta--tr{right:calc(var(--gut) + 12px);font-size:11px;line-height:1.4}
.sh-meta--tr span{display:block}
.sh-band{grid-template-columns:1fr;align-content:center;gap:16px;padding:0 12px;bottom:10px;height:calc(var(--sh-band) - 10px)}
.sh-pitch{font-size:14px}
.sh-br{display:none}
.sh-cta{justify-self:start;grid-column:auto}
.sh-hold{position:absolute;left:6px;bottom:calc(100% + 10px)}
.sh-hold__bar,.sh-hold__pct{display:none}
.sh-load__top span:last-child{display:none}
}
</style>
<script>
(function(){
'use strict';
var SP = window.SP || {};
var RED = !!SP.reduced;
var lenis = SP.lenis || null;
var $ = function(s, r){ return (r || document).querySelector(s); };
var $$ = function(s, r){ return Array.prototype.slice.call((r || document).querySelectorAll(s)); };
var hdr = $('.sh-hdr'), hdrIn = $('.sh-hdr__in'), burger = $('.sh-burger'), menu = $('#sh-menu');
var hero = $('#top'), words = $('.sh-words', hero), lineA = $('.sh-line--a', hero), lineB = $('.sh-line--b', hero);
var inA = $('.sh-line--a .sh-line__in', hero), inB = $('.sh-line--b .sh-line__in', hero);
var frame = $('.sh-frame', hero), rule = $('.sh-rule', hero), zone = $('.sh-zone', hero);
var cvA = $('.sh-cv--a', hero), cvB = $('.sh-cv--b', hero);
var holdBtn = $('.sh-hold', hero), holdLab = $('.sh-hold__lab', hero), holdSq = $('.sh-hold__box i', hero),
holdBar = $('.sh-hold__bar i', hero), holdPct = $('.sh-hold__pct', hero);
var cur = $('.sh-cur', hero), curFill = $('.sh-cur__fill', hero), curT = $('.sh-cur__t', hero);
var load = $('.sh-load');
if (RED) hero.classList.add('sh-red');
/* ================= header: hide on scroll down, show on scroll up ================= */
var menuOpen = false, lastY = window.scrollY;
function onScroll(){
var y = window.scrollY;
hdr.classList.toggle('is-scrolled', y > 8);
if (menuOpen){ lastY = y; return; }
if (y < 100){ hdr.classList.remove('is-hidden'); lastY = y; return; }
var dy = y - lastY;
if (Math.abs(dy) < 6) return;
hdr.classList.toggle('is-hidden', dy > 0);
lastY = y;
}
window.addEventListener('scroll', onScroll, { passive: true });
onScroll();
hdr.addEventListener('focusin', function(){ hdr.classList.remove('is-hidden'); });
$$('.sh-nav a', hdr).forEach(function(a){
var txt = a.textContent.trim(), busy = false;
a.addEventListener('pointerenter', function(){
if (RED || busy || !SP.scramble) return;
busy = true;
a.style.minWidth = a.offsetWidth + 'px';
SP.scramble(a, { text: txt, duration: .45 });
setTimeout(function(){ busy = false; a.textContent = txt; }, 540);
});
});
/* ================= mobile menu ================= */
var mLinks = $$('a', menu), mItems = $$('.sh-menu__t, .sh-menu__i', menu), mFoot = $('.sh-menu__foot', menu), mTl = null;
function setMenu(open){
if (open === menuOpen) return;
menuOpen = open;
burger.setAttribute('aria-expanded', open ? 'true' : 'false');
burger.setAttribute('aria-label', open ? 'Close menu' : 'Open menu');
menu.setAttribute('aria-hidden', open ? 'false' : 'true');
hdr.classList.toggle('is-open', open);
document.documentElement.classList.toggle('sh-lock', open);
if (lenis){ open ? lenis.stop() : lenis.start(); }
if (mTl) mTl.kill();
if (open){
hdr.classList.remove('is-hidden');
menu.classList.add('is-open');
if (RED){ gsap.set(menu, { clipPath: 'inset(0% 0% 0% 0%)' }); gsap.set(mItems, { yPercent: 0 }); gsap.set(mFoot, { autoAlpha: 1, y: 0 }); }
else {
mTl = gsap.timeline()
.fromTo(menu, { clipPath: 'inset(0% 0% 100% 0%)' }, { clipPath: 'inset(0% 0% 0% 0%)', duration: .75, ease: 'expo.inOut' })
.fromTo(mItems, { yPercent: 110 }, { yPercent: 0, duration: .95, ease: 'expo.out', stagger: .045 }, .3)
.fromTo(mFoot, { autoAlpha: 0, y: 18 }, { autoAlpha: 1, y: 0, duration: .7, ease: 'power3.out' }, .55);
}
setTimeout(function(){ if (menuOpen && mLinks[0]) mLinks[0].focus({ preventScroll: true }); }, RED ? 0 : 380);
} else {
var done = function(){ menu.classList.remove('is-open'); };
if (RED){ gsap.set(menu, { clipPath: 'inset(0% 0% 100% 0%)' }); done(); }
else mTl = gsap.timeline({ onComplete: done }).to(menu, { clipPath: 'inset(0% 0% 100% 0%)', duration: .6, ease: 'expo.inOut' });
}
}
burger.addEventListener('click', function(){ setMenu(!menuOpen); });
mLinks.forEach(function(a){ a.addEventListener('click', function(){ setMenu(false); }); });
document.addEventListener('keydown', function(e){
if (!menuOpen) return;
if (e.key === 'Escape'){ setMenu(false); burger.focus(); return; }
if (e.key === 'Tab'){
var f = [burger].concat(mLinks), i = f.indexOf(document.activeElement);
e.preventDefault();
var n = e.shiftKey ? (i <= 0 ? f.length - 1 : i - 1) : (i === -1 || i === f.length - 1 ? 0 : i + 1);
f[n].focus();
}
});
var mqDesk = matchMedia('(min-width: 900px)');
var onMq = function(e){ if (e.matches) setMenu(false); };
mqDesk.addEventListener ? mqDesk.addEventListener('change', onMq) : mqDesk.addListener(onMq);
/* ================= hero: word fitting ================= */
var WORDS = ['We plant', 'Hard ideas'], NEXT = ['Yours', 'Is next'];
function fitWords(){
words.style.removeProperty('--sh-fs');
var avail = frame.offsetWidth - (hero.clientWidth < 900 ? 8 : 48);
var mw = Math.max(lineA.offsetWidth, lineB.offsetWidth);
if (mw > avail){
var fs = parseFloat(getComputedStyle(words).fontSize);
words.style.setProperty('--sh-fs', (fs * avail / mw).toFixed(2) + 'px');
}
}
function swapWords(list, dur){
if (!SP.scramble){ inA.textContent = list[0]; inB.textContent = list[1]; return; }
SP.scramble(inA, { text: list[0], duration: dur || .7 });
SP.scramble(inB, { text: list[1], duration: dur || .7, delay: .12 });
}
/* ================= WebGL dandelion ================= */
var GL = false, rA = null, rB = null;
try {
rA = new THREE.WebGLRenderer({ canvas: cvA, alpha: true, antialias: true, powerPreference: 'high-performance' });
rB = new THREE.WebGLRenderer({ canvas: cvB, alpha: true, antialias: true, powerPreference: 'high-performance' });
rA.setClearColor(0xffffff, 0); rB.setClearColor(0xffffff, 0);
GL = true;
} catch (err){ GL = false; }
if (!GL) hero.classList.add('sh-nogl');
var MOB = matchMedia('(max-width: 899px)').matches;
var I = { stem: RED ? 1 : 0, head: RED ? 1 : 0 }; // intro drivers
var prox = { p: 0 }; // scroll progress (scrubbed)
var holding = false, hold = 0, scattered = false, scatterClock = 0, windAmt = 0, idle = 0, introDone = RED;
var visible = true, resetT = 0, labState = '';
var ptrX = 0, ptrY = 0;
var sim = null;
if (GL){ sim = buildSim(); }
function buildSim(){
var sd = 41;
function rnd(){ sd |= 0; sd = sd + 0x6D2B79F5 | 0; var t = Math.imul(sd ^ sd >>> 15, 1 | sd);
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t; return ((t ^ t >>> 14) >>> 0) / 4294967296; }
var K = MOB ? 10 : 12; // filaments per parachute
var R0 = 0.12; // core radius (head radius ~1)
var WANT = MOB ? 220 : 460;
var WX = .78, WY = .55, WZ = .3; // wind: up and to the right, a little toward camera
var wl = Math.hypot(WX, WY, WZ); WX /= wl; WY /= wl; WZ /= wl;
/* ---- Fibonacci sphere, bottom cap left bare for the stem ---- */
var dirs = [], M = Math.round(WANT / 0.94), GA = Math.PI * (3 - Math.sqrt(5));
for (var i = 0; i < M; i++){
var y = 1 - 2 * (i + .5) / M; if (y < -0.88) continue;
var r = Math.sqrt(1 - y * y), th = i * GA;
var x = r * Math.cos(th) + (rnd() - .5) * .03, z = r * Math.sin(th) + (rnd() - .5) * .03; y += (rnd() - .5) * .03;
var l = Math.hypot(x, y, z); dirs.push(x / l, y / l, z / l);
}
var N = dirs.length / 3, F = Math.round(N * 1.6), U = N + F;
var D3 = new Float32Array(dirs), E1 = new Float32Array(N * 3);
var LS = new Float32Array(N), PL = new Float32Array(N), OPEN = new Float32Array(N), TAU = new Float32Array(N),
FLEX = new Float32Array(N), PH = new Float32Array(N), ST = new Float32Array(N), SDIST = new Float32Array(N), WAVE = new Float32Array(N);
var GROW = new Float32Array(N), GDEL = new Float32Array(N), PRES = new Uint8Array(N);
var FTH = new Float32Array(N * K), FLN = new Float32Array(N * K);
for (i = 0; i < N; i++){
var dx = D3[i*3], dy = D3[i*3+1], dz = D3[i*3+2];
var ax = rnd() - .5, ay = rnd() - .5, az = rnd() - .5, dd = ax*dx + ay*dy + az*dz;
ax -= dx*dd; ay -= dy*dd; az -= dz*dd; l = Math.hypot(ax, ay, az) || 1;
E1[i*3] = ax / l; E1[i*3+1] = ay / l; E1[i*3+2] = az / l;
LS[i] = .6 + rnd() * .08; PL[i] = .29 + rnd() * .07; OPEN[i] = 1.2 + rnd() * .2;
TAU[i] = .35 + rnd() * .75; FLEX[i] = .6 + rnd() * .8; PH[i] = rnd() * 6.283;
ST[i] = rnd() < .3 ? .04 + rnd() * .62 : -1; SDIST[i] = 2.2 + rnd() * 3.2; WAVE[i] = rnd();
GROW[i] = RED ? 1 : 0; GDEL[i] = RED ? 0 : 1e9; PRES[i] = 1;
for (var k = 0; k < K; k++){ FTH[i*K+k] = k / K * 6.2832 + (rnd() - .5) * .38; FLN[i*K+k] = .8 + rnd() * .34; }
}
/* flyers (seeds that left the head) */
var FP = new Float32Array(F*3), FV = new Float32Array(F*3), FU = new Float32Array(F*3), FE = new Float32Array(F*3);
var FPHI = new Float32Array(F), FSP = new Float32Array(F), FLS = new Float32Array(F), FPL = new Float32Array(F),
FOP = new Float32Array(F), FAGE = new Float32Array(F), FSRC = new Uint32Array(F), FAL = new Uint8Array(F);
var fNext = 0;
/* ---- buffers: stalks (normal canvas) and parachutes (difference canvas) ---- */
var stPos = new Float32Array(U * 2 * 3), stCol = new Float32Array(U * 2 * 4), stHead = new Float32Array(U * 2);
var ppPos = new Float32Array(U * K * 2 * 3), ppCol = new Float32Array(U * K * 2 * 4), ppHead = new Float32Array(U * K * 2);
for (var u = 0; u < U; u++){
var o = u * 8;
stCol.set([.047, .051, .059, .92, .369, .388, .42, .42], o);
var am = .7 + rnd() * .3;
for (k = 0; k < K; k++){
var q = (u * K + k) * 8;
ppCol.set([1, 1, 1, .4 * am, 1, 1, 1, .035 * am], q);
ppHead[(u*K+k)*2] = ppHead[(u*K+k)*2+1] = u < N ? 1 : 0;
}
}
function lineGeo(pos, col, head){
var g = new THREE.BufferGeometry();
var pa = new THREE.BufferAttribute(pos, 3); pa.setUsage(THREE.DynamicDrawUsage);
g.setAttribute('position', pa);
g.setAttribute('aCol', new THREE.BufferAttribute(col, 4));
g.setAttribute('aHead', new THREE.BufferAttribute(head, 1));
return g;
}
var VS = [
'attribute vec4 aCol; attribute float aHead;',
'uniform float uCz; uniform float uCr;',
'varying vec4 vCol; varying float vHead;',
'void main(){',
' vec4 mv = modelViewMatrix * vec4(position, 1.0);',
' float k = clamp(0.5 + (uCz + mv.z) / (2.0 * uCr), 0.0, 1.0);',
' vCol = vec4(aCol.rgb, aCol.a * mix(0.3, 1.0, k));',
' vHead = aHead;',
' gl_Position = projectionMatrix * mv;',
'}'].join('\n');
var FS = [
'uniform vec3 uCore; uniform float uOp;',
'varying vec4 vCol; varying float vHead;',
'void main(){',
' float d = length(gl_FragCoord.xy - uCore.xy);',
' float m = mix(1.0, smoothstep(uCore.z * 0.8, uCore.z * 1.75, d), vHead);',
' gl_FragColor = vec4(vCol.rgb, vCol.a * m * uOp);',
'}'].join('\n');
function lineMat(){
return new THREE.ShaderMaterial({ vertexShader: VS, fragmentShader: FS, transparent: true, depthWrite: false,
uniforms: { uCz: { value: 8 }, uCr: { value: 1 }, uCore: { value: new THREE.Vector3(0, 0, 0) }, uOp: { value: 1 } } });
}
var stGeo = lineGeo(stPos, stCol, stHead), ppGeo = lineGeo(ppPos, ppCol, ppHead);
var stMat = lineMat(), ppMat = lineMat();
var stalks = new THREE.LineSegments(stGeo, stMat), paps = new THREE.LineSegments(ppGeo, ppMat);
stalks.frustumCulled = paps.frustumCulled = false;
stalks.renderOrder = 3;
/* ---- core: small pitted sphere, soft two-tone shading ---- */
var cg = new THREE.SphereGeometry(R0, MOB ? 64 : 96, MOB ? 48 : 72);
var cp = cg.attributes.position, v = new THREE.Vector3();
var sig2 = Math.pow(Math.sqrt(4 * Math.PI / N) * .34, 2);
for (var j = 0; j < cp.count; j++){
v.fromBufferAttribute(cp, j).normalize();
var best = -1;
for (i = 0; i < N; i++){ var dt = v.x*D3[i*3] + v.y*D3[i*3+1] + v.z*D3[i*3+2]; if (dt > best) best = dt; }
var pit = Math.exp(-2 * (1 - best) / sig2);
var lump = Math.sin(v.x * 7.1 + 1.3) * Math.sin(v.y * 8.3) * Math.sin(v.z * 6.7 + .4);
var rr = R0 * (1 + .05 * lump - .11 * pit);
cp.setXYZ(j, v.x * rr, v.y * rr, v.z * rr);
}
cg.computeVertexNormals();
var coreMat = new THREE.ShaderMaterial({
uniforms: { uA: { value: new THREE.Color(0x2EE06F) }, uB: { value: new THREE.Color(0x13A94C) } },
vertexShader: 'varying vec3 vN; varying vec3 vV; void main(){ vec4 mv = modelViewMatrix * vec4(position,1.0); vN = normalize(normalMatrix * normal); vV = normalize(-mv.xyz); gl_Position = projectionMatrix * mv; }',
fragmentShader: 'uniform vec3 uA; uniform vec3 uB; varying vec3 vN; varying vec3 vV; void main(){ vec3 n = normalize(vN); float l = clamp(dot(n, normalize(vec3(-.35,.8,.55))) * .5 + .5, 0.0, 1.0); float f = pow(1.0 - max(dot(n, vV), 0.0), 2.0); vec3 c = mix(uB, uA, smoothstep(.15, .95, l)); c = mix(c, uB, f * .55); gl_FragColor = vec4(c, 1.0); }'
});
var core = new THREE.Mesh(cg, coreMat);
var head = new THREE.Object3D(); head.add(core);
/* ---- stem: flat ribbon facing the camera ---- */
var SM = 48, stemPos = new Float32Array((SM + 1) * 2 * 3), idx = [];
for (i = 0; i < SM; i++){ var a = i * 2; idx.push(a, a + 1, a + 2, a + 1, a + 3, a + 2); }
var stemGeo = new THREE.BufferGeometry();
var sa = new THREE.BufferAttribute(stemPos, 3); sa.setUsage(THREE.DynamicDrawUsage);
stemGeo.setAttribute('position', sa); stemGeo.setIndex(idx);
var stem = new THREE.Mesh(stemGeo, new THREE.MeshBasicMaterial({ color: 0x0C0D0F, side: THREE.DoubleSide }));
stem.frustumCulled = false;
var sceneA = new THREE.Scene(), sceneB = new THREE.Scene();
sceneA.add(stem, head, stalks); sceneB.add(paps);
var cam = new THREE.PerspectiveCamera(30, 1, .05, 200);
var TAN = Math.tan(15 * Math.PI / 180);
var L = { W: 1, H: 1, dpr: 1, wpp: 1, Rpx: 200, D: 8, gy: -2, hy: 0, halfW: 3, halfH: 2 };
var t = 0, autoRot = .6, pxS = 0, pyS = 0, hp = new THREE.Vector3();
function layout(){
var W = hero.clientWidth, H = hero.clientHeight, dpr = Math.min(window.devicePixelRatio || 1, 1.75);
rA.setPixelRatio(dpr); rB.setPixelRatio(dpr); rA.setSize(W, H, false); rB.setSize(W, H, false);
var ruleY = rule.offsetTop, top = 64, area = Math.max(200, ruleY - top), Rpx, cy;
if (W < 900){ Rpx = Math.min(W * .4, area * .3); cy = top + area * .44; }
else { Rpx = Math.min(W * .19, area * .4); cy = top + area * .46; }
var wpp = 1 / Rpx;
cam.aspect = W / H; cam.position.set(0, 0, H * wpp / (2 * TAN)); cam.updateProjectionMatrix();
L.W = W; L.H = H; L.dpr = dpr; L.wpp = wpp; L.Rpx = Rpx; L.D = cam.position.z;
L.gy = (H / 2 - ruleY) * wpp; L.hy = (H / 2 - cy) * wpp; L.halfW = W / 2 * wpp; L.halfH = H / 2 * wpp;
}
function far(uIdx){
stPos.fill(0, uIdx * 6, uIdx * 6 + 6); stPos[uIdx * 6 + 2] = stPos[uIdx * 6 + 5] = -1e5;
var b = uIdx * K * 6;
for (var n = 0; n < K * 2; n++){ ppPos[b + n*3] = 0; ppPos[b + n*3 + 1] = 0; ppPos[b + n*3 + 2] = -1e5; }
}
for (u = 0; u < U; u++) far(u);
function writeUnit(uIdx, Cx, Cy, Cz, ux, uy, uz, ex, ey, ez, ls, pl, op, phi, src){
var o = uIdx * 6;
stPos[o] = Cx - ux * ls; stPos[o+1] = Cy - uy * ls; stPos[o+2] = Cz - uz * ls;
stPos[o+3] = Cx; stPos[o+4] = Cy; stPos[o+5] = Cz;
var fx = uy * ez - uz * ey, fy = uz * ex - ux * ez, fz = ux * ey - uy * ex; // e2 = u x e1
var ca = Math.cos(op), sa = Math.sin(op), b = uIdx * K * 6;
for (var k = 0; k < K; k++){
var th = FTH[src * K + k] + phi, c = Math.cos(th) * sa, s = Math.sin(th) * sa, len = pl * FLN[src * K + k];
var p = b + k * 6;
ppPos[p] = Cx; ppPos[p+1] = Cy; ppPos[p+2] = Cz;
ppPos[p+3] = Cx + (ux * ca + ex * c + fx * s) * len;
ppPos[p+4] = Cy + (uy * ca + ey * c + fy * s) * len;
ppPos[p+5] = Cz + (uz * ca + ez * c + fz * s) * len;
}
}
function spawn(Cx, Cy, Cz, ux, uy, uz, ex, ey, ez, ls, pl, op, phi, src, burst){
var f = fNext; fNext = (fNext + 1) % F;
FAL[f] = 1; FAGE[f] = 0; FSRC[f] = src; FLS[f] = ls; FPL[f] = pl; FOP[f] = op; FPHI[f] = phi;
FSP[f] = (rnd() - .5) * 3.4;
FP[f*3] = Cx; FP[f*3+1] = Cy; FP[f*3+2] = Cz;
FU[f*3] = ux; FU[f*3+1] = uy; FU[f*3+2] = uz;
FE[f*3] = ex; FE[f*3+1] = ey; FE[f*3+2] = ez;
var kk = .35 + .55 * rnd() + burst, out = .5 + burst * .4;
FV[f*3] = ux * out + WX * kk + (rnd() - .5) * .45;
FV[f*3+1] = uy * out + WY * kk + (rnd() - .5) * .45;
FV[f*3+2] = uz * out + WZ * kk + (rnd() - .5) * .3;
}
function regrow(all, base){
for (var i = 0; i < N; i++){
if (!all && PRES[i]) continue;
PRES[i] = 1; GROW[i] = 0;
GDEL[i] = base + (D3[i*3+1] + 1) * .5 * .3 + rnd() * .6;
}
}
function step(dt){
t += dt;
var lk = 1 - Math.exp(-dt * 3);
pxS += (ptrX - pxS) * lk; pyS += (ptrY - pyS) * lk;
/* hold / wind state */
if (holding && !scattered && introDone){
hold = Math.min(1, hold + dt / 2.3);
if (hold >= 1) fullScatter();
} else if (!scattered) hold = Math.max(0, hold - dt / .8);
if (scattered) scatterClock += dt;
var wT = scattered ? (scatterClock < 1.3 ? 1.3 : .3) : hold * hold * (3 - 2 * hold);
windAmt += (wT - windAmt) * (1 - Math.exp(-dt * (scattered ? 3.5 : 6)));
var missing = 0;
for (var i = 0; i < N; i++) if (!PRES[i]) missing++;
if (!holding && !scattered && hold === 0 && introDone){
idle += dt; if (idle > 2.2 && missing) { regrow(false, 0); idle = 0; }
} else idle = 0;
/* head transform: stem pivots at the soil line, head bends toward the pointer and with the wind */
var sp = prox.p, breath = Math.sin(t * 1.25);
var sw = pxS * .1 + Math.sin(t * .55) * .022 + windAmt * .12;
var len = (L.hy - L.gy + sp * L.H * L.wpp * .2) * I.stem;
var hx = Math.sin(sw) * len, hy = L.gy + Math.cos(sw) * len;
var s = Math.max(1e-4, I.head * (1 - .2 * sp) * (1 + .012 * breath));
autoRot += dt * (.085 + windAmt * .3);
head.position.set(hx, hy, 0); head.scale.setScalar(s);
head.rotation.set(.14 + pyS * .3, autoRot + pxS * .45, -sw * .9);
head.updateMatrixWorld(true);
var e = head.matrixWorld.elements, is = 1 / s;
var m0 = e[0]*is, m1 = e[1]*is, m2 = e[2]*is, m4 = e[4]*is, m5 = e[5]*is, m6 = e[6]*is, m8 = e[8]*is, m9 = e[9]*is, m10 = e[10]*is;
/* wind in head space */
var wlx = m0*WX + m1*WY + m2*WZ, wly = m4*WX + m5*WY + m6*WZ, wlz = m8*WX + m9*WY + m10*WZ;
var bendK = windAmt * .42, trem = windAmt * .075 + .012;
for (i = 0; i < N; i++){
if (!PRES[i]){ far(i); continue; }
if (GROW[i] < 1){
if (GDEL[i] > 0) GDEL[i] -= dt; else GROW[i] = Math.min(1, GROW[i] + dt / .95);
}
var g = GROW[i];
if (g <= 0){ far(i); continue; }
var gs = Math.min(1, g / .62), gp = Math.max(0, Math.min(1, (g - .38) / .62));
gs = 1 - Math.pow(1 - gs, 3); gp = 1 - Math.pow(1 - gp, 3);
var i3 = i * 3, dx = D3[i3], dy = D3[i3+1], dz = D3[i3+2], ph = PH[i], fl = FLEX[i];
/* local axis: rest direction + wind bend + tremble */
var lx = dx + wlx * bendK * fl + Math.sin(t * 17 + ph) * trem;
var ly = dy + wly * bendK * fl + Math.sin(t * 13 + ph * 1.7) * trem;
var lz = dz + wlz * bendK * fl + Math.sin(t * 11 + ph * 2.3) * trem;
var ll = 1 / Math.hypot(lx, ly, lz); lx *= ll; ly *= ll; lz *= ll;
/* to world */
var ar = R0 * .95;
var Ax = e[12] + s * (m0*dx + m4*dy + m8*dz) * ar, Ay = e[13] + s * (m1*dx + m5*dy + m9*dz) * ar, Az = e[14] + s * (m2*dx + m6*dy + m10*dz) * ar;
var ux = m0*lx + m4*ly + m8*lz, uy = m1*lx + m5*ly + m9*lz, uz = m2*lx + m6*ly + m10*lz;
var ex0 = E1[i3], ey0 = E1[i3+1], ez0 = E1[i3+2];
var ex = m0*ex0 + m4*ey0 + m8*ez0, ey = m1*ex0 + m5*ey0 + m9*ez0, ez = m2*ex0 + m6*ey0 + m10*ez0;
var ls = LS[i] * s * gs * (1 + .012 * breath), pl = PL[i] * s * gp;
var op = OPEN[i] * (.2 + .8 * gp) + .04 * breath, phi = 0;
var Cx = Ax + ux * ls, Cy = Ay + uy * ls, Cz = Az + uz * ls;
/* scroll release: deterministic drift so it reverses cleanly */
if (ST[i] >= 0 && sp > ST[i] && g >= 1){
var qq = Math.min(1, (sp - ST[i]) / .3); qq = qq * qq * (3 - 2 * qq);
var dist = SDIST[i] * qq;
Cx += WX * dist + Math.sin(qq * 2.6 + ph) * .35 * qq;
Cy += WY * dist + Math.cos(qq * 2.1 + ph) * .3 * qq;
Cz += WZ * dist * .5;
ux += (WX - ux) * qq; uy += (WY - uy) * qq; uz += (WZ - uz) * qq;
phi = qq * 3.5;
}
var un = 1 / Math.hypot(ux, uy, uz); ux *= un; uy *= un; uz *= un;
var de = ex*ux + ey*uy + ez*uz; ex -= ux*de; ey -= uy*de; ez -= uz*de;
var en = 1 / (Math.hypot(ex, ey, ez) || 1); ex *= en; ey *= en; ez *= en;
/* detach? */
if (g >= .95 && introDone){
var face = ux*WX + uy*WY + uz*WZ, go = false, burst = 0;
if (scattered){ if (scatterClock > (1 - face) * .2 + WAVE[i] * .14){ go = true; burst = .9; } }
else if (hold > 0 && hold > TAU[i] - .28 * face) go = true;
if (go){
spawn(Cx, Cy, Cz, ux, uy, uz, ex, ey, ez, ls, pl, op, phi, i, burst);
PRES[i] = 0; GROW[i] = 0; far(i); continue;
}
}
writeUnit(i, Cx, Cy, Cz, ux, uy, uz, ex, ey, ez, ls, pl, op, phi, i);
}
/* flyers: air = wind + curl-ish swirl + buoyancy; drag pulls velocity toward the air */
var airK = .3 + 1.6 * windAmt, kd = 1 - Math.exp(-dt * 1.15), ko = 1 - Math.exp(-dt * 2.2);
var limX = L.halfW * 1.35 + 1.4, limY = L.halfH * 1.35 + 1.4;
for (var f = 0; f < F; f++){
if (!FAL[f]) continue;
var f3 = f * 3, px = FP[f3], py = FP[f3+1], pz = FP[f3+2];
FAGE[f] += dt;
var cx = Math.sin(py * 1.7 + t * .6) + Math.cos(pz * 1.3 - t * .4) * .6;
var cy = Math.sin(pz * 1.5 + t * .5) + Math.cos(px * 1.1 + t * .7) * .6;
var cz = Math.sin(px * 1.3 - t * .45) + Math.cos(py * 1.2 + t * .3) * .6;
var vx = FV[f3], vy = FV[f3+1], vz = FV[f3+2];
vx += (WX * airK + cx * .5 - vx) * kd;
vy += (WY * airK + cy * .5 + .22 - vy) * kd;
vz += (WZ * airK * .7 + cz * .3 - vz) * kd;
FV[f3] = vx; FV[f3+1] = vy; FV[f3+2] = vz;
px += vx * dt; py += vy * dt; pz += vz * dt;
FP[f3] = px; FP[f3+1] = py; FP[f3+2] = pz;
if (px > limX || px < -limX || py > limY || py < -limY || pz > L.D * .55 || FAGE[f] > 16){ FAL[f] = 0; far(N + f); continue; }
/* parachute turns to face its direction of travel */
var vv = Math.hypot(vx, vy, vz);
var fux = FU[f3], fuy = FU[f3+1], fuz = FU[f3+2];
if (vv > 1e-4){ fux += (vx / vv - fux) * ko; fuy += (vy / vv - fuy) * ko; fuz += (vz / vv - fuz) * ko; }
var fn = 1 / Math.hypot(fux, fuy, fuz); fux *= fn; fuy *= fn; fuz *= fn;
FU[f3] = fux; FU[f3+1] = fuy; FU[f3+2] = fuz;
var fex = FE[f3], fey = FE[f3+1], fez = FE[f3+2], fd = fex*fux + fey*fuy + fez*fuz;
fex -= fux*fd; fey -= fuy*fd; fez -= fuz*fd;
var fen = 1 / (Math.hypot(fex, fey, fez) || 1); fex *= fen; fey *= fen; fez *= fen;
FE[f3] = fex; FE[f3+1] = fey; FE[f3+2] = fez;
FPHI[f] += FSP[f] * dt;
writeUnit(N + f, px, py, pz, fux, fuy, fuz, fex, fey, fez, FLS[f], FPL[f], FOP[f], FPHI[f], FSRC[f]);
}
/* stem: quadratic curve from the soil point up into the core */
var gx = 0, gy = L.gy, cxs = 0, cys = gy + len * .58, tx = hx, ty = hy;
var w0 = 1.7 * L.wpp, w1 = 1.05 * L.wpp;
for (var n = 0; n <= SM; n++){
var tt = n / SM, it = 1 - tt;
var bx = it*it*gx + 2*it*tt*cxs + tt*tt*tx, by = it*it*gy + 2*it*tt*cys + tt*tt*ty;
var dvx = 2*it*(cxs - gx) + 2*tt*(tx - cxs), dvy = 2*it*(cys - gy) + 2*tt*(ty - cys);
var dl = Math.hypot(dvx, dvy) || 1, nx = -dvy / dl, ny = dvx / dl, hw = (w0 + (w1 - w0) * tt) * (len > 1e-3 ? 1 : 0);
var o6 = n * 6;
stemPos[o6] = bx + nx * hw; stemPos[o6+1] = by + ny * hw; stemPos[o6+2] = 0;
stemPos[o6+3] = bx - nx * hw; stemPos[o6+4] = by - ny * hw; stemPos[o6+5] = 0;
}
/* uniforms */
hp.set(hx, hy, 0).project(cam);
ppMat.uniforms.uCore.value.set((hp.x * .5 + .5) * L.W * L.dpr, (hp.y * .5 + .5) * L.H * L.dpr, R0 * s * L.Rpx * L.dpr);
stMat.uniforms.uCz.value = ppMat.uniforms.uCz.value = L.D;
stMat.uniforms.uCr.value = ppMat.uniforms.uCr.value = Math.max(.2, s);
}
function draw(){
stGeo.attributes.position.needsUpdate = true;
ppGeo.attributes.position.needsUpdate = true;
stemGeo.attributes.position.needsUpdate = true;
rA.render(sceneA, cam); rB.render(sceneB, cam);
}
return { layout: layout, step: step, draw: draw, regrow: regrow, N: N };
}
/* ================= hold to scatter ================= */
function setLabel(st){
if (st === labState) return; labState = st;
holdLab.textContent = { idle: 'Hold to scatter', hold: 'Keep holding', gone: 'Scattered', grow: 'Regrowing' }[st];
curT.textContent = { idle: 'Hold', hold: 'Hold', gone: 'Gone', grow: 'Wait' }[st];
}
function holdStart(){
if (!GL || RED || !introDone || scattered) return;
holding = true; hero.classList.add('is-holding'); cur.classList.add('is-down');
}
function holdEnd(){
holding = false; hero.classList.remove('is-holding'); cur.classList.remove('is-down');
}
function fullScatter(){
scattered = true; scatterClock = 0; hold = 1; holdEnd();
setLabel('gone');
swapWords(NEXT, .75);
clearTimeout(resetT);
resetT = setTimeout(function(){
sim.regrow(true, 0);
scattered = false; hold = 0; windAmt = Math.min(windAmt, .3);
setLabel('grow');
swapWords(WORDS, .8);
setTimeout(function(){ if (!scattered) setLabel('idle'); }, 1600);
}, 2500);
}
var lastPct = -1;
function drawUI(){
var hv = scattered ? 1 : hold;
holdSq.style.transform = 'scaleY(' + hv.toFixed(3) + ')';
holdBar.style.transform = 'scaleX(' + hv.toFixed(3) + ')';
curFill.style.transform = 'scaleY(' + hv.toFixed(3) + ')';
var pc = Math.min(99, Math.round(hv * 99));
if (pc !== lastPct){ lastPct = pc; holdPct.textContent = (pc < 10 ? '0' : '') + pc; }
if (!scattered && labState !== 'grow') setLabel(holding && hold > .02 ? 'hold' : 'idle');
}
zone.addEventListener('pointerdown', function(e){
if (e.button !== 0) return;
if (e.pointerType === 'mouse'){ e.preventDefault(); try { zone.setPointerCapture(e.pointerId); } catch (x){} }
holdStart();
});
holdBtn.addEventListener('pointerdown', function(e){ if (e.button !== 0) return; e.preventDefault(); holdStart(); });
['pointerup', 'pointercancel'].forEach(function(ev){ window.addEventListener(ev, holdEnd); });
window.addEventListener('blur', holdEnd);
zone.addEventListener('contextmenu', function(e){ e.preventDefault(); });
holdBtn.addEventListener('contextmenu', function(e){ e.preventDefault(); });
function heroActive(){
if (!visible || !introDone || menuOpen) return false;
var r = hero.getBoundingClientRect();
return r.bottom > window.innerHeight * .5 && r.top < window.innerHeight * .5;
}
window.addEventListener('keydown', function(e){
var isSpace = e.code === 'Space' || e.key === ' ';
var isEnterOnBtn = e.key === 'Enter' && e.target === holdBtn;
if (!isSpace && !isEnterOnBtn) return;
if (e.repeat){ if (holding) e.preventDefault(); return; }
var tg = e.target;
if (tg !== holdBtn && tg && tg.closest && tg.closest('a,button,input,textarea,select,summary,[contenteditable],[role="button"]')) return;
if (!heroActive()) return;
e.preventDefault(); holdStart();
});
window.addEventListener('keyup', function(e){
if ((e.code === 'Space' || e.key === ' ' || e.key === 'Enter') && holding){ e.preventDefault(); holdEnd(); }
});
window.addEventListener('pointermove', function(e){
ptrX = e.clientX / window.innerWidth * 2 - 1;
ptrY = e.clientY / window.innerHeight * 2 - 1;
}, { passive: true });
/* pointer tag */
var fine = matchMedia('(hover: hover) and (pointer: fine)').matches;
if (fine && !RED && GL){
var qx = gsap.quickTo(cur, 'x', { duration: .35, ease: 'power3.out' }), qy = gsap.quickTo(cur, 'y', { duration: .35, ease: 'power3.out' });
var inZone = false;
zone.addEventListener('pointerenter', function(){ inZone = true; });
zone.addEventListener('pointerleave', function(){ inZone = false; cur.classList.remove('is-on'); });
zone.addEventListener('pointermove', function(e){
var r = hero.getBoundingClientRect(), x = e.clientX - r.left, y = e.clientY - r.top;
if (!cur.classList.contains('is-on')){ gsap.set(cur, { x: x, y: y }); }
qx(x); qy(y);
cur.classList.toggle('is-on', inZone && introDone);
});
}
/* ================= loop, visibility, resize ================= */
if (GL){
new IntersectionObserver(function(en){ visible = en[0].isIntersecting; }, { threshold: 0 }).observe(hero);
var last = performance.now();
var tick = function(){
var now = performance.now(), dt = Math.min(.05, (now - last) / 1000); last = now;
if (!visible || document.hidden) return;
if (parseFloat(cvA.style.opacity === '' ? 1 : cvA.style.opacity) < .01) return;
sim.step(dt); sim.draw(); drawUI();
};
if (!RED) gsap.ticker.add(tick);
var rz = 0;
var relayout = function(){
fitWords(); sim.layout();
if (RED){ sim.step(0); sim.draw(); }
};
window.addEventListener('resize', function(){ cancelAnimationFrame(rz); rz = requestAnimationFrame(relayout); });
relayout();
} else {
fitWords();
window.addEventListener('resize', fitWords);
}
if (document.fonts && document.fonts.ready) document.fonts.ready.then(function(){ fitWords(); if (GL && RED){ sim.step(0); sim.draw(); } });
/* ================= scroll: pin, drift, words slide apart ================= */
if (!RED){
var mm = gsap.matchMedia();
var build = function(pin){
var tl = gsap.timeline({ scrollTrigger: pin
? { trigger: hero, start: 'top top', end: '+=80%', pin: true, scrub: .8, anticipatePin: 1, invalidateOnRefresh: true }
: { trigger: hero, start: 'top top', end: 'bottom top', scrub: .6, invalidateOnRefresh: true } });
tl.to(prox, { p: 1, ease: 'none', duration: 1 }, 0)
.to(lineA, { x: '-15vw', skewX: -7, ease: 'none', duration: 1 }, 0)
.to(lineB, { x: '15vw', skewX: 7, ease: 'none', duration: 1 }, 0)
.to([cvA, cvB], { opacity: 0, ease: 'power1.in', duration: .24 }, .76);
return function(){ prox.p = 0; };
};
mm.add('(min-width: 900px)', function(){ return build(true); });
mm.add('(max-width: 899px)', function(){ return build(false); });
}
/* ================= intro ================= */
var wordSpans = [];
function finishIntro(){ introDone = true; gsap.set(frame, { clearProps: 'clipPath' }); hero.classList.add('is-ready'); setLabel('idle'); }
if (RED){
if (load) load.remove();
finishIntro();
} else {
if ('scrollRestoration' in history) history.scrollRestoration = 'manual';
window.scrollTo(0, 0);
if (lenis) lenis.stop();
wordSpans = SP.split ? SP.split(inA).concat(SP.split(inB)) : [inA, inB];
var fadeEls = $$('.sh-meta, .sh-pitch, .sh-hold, .sh-cta', hero);
gsap.set(wordSpans, { yPercent: 115 });
gsap.set(hdrIn, { yPercent: -100 });
gsap.set(frame, { clipPath: 'inset(0% 0% 100% 0%)' });
gsap.set(rule, { scaleX: 0 });
gsap.set(fadeEls, { autoAlpha: 0, y: 14 });
setLabel('idle');
var num = $('.sh-load__n', load), bar = $('.sh-load__line i', load), cnt = { v: 0 };
var paint = function(){ num.textContent = String(Math.round(cnt.v * 100)).padStart(3, '0'); bar.style.transform = 'scaleX(' + cnt.v.toFixed(4) + ')'; };
var fontsOk = false, timeOk = false, fired = false;
var fontP = (document.fonts && document.fonts.ready) ? document.fonts.ready : Promise.resolve();
Promise.race([fontP, new Promise(function(r){ setTimeout(r, 3000); })]).then(function(){ fontsOk = true; maybeGo(); });
gsap.to(cnt, { v: .86, duration: 1.2, ease: 'power2.inOut', onUpdate: paint, onComplete: function(){ timeOk = true; maybeGo(); } });
var maybeGo = function(){
if (!fontsOk || !timeOk || fired) return; fired = true;
gsap.to(cnt, { v: 1, duration: .3, ease: 'power2.out', onUpdate: paint, onComplete: wipe });
};
var wipe = function(){
fitWords(); if (GL) sim.layout();
gsap.to(load, { clipPath: 'inset(0% 0% 100% 0%)', duration: 1.05, ease: 'expo.inOut',
onComplete: function(){ load.remove(); if (lenis) lenis.start(); ScrollTrigger.refresh(); } });
var tl = gsap.timeline({ delay: .32 });
tl.to(frame, { clipPath: 'inset(0% 0% 0% 0%)', duration: 1.4, ease: 'expo.inOut' }, 0)
.to(rule, { scaleX: 1, duration: 1.3, ease: 'expo.inOut' }, .05)
.to(wordSpans, { yPercent: 0, duration: 1.5, ease: 'expo.out', stagger: .075 }, .12)
.to(hdrIn, { yPercent: 0, duration: 1.1, ease: 'expo.out' }, .4)
.to(I, { stem: 1, duration: 1.3, ease: 'power3.out' }, .15)
.to(I, { head: 1, duration: 1.5, ease: 'back.out(1.9)' }, .55)
.add(function(){ if (GL) sim.regrow(true, 0); }, .6)
.to(fadeEls, { autoAlpha: 1, y: 0, duration: 1, ease: 'power3.out', stagger: .07 }, .75)
.add(finishIntro, 2.1);
};
}
})();
</script>
```
## `sections/02-thesis.html`
```html
<section id="thesis" class="st" aria-labelledby="st-title">
<style>
.st{padding-block:clamp(72px,10vw,160px);background:var(--bg);color:var(--ink)}
.st-sheet{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:1px;background:var(--line);border:1px solid var(--line)}
.st-sheet>*{background:var(--bg);min-width:0;position:relative}
/* column ruler: makes the 12-col framework readable */
.st-rule{height:26px;display:flex;align-items:center;padding-inline:8px;font-family:var(--mono);font-size:10px;letter-spacing:.06em;color:var(--mute)}
.st-rule--on{color:var(--ink)}
.st-rule--on::after{content:"";position:absolute;right:8px;top:50%;width:5px;height:5px;margin-top:-2.5px;background:var(--spore)}
/* row 1: kicker + statement */
.st-meta{grid-column:span 3;padding:clamp(20px,2.2vw,32px);display:flex;flex-direction:column;justify-content:space-between;gap:28px}
.st-meta .kick{margin:0}
.st-log{margin:0;font-family:var(--mono);font-size:11.5px;line-height:1.7;letter-spacing:.03em;color:var(--mute);text-transform:uppercase}
.st-log b{font-weight:500;color:var(--ink)}
.st-copy{grid-column:span 9;padding:clamp(28px,4vw,72px) clamp(20px,4vw,72px) clamp(36px,4.5vw,80px)}
.st-say{margin:0;font-family:var(--body);font-weight:400;font-size:clamp(28px,3.6vw,56px);line-height:1.1;letter-spacing:-.022em;max-width:20.5em}
.st-say .w{padding-bottom:.12em;margin-bottom:-.12em}
.st-hl{background-image:linear-gradient(var(--spore),var(--spore));background-repeat:no-repeat;background-position:0 55%;background-size:100% 88%;color:var(--ink)}
.st-sign{margin:clamp(24px,3vw,44px) 0 0;display:flex;flex-wrap:wrap;gap:8px 22px;font-family:var(--mono);font-size:12px;letter-spacing:.04em;color:var(--mute);text-transform:uppercase}
.st-sign span::before{content:"/ ";color:var(--line)}
/* row 2: sectors */
.st-sec{grid-column:span 3;padding:clamp(18px,1.8vw,28px);min-height:clamp(260px,24vw,340px);display:flex;flex-direction:column;isolation:isolate;overflow:hidden}
.st-sec::before{content:"";position:absolute;inset:0;background:var(--fog);transform:scaleY(0);transform-origin:50% 100%;transition:transform .55s var(--ease);z-index:-1}
.st-sec:hover::before{transform:none}
.st-top{display:flex;justify-content:space-between;align-items:center;font-family:var(--mono);font-size:12px;letter-spacing:.06em;color:var(--mute)}
.st-chk{width:18px;height:18px;background:var(--spore);display:grid;place-items:center;opacity:0;transform:scale(.4) rotate(-45deg);transition:opacity .3s var(--ease),transform .45s var(--ease)}
.st-chk svg{width:12px;height:12px}
.st-sec:hover .st-chk{opacity:1;transform:none}
.st-ico{width:clamp(56px,4.6vw,68px);height:auto;margin:clamp(22px,3vw,44px) 0 auto;overflow:visible}
.st-ico [pathLength]{fill:none;stroke:var(--ink);stroke-width:1.5;stroke-linecap:square;stroke-linejoin:miter;stroke-dasharray:1;stroke-dashoffset:0;vector-effect:non-scaling-stroke}
.st-ico .st-acc{stroke:var(--spore-deep)}
.st-ico .st-dot{fill:var(--spore);stroke:none}
.st-name{margin:28px 0 0;font-size:clamp(34px,2.9vw,42px)}
.st-desc{margin:12px 0 0;font-family:var(--mono);font-size:12.5px;line-height:1.55;color:var(--mute);max-width:30ch}
/* row 3: stats */
.st-stat{grid-column:span 3;padding:clamp(18px,1.8vw,28px) clamp(18px,1.8vw,28px) clamp(22px,2.2vw,32px);display:flex;flex-direction:column;gap:14px}
.st-stat-id{font-family:var(--mono);font-size:11px;letter-spacing:.08em;color:var(--mute);text-transform:uppercase;display:flex;align-items:center;gap:8px}
.st-stat-id::before{content:"";width:6px;height:6px;background:var(--spore)}
.st-num{display:block;font-size:clamp(64px,9vw,150px);white-space:nowrap;font-variant-numeric:tabular-nums;margin-top:clamp(8px,2vw,28px)}
.st-lab{margin:0;font-family:var(--mono);font-size:12.5px;line-height:1.5;letter-spacing:.02em;color:var(--mute);max-width:24ch}
/* row 4: photo + note */
.st-photo{grid-column:span 8;padding:clamp(10px,1vw,14px)}
.st-frame{margin:0;position:relative;overflow:hidden;aspect-ratio:1600/1066;background:var(--fog)}
.st-frame img{width:100%;height:100%;object-fit:cover;will-change:transform}
.st-chip{position:absolute;left:clamp(10px,1.4vw,20px);bottom:clamp(10px,1.4vw,20px);background:var(--ink);color:#fff;font-family:var(--mono);font-size:11px;letter-spacing:.06em;text-transform:uppercase;padding:7px 10px 7px 9px;display:flex;align-items:center;gap:8px}
.st-chip::before{content:"";width:6px;height:6px;background:var(--spore);animation:st-blink 1.6s steps(2,jump-none) infinite}
@keyframes st-blink{50%{opacity:.15}}
.st-note{grid-column:span 4;padding:clamp(20px,2.2vw,32px);display:flex;flex-direction:column;gap:24px}
.st-note-k{font-family:var(--mono);font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:var(--mute)}
.st-note p{margin:0;font-size:clamp(17px,1.35vw,20px);line-height:1.45;letter-spacing:-.005em}
.st-spec{margin:auto 0 0;display:grid;border-top:1px solid var(--line)}
.st-spec div{display:grid;grid-template-columns:9ch 1fr;gap:12px;padding:10px 0;border-bottom:1px solid var(--line);font-family:var(--mono);font-size:12px;line-height:1.45}
.st-spec dt{color:var(--mute);text-transform:uppercase;letter-spacing:.06em}
.st-spec dd{margin:0}
@media (max-width:1100px){
.st-meta{grid-column:span 12;flex-direction:row;align-items:flex-start}
.st-copy{grid-column:span 12}
.st-sec{grid-column:span 6}
.st-stat{grid-column:span 6}
.st-photo{grid-column:span 12}
.st-note{grid-column:span 12}
.st-spec{margin-top:0}
}
@media (max-width:600px){
.st-rule{padding-inline:0;justify-content:center;font-size:9px}
.st-rule--on::after{display:none}
.st-meta{flex-direction:column;gap:18px}
.st-copy{padding:26px 18px 32px}
.st-sec{grid-column:span 12;min-height:0}
.st-ico{margin:22px 0 0}
.st-name{margin-top:22px}
.st-stat{padding:16px 14px 20px}
.st-lab{font-size:11.5px}
.st-photo{padding:8px}
.st-note{padding:22px 18px}
}
@media (prefers-reduced-motion: reduce){
.st-sec::before,.st-chk{transition:none}
.st-chip::before{animation:none}
}
</style>
<div class="wrap">
<div class="st-sheet">
<span class="st-rule st-rule--on" aria-hidden="true">01</span><span class="st-rule" aria-hidden="true">02</span><span class="st-rule" aria-hidden="true">03</span><span class="st-rule" aria-hidden="true">04</span><span class="st-rule" aria-hidden="true">05</span><span class="st-rule" aria-hidden="true">06</span><span class="st-rule" aria-hidden="true">07</span><span class="st-rule" aria-hidden="true">08</span><span class="st-rule" aria-hidden="true">09</span><span class="st-rule" aria-hidden="true">10</span><span class="st-rule" aria-hidden="true">11</span><span class="st-rule" aria-hidden="true">12</span>
<div class="st-meta">
<h2 class="kick" id="st-title">001 — Thesis</h2>
<p class="st-log"><b>Entry</b> 2019-03-04<br>Berlin-Wedding<br>Halle 3, bench 1</p>
</div>
<div class="st-copy">
<p class="st-say">Most funds <span class="st-hl">won't touch hardware.</span> It's slow, it's messy, and every good idea needs a workshop nobody wants to pay for. So <span class="st-hl">we built the workshop</span> — then put the money, the machinists and an unreasonable amount of <span class="st-hl">patience</span> under one roof.</p>
<p class="st-sign"><span>Seeds</span><span>Soil</span><span>Root</span><span>Sprout</span><span>Bloom</span></p>
</div>
<article class="st-sec">
<div class="st-top"><span>01</span><span class="st-chk" aria-hidden="true"><svg viewBox="0 0 12 12" fill="none" stroke="#0C0D0F" stroke-width="1.6"><path d="M2 6.2 4.8 9 10 3"/></svg></span></div>
<svg class="st-ico" viewBox="0 0 48 48" aria-hidden="true">
<path pathLength="1" d="M5 43h18"/>
<path pathLength="1" d="M14 43V31"/>
<circle pathLength="1" cx="14" cy="28.5" r="2.5"/>
<path pathLength="1" d="M15.8 26.7 27.7 14.8"/>
<circle pathLength="1" cx="29.5" cy="13" r="2.5"/>
<path pathLength="1" class="st-acc" d="M31.5 11.2 38 7l4 4-5 2"/>
<path pathLength="1" class="st-acc" d="M31.3 15.2 35 22l5-1.5-2.5-4.5"/>
</svg>
<h3 class="st-name disp">Robotics</h3>
<p class="st-desc">Arms, grippers, and machines that learn a task by watching once.</p>
</article>
<article class="st-sec">
<div class="st-top"><span>02</span><span class="st-chk" aria-hidden="true"><svg viewBox="0 0 12 12" fill="none" stroke="#0C0D0F" stroke-width="1.6"><path d="M2 6.2 4.8 9 10 3"/></svg></span></div>
<svg class="st-ico" viewBox="0 0 48 48" aria-hidden="true">
<path pathLength="1" d="M7 41C6 22 18 9 42 6c1 22-11 35-35 35Z"/>
<path pathLength="1" d="M7 41 24 24"/>
<path pathLength="1" class="st-acc" d="M30 13l-6 10h7l-5 10"/>
</svg>
<h3 class="st-name disp">Climate hardware</h3>
<p class="st-desc">Sensors, storage and field kit that has to survive actual weather.</p>
</article>
<article class="st-sec">
<div class="st-top"><span>03</span><span class="st-chk" aria-hidden="true"><svg viewBox="0 0 12 12" fill="none" stroke="#0C0D0F" stroke-width="1.6"><path d="M2 6.2 4.8 9 10 3"/></svg></span></div>
<svg class="st-ico" viewBox="0 0 48 48" aria-hidden="true">
<path pathLength="1" d="M5 9h38v30H5Z"/>
<path pathLength="1" class="st-acc" d="M5 26h9l3-7 5 14 5-19 4 12h12"/>
<path pathLength="1" d="M18 43h12"/>
</svg>
<h3 class="st-name disp">Health devices</h3>
<p class="st-desc">Wearables and rehab tools built to pass a clinic, not just a demo.</p>
</article>
<article class="st-sec">
<div class="st-top"><span>04</span><span class="st-chk" aria-hidden="true"><svg viewBox="0 0 12 12" fill="none" stroke="#0C0D0F" stroke-width="1.6"><path d="M2 6.2 4.8 9 10 3"/></svg></span></div>
<svg class="st-ico" viewBox="0 0 48 48" aria-hidden="true">
<path pathLength="1" d="M13 13h22v22H13Z"/>
<path pathLength="1" class="st-acc" d="M19 19h10v10H19Z"/>
<path pathLength="1" d="M18 13V6M24 13V6M30 13V6M18 42v-7M24 42v-7M30 42v-7M13 18H6M13 24H6M13 30H6M42 18h-7M42 24h-7M42 30h-7"/>
</svg>
<h3 class="st-name disp">Edge AI</h3>
<p class="st-desc">Models squeezed onto chips that run far from any data centre.</p>
</article>
<div class="st-stat">
<span class="st-stat-id">S.01 / portfolio</span>
<span class="st-num disp" data-to="41">41</span>
<p class="st-lab">Companies backed since 2019</p>
</div>
<div class="st-stat">
<span class="st-stat-id">S.02 / capital</span>
<span class="st-num disp" data-to="58" data-prefix="€" data-suffix="M">€58M</span>
<p class="st-lab">Raised by the portfolio after residency</p>
</div>
<div class="st-stat">
<span class="st-stat-id">S.03 / survival</span>
<span class="st-num disp" data-to="74" data-suffix="%">74%</span>
<p class="st-lab">Closed a follow-on round within 18 months</p>
</div>
<div class="st-stat">
<span class="st-stat-id">S.04 / output</span>
<span class="st-num disp" data-to="312">312</span>
<p class="st-lab">Prototypes built in our own shop</p>
</div>
<div class="st-photo">
<figure class="st-frame">
<img src="assets/lab.jpg" width="1600" height="1066" loading="lazy" alt="A technician solders components onto a circuit board at a workbench">
<figcaption class="st-chip">Halle 3 · machine shop, 06:40</figcaption>
</figure>
</div>
<aside class="st-note">
<span class="st-note-k">Site note — Halle 3</span>
<p>2,400 m² of former tram depot. A wet lab, a machine shop, an RF room, and an espresso machine that has been pulling shots longer than most of our founders have been alive.</p>
<dl class="st-spec">
<div><dt>Floor</dt><dd>2,400 m²</dd></div>
<div><dt>Rooms</dt><dd>Wet lab · Machine shop · RF room</dd></div>
<div><dt>Where</dt><dd>Berlin-Wedding, since 2019</dd></div>
</dl>
</aside>
</div>
</div>
<script>
(function(){
var root=document.getElementById('thesis');
if(!root||!window.SP)return;
root.querySelectorAll('.st-num').forEach(function(el){SP.count(el)});
if(SP.reduced)return;
var css=getComputedStyle(document.documentElement);
var LINE=css.getPropertyValue('--line').trim()||'#D6D9DE';
var INK=css.getPropertyValue('--ink').trim()||'#0C0D0F';
/* statement: words scrub grey -> ink, highlight bars wipe in as each phrase is reached */
var say=root.querySelector('.st-say');
var words=SP.split(say);
var hls=[].slice.call(say.querySelectorAll('.st-hl'));
var STEP=.3;
gsap.set(words,{color:LINE});
gsap.set(hls,{backgroundSize:'0% 88%'});
var tl=gsap.timeline({scrollTrigger:{trigger:say,start:'top 82%',end:'bottom 42%',scrub:.6}});
tl.to(words,{color:INK,duration:1,ease:'none',stagger:STEP},0);
hls.forEach(function(h){
var inner=[].slice.call(h.querySelectorAll('.w>span'));
var i=words.indexOf(inner[0]);
tl.to(h,{backgroundSize:'100% 88%',duration:inner.length*STEP+.5,ease:'power1.inOut'},i*STEP+.5);
});
/* sectors: line-art draws in on enter, redraws on hover */
var secs=[].slice.call(root.querySelectorAll('.st-sec'));
var draw=function(paths,delay){
return gsap.fromTo(paths,{strokeDashoffset:1},{strokeDashoffset:0,duration:1.1,ease:'power2.inOut',stagger:.09,delay:delay||0,overwrite:true});
};
secs.forEach(function(s){gsap.set(s.querySelectorAll('.st-ico [pathLength]'),{strokeDashoffset:1})});
gsap.set(secs.map(function(s){return s.querySelectorAll('.st-top,.st-name,.st-desc')}),{autoAlpha:0,y:18});
ScrollTrigger.batch(secs,{start:'top 86%',once:true,onEnter:function(b){
b.forEach(function(s,k){
draw(s.querySelectorAll('.st-ico [pathLength]'),k*.12);
gsap.to(s.querySelectorAll('.st-top,.st-name,.st-desc'),{autoAlpha:1,y:0,duration:.9,ease:'expo.out',stagger:.07,delay:k*.12+.15});
});
}});
if(!matchMedia('(hover:none)').matches){
secs.forEach(function(s){
s.addEventListener('pointerenter',function(){draw(s.querySelectorAll('.st-ico [pathLength]'),0)});
});
}
/* stats: labels lift in alongside the counters */
gsap.from(root.querySelectorAll('.st-stat-id,.st-lab'),{autoAlpha:0,y:12,duration:.9,ease:'expo.out',stagger:.05,
scrollTrigger:{trigger:root.querySelector('.st-stat'),start:'top 88%',once:true}});
/* photo: clip reveal + slow parallax inside the frame */
var frame=root.querySelector('.st-frame'),img=frame.querySelector('img'),chip=frame.querySelector('.st-chip');
gsap.set(img,{scale:1.15,transformOrigin:'50% 50%'});
gsap.fromTo(frame,{clipPath:'inset(100% 0% 0% 0%)'},{clipPath:'inset(0% 0% 0% 0%)',duration:1.5,ease:'expo.inOut',
scrollTrigger:{trigger:frame,start:'top 85%',once:true}});
gsap.from(chip,{autoAlpha:0,x:-14,duration:.8,ease:'expo.out',delay:1.1,scrollTrigger:{trigger:frame,start:'top 85%',once:true}});
gsap.fromTo(img,{yPercent:-6},{yPercent:6,ease:'none',scrollTrigger:{trigger:frame,start:'top bottom',end:'bottom top',scrub:true}});
gsap.from(root.querySelectorAll('.st-note>*'),{autoAlpha:0,y:16,duration:.9,ease:'expo.out',stagger:.08,
scrollTrigger:{trigger:root.querySelector('.st-note'),start:'top 86%',once:true}});
})();
</script>
</section>
```
## `sections/03-programs.html`
```html
<section id="programs" class="sp" aria-labelledby="sp-title">
<style>
.sp{padding-block:clamp(96px,12vw,176px);background:var(--bg);color:var(--ink);overflow-x:clip}
.sp *{min-width:0}
/* ---------- header row ---------- */
.sp-head{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,380px);gap:clamp(24px,4vw,64px);align-items:end;
padding-bottom:clamp(28px,3vw,44px);border-bottom:1px solid var(--line)}
.sp-head .kick{margin-bottom:clamp(18px,2vw,28px)}
.sp-h2{font-size:clamp(64px,10vw,180px)}
.sp-intro{margin:0;color:var(--mute);line-height:1.6;max-width:38ch}
.sp-intro b{color:var(--ink);font-weight:500}
/* ---------- program cards ---------- */
.sp-cards{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:clamp(16px,2vw,28px);margin-top:clamp(40px,5vw,80px)}
.sp-card{--notch:24px;height:100%;transition:transform .6s var(--ease)}
.sp-card__edge,.sp-card__body{clip-path:polygon(0 0,calc(100% - var(--notch)) 0,100% var(--notch),100% 100%,0 100%)}
.sp-card__edge{position:absolute;inset:0;background:var(--line);transition:background-color .4s var(--ease)}
.sp-card__body{position:relative;margin:1px;height:calc(100% - 2px);background:#fff;display:flex;flex-direction:column;
padding:clamp(24px,3vw,44px)}
/* edge layer peeks out 1px around the body, including along the notch diagonal */
.sp-card__bar{position:absolute;top:0;left:0;right:0;height:5px;background:var(--spore);transform:scaleX(0);transform-origin:0 50%;
transition:transform .7s var(--ease)}
.sp-card__top{display:flex;align-items:center;justify-content:space-between;gap:16px;padding-right:calc(var(--notch) + 4px)}
.sp-card__idx{display:flex;gap:14px;color:var(--mute)}
.sp-card__idx b{color:var(--ink);font-weight:600}
.sp-seed{width:44px;height:44px;flex:none;color:var(--ink);transition:transform .9s var(--ease)}
.sp-seed .sp-seed__dot{fill:var(--spore);stroke:none}
.sp-card__name{font-size:clamp(60px,8.4vw,136px);margin-top:clamp(28px,4vw,56px)}
.sp-card__sub{margin:clamp(14px,1.6vw,20px) 0 0;font-family:var(--mono);font-size:13px;letter-spacing:.02em;color:var(--mute);max-width:44ch}
.sp-facts{list-style:none;margin:clamp(28px,3vw,40px) 0 0;padding:0;border-top:1px solid var(--line)}
.sp-facts li{display:flex;align-items:baseline;gap:14px;padding:14px 0;border-bottom:1px solid var(--line);font-size:16px;line-height:1.4}
.sp-facts li::before{content:"";width:8px;height:8px;background:var(--spore);flex:none;transform:translateY(-1px)}
.sp-facts li>span{flex:1 1 auto}
.sp-facts em{flex:none;font-style:normal;font-family:var(--mono);font-size:12px;color:var(--mute);margin-left:auto;padding-left:12px;white-space:nowrap}
.sp-card__foot{margin-top:auto;padding-top:clamp(28px,3vw,40px);display:flex;flex-wrap:wrap;align-items:center;gap:14px 22px}
.sp-card__meta{color:var(--mute);font-size:12px}
@media (hover:hover){
.sp-card:hover{transform:translateY(-6px)}
.sp-card:hover .sp-card__edge{background:var(--ink)}
.sp-card:hover .sp-card__bar{transform:scaleX(1)}
.sp-card:hover .sp-seed{transform:rotate(200deg)}
}
.sp-card:focus-within .sp-card__edge{background:var(--ink)}
.sp-card:focus-within .sp-card__bar{transform:scaleX(1)}
/* ---------- what grows here ---------- */
.sp-grow-head{display:flex;flex-wrap:wrap;justify-content:space-between;align-items:end;gap:20px 40px;
margin-top:clamp(96px,11vw,176px);padding-bottom:clamp(24px,2.6vw,36px);border-bottom:1px solid var(--ink)}
.sp-grow-head .kick{margin-bottom:18px}
.sp-h3{font-size:clamp(48px,6.4vw,112px)}
.sp-grow-note{margin:0;color:var(--mute);max-width:40ch}
.sp-grow{display:grid;grid-template-columns:minmax(0,5fr) minmax(0,7fr);gap:clamp(32px,5vw,96px);margin-top:clamp(28px,3vw,40px)}
.sp-stick{position:sticky;top:calc(var(--hdr) + 24px);height:calc(100vh - var(--hdr) - 48px);min-height:520px;max-height:900px;
display:grid;grid-template-columns:4px minmax(0,1fr);grid-template-rows:minmax(0,1fr) auto;gap:18px 16px}
.sp-prog{grid-row:1;display:flex;flex-direction:column;gap:3px}
.sp-prog i{flex:1;background:var(--line);position:relative;overflow:hidden}
.sp-prog i::after{content:"";position:absolute;inset:0;background:var(--spore);transform:scaleY(0);transform-origin:50% 0;
transition:transform .45s var(--ease)}
.sp-prog i.is-lit::after{transform:none}
.sp-frame{grid-row:1;grid-column:2;border:1px solid var(--line);padding:10px;background:#fff}
.sp-frame__in{position:relative;width:100%;height:100%;overflow:hidden;background:var(--fog)}
.sp-frame__in img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;z-index:1}
.sp-frame__in img.is-top{z-index:3}
.sp-cap{position:absolute;left:12px;bottom:12px;z-index:9;background:var(--ink);color:#fff;padding:7px 10px;font-size:11px;
text-transform:uppercase;letter-spacing:.08em;max-width:calc(100% - 24px)}
.sp-count{grid-column:1 / -1;display:flex;align-items:end;justify-content:space-between;gap:16px;border-top:1px solid var(--line);padding-top:14px}
.sp-count__n{font-family:var(--mono);font-weight:500;font-size:clamp(44px,4.6vw,76px);line-height:.9;letter-spacing:-.04em;white-space:nowrap}
.sp-count__n span{color:var(--mute)}
.sp-count__lbl{color:var(--mute);text-align:right;max-width:24ch;font-size:12px;text-transform:uppercase;letter-spacing:.06em}
.sp-list{list-style:none;margin:0;padding:0}
.sp-list-wrap{padding-bottom:30vh}
.sp-row{border-bottom:1px solid var(--line)}
.sp-row:first-child{border-top:1px solid var(--line)}
.sp-row__btn{display:block;width:100%;text-align:left;padding:clamp(24px,2.6vw,34px) 0;color:var(--mute);transition:color .4s var(--ease)}
.sp-row__in{display:grid;grid-template-columns:72px minmax(0,1fr) auto;align-items:baseline;width:calc(100% - 12px);
transition:transform .5s var(--ease)}
.sp-row__num{display:inline-flex;align-items:center;gap:10px;font-family:var(--mono);font-size:13px}
.sp-row__num::before{content:"";width:8px;height:8px;background:var(--spore);transform:scale(0);transition:transform .4s var(--ease)}
.sp-row__t{font-family:var(--body);font-weight:500;font-size:clamp(21px,2vw,28px);line-height:1.2;letter-spacing:-.01em}
.sp-row__t small{font-family:var(--mono);font-size:12px;font-weight:400;letter-spacing:.04em;margin-left:8px;color:var(--mute)}
.sp-row__pm{font-family:var(--mono);font-size:16px;width:20px;text-align:center;transition:transform .4s var(--ease)}
.sp-row__d{display:grid;grid-template-rows:0fr;transition:grid-template-rows .55s var(--ease)}
.sp-row__d>div{overflow:hidden}
.sp-row__d p{margin:0;padding:0 12px clamp(24px,2.6vw,34px) 84px;max-width:calc(56ch + 84px);color:var(--ink);font-size:16px;line-height:1.55;
opacity:0;transition:opacity .4s var(--ease)}
.sp-row.is-on .sp-row__btn{color:var(--ink)}
.sp-row.is-on .sp-row__in{transform:translateX(12px)}
.sp-row.is-on .sp-row__num::before{transform:none}
.sp-row.is-on .sp-row__t small{color:var(--ink)}
.sp-row.is-on .sp-row__pm{transform:rotate(45deg)}
.sp-row.is-on .sp-row__d{grid-template-rows:1fr}
.sp-row.is-on .sp-row__d p{opacity:1;transition-delay:.12s}
.sp-row__btn:hover{color:var(--ink)}
.sp-end{margin:28px 0 0;color:var(--mute);font-size:12px}
/* reduced motion: final states, no transitions */
.sp-rm *,.sp-rm *::before,.sp-rm *::after{transition:none !important}
/* ---------- < 900: no pin, framed image on top, tap accordion ---------- */
@media (max-width:899px){
.sp-head{grid-template-columns:minmax(0,1fr)}
.sp-cards{grid-template-columns:minmax(0,1fr)}
.sp-card{--notch:18px}
.sp-facts li{font-size:15px}
.sp-grow{grid-template-columns:minmax(0,1fr)}
.sp-stick{position:relative;top:auto;height:auto;min-height:0;max-height:none;grid-template-columns:minmax(0,1fr);grid-template-rows:auto auto}
.sp-prog{display:none}
.sp-frame{grid-column:1;padding:8px}
.sp-frame__in{height:auto;aspect-ratio:4/3}
.sp-list-wrap{padding-bottom:0}
.sp-row__in{grid-template-columns:44px minmax(0,1fr) auto}
.sp-row__num::before{display:none}
.sp-row.is-on .sp-row__in{transform:translateX(6px)}
.sp-row__d p{padding-left:44px;max-width:none;font-size:15px}
.sp-row__t small{display:block;margin:4px 0 0}
}
</style>
<div class="wrap">
<header class="sp-head">
<div>
<p class="kick">002 — Programs</p>
<h2 class="disp sp-h2" id="sp-title">Two ways in</h2>
</div>
<p class="mono sp-intro">Pick by the state of your bench. <b>Germinate</b> if the idea still has wires hanging out of it. <b>Canopy</b> if it already works and now has to survive contact with a factory.</p>
</header>
<div class="sp-cards">
<div data-sp-reveal>
<article class="sp-card ticks" aria-labelledby="sp-germ">
<div class="sp-card__edge" aria-hidden="true"></div>
<div class="sp-card__body">
<span class="sp-card__bar" aria-hidden="true"></span>
<div class="sp-card__top">
<p class="mono sp-card__idx" style="margin:0"><b>01</b><span>Residency</span></p>
<svg class="sp-seed" viewBox="0 0 48 48" fill="none" stroke="currentColor" stroke-width="1.4" aria-hidden="true">
<path d="M24 6c7 6 11 13 11 21a11 11 0 0 1-22 0c0-8 4-15 11-21Z"/>
<path d="M24 12v26M24 22l-5-4M24 29l6-5"/>
<circle class="sp-seed__dot" cx="24" cy="42" r="2.2"/>
</svg>
</div>
<h3 class="disp sp-card__name" id="sp-germ">Germinate</h3>
<p class="sp-card__sub">Sixteen weeks inside Halle 3 with the tools, the money and the people who have shipped hardware before.</p>
<ul class="sp-facts">
<li><span>16-week residency</span> <em>Wk 01–16</em></li>
<li><span>2 cohorts a year</span> <em>Spring · Autumn</em></li>
<li><span>8 teams per cohort</span> <em>16 / yr</em></li>
<li><span>Desk, shop access, €150k on day one</span> <em>Day 01</em></li>
</ul>
<div class="sp-card__foot">
<a class="btn" href="#apply">Apply to Germinate <svg class="arr" viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M2 12 12 2M4 2h8v8"/></svg></a>
<span class="mono sp-card__meta">Autumn closes 30 Nov 2026</span>
</div>
</div>
</article>
</div>
<div data-sp-reveal>
<article class="sp-card ticks" aria-labelledby="sp-canopy">
<div class="sp-card__edge" aria-hidden="true"></div>
<div class="sp-card__body">
<span class="sp-card__bar" aria-hidden="true"></span>
<div class="sp-card__top">
<p class="mono sp-card__idx" style="margin:0"><b>02</b><span>Investment</span></p>
<svg class="sp-seed" viewBox="0 0 48 48" fill="none" stroke="currentColor" stroke-width="1.4" aria-hidden="true">
<path d="M24 44V20"/>
<path d="M24 30c-6 0-10-4-10-10 6 0 10 4 10 10ZM24 24c0-7 4-12 11-12 0 7-4 12-11 12Z"/>
<path d="M16 44h16"/>
<circle class="sp-seed__dot" cx="24" cy="8" r="2.2"/>
</svg>
</div>
<h3 class="disp sp-card__name" id="sp-canopy">Canopy</h3>
<p class="sp-card__sub">Seed money for teams whose prototype already does the thing, and who now need to make a thousand of it.</p>
<ul class="sp-facts">
<li><span>Seed investment</span> <em>Equity</em></li>
<li><span>First cheques €150k–€600k</span> <em>Lead or co</em></li>
<li><span>Follow-on reserved</span> <em>Pro rata</em></li>
<li><span>For teams already past a working prototype</span> <em>TRL 5+</em></li>
</ul>
<div class="sp-card__foot">
<a class="btn btn--line" href="#apply">Pitch Canopy <svg class="arr" viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M2 12 12 2M4 2h8v8"/></svg></a>
<span class="mono sp-card__meta">Rolling intake, no cohort</span>
</div>
</div>
</article>
</div>
</div>
<div class="sp-grow-head">
<div>
<p class="kick">Inventory — 10 items</p>
<h3 class="disp sp-h3">What grows here</h3>
</div>
<p class="mono sp-grow-note">What founders actually get, itemised. No "ecosystem", no "network effects" — just the list.</p>
</div>
<div class="sp-grow">
<aside class="sp-aside" aria-label="Illustration for the active item">
<div class="sp-stick">
<div class="sp-prog" aria-hidden="true"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></div>
<figure class="sp-frame ticks" style="margin:0">
<div class="sp-frame__in">
<img class="is-top" src="assets/proto.jpg" width="1600" height="1067" loading="lazy" alt="A row of 3D printers building prototype parts in the workshop">
<img src="assets/studio.jpg" width="2000" height="1324" loading="lazy" alt="Bright loft studio with long desks and tall windows">
<img src="assets/team.jpg" width="1600" height="1066" loading="lazy" alt="A founding team planning at a whiteboard">
<figcaption class="mono sp-cap">Fig. 01 — Print bay, Halle 3</figcaption>
</div>
</figure>
<div class="sp-count">
<p class="sp-count__n" style="margin:0" aria-live="polite"><b class="sp-count__cur" style="font-weight:inherit">01</b><span>/10</span></p>
<p class="mono sp-count__lbl" style="margin:0">Machine shop & wet lab</p>
</div>
</div>
</aside>
<div class="sp-list-wrap">
<ol class="sp-list">
<li class="sp-row" data-img="0"><button class="sp-row__btn" type="button" aria-expanded="false" aria-controls="sp-d1"><span class="sp-row__in"><span class="sp-row__num">01</span><span class="sp-row__t">Machine shop & wet lab access</span><span class="sp-row__pm" aria-hidden="true">+</span></span></button>
<div class="sp-row__d" id="sp-d1"><div><p>Five-axis mill, laser cutter, pick-and-place and a fume-hooded bench, unlocked 7am to midnight.</p></div></div></li>
<li class="sp-row" data-img="0"><button class="sp-row__btn" type="button" aria-expanded="false" aria-controls="sp-d2"><span class="sp-row__in"><span class="sp-row__num">02</span><span class="sp-row__t">Prototype budget</span><span class="sp-row__pm" aria-hidden="true">+</span></span></button>
<div class="sp-row__d" id="sp-d2"><div><p>A parts budget kept apart from the cheque, so salaries never lose a fight with stepper motors.</p></div></div></li>
<li class="sp-row" data-img="0"><button class="sp-row__btn" type="button" aria-expanded="false" aria-controls="sp-d3"><span class="sp-row__in"><span class="sp-row__num">03</span><span class="sp-row__t">Supply-chain intros <small>EU contract manufacturers</small></span><span class="sp-row__pm" aria-hidden="true">+</span></span></button>
<div class="sp-row__d" id="sp-d3"><div><p>Warm handshakes with European factories that quote batches of fifty and actually answer email.</p></div></div></li>
<li class="sp-row" data-img="1"><button class="sp-row__btn" type="button" aria-expanded="false" aria-controls="sp-d4"><span class="sp-row__in"><span class="sp-row__num">04</span><span class="sp-row__t">Regulatory path <small>CE / MDR</small></span><span class="sp-row__pm" aria-hidden="true">+</span></span></button>
<div class="sp-row__d" id="sp-d4"><div><p>Your certification route drawn up in week two, before the design paints itself into a corner.</p></div></div></li>
<li class="sp-row" data-img="1"><button class="sp-row__btn" type="button" aria-expanded="false" aria-controls="sp-d5"><span class="sp-row__in"><span class="sp-row__num">05</span><span class="sp-row__t">Fundraise prep & investor room</span><span class="sp-row__pm" aria-hidden="true">+</span></span></button>
<div class="sp-row__d" id="sp-d5"><div><p>Deck teardowns, a data-room checklist and a closed room of investors who know what a BOM is.</p></div></div></li>
<li class="sp-row" data-img="1"><button class="sp-row__btn" type="button" aria-expanded="false" aria-controls="sp-d6"><span class="sp-row__in"><span class="sp-row__num">06</span><span class="sp-row__t">Hiring your first 5 engineers</span><span class="sp-row__pm" aria-hidden="true">+</span></span></button>
<div class="sp-row__d" id="sp-d6"><div><p>We source, screen and sit in on interviews until the first five desks have names on them.</p></div></div></li>
<li class="sp-row" data-img="1"><button class="sp-row__btn" type="button" aria-expanded="false" aria-controls="sp-d7"><span class="sp-row__in"><span class="sp-row__num">07</span><span class="sp-row__t">Pilot customers</span><span class="sp-row__pm" aria-hidden="true">+</span></span></button>
<div class="sp-row__d" id="sp-d7"><div><p>Farms, clinics and depots that have agreed to host a pilot before the enclosure is pretty.</p></div></div></li>
<li class="sp-row" data-img="2"><button class="sp-row__btn" type="button" aria-expanded="false" aria-controls="sp-d8"><span class="sp-row__in"><span class="sp-row__num">08</span><span class="sp-row__t">IP & patent strategy</span><span class="sp-row__pm" aria-hidden="true">+</span></span></button>
<div class="sp-row__d" id="sp-d8"><div><p>What to file, what to keep as a trade secret, and what is not worth the attorney hours.</p></div></div></li>
<li class="sp-row" data-img="2"><button class="sp-row__btn" type="button" aria-expanded="false" aria-controls="sp-d9"><span class="sp-row__in"><span class="sp-row__num">09</span><span class="sp-row__t">Grant writing <small>EU / national</small></span><span class="sp-row__pm" aria-hidden="true">+</span></span></button>
<div class="sp-row__d" id="sp-d9"><div><p>Applications drafted alongside you, because non-dilutive money spends exactly like the other kind.</p></div></div></li>
<li class="sp-row" data-img="2"><button class="sp-row__btn" type="button" aria-expanded="false" aria-controls="sp-d10"><span class="sp-row__in"><span class="sp-row__num">10</span><span class="sp-row__t">Demo Day in Halle 3</span><span class="sp-row__pm" aria-hidden="true">+</span></span></button>
<div class="sp-row__d" id="sp-d10"><div><p>Week sixteen ends on the shop floor: live hardware, a full room, and no slide-only demos allowed.</p></div></div></li>
</ol>
<p class="mono sp-end">— End of inventory. All ten come with Germinate; Canopy teams book them as needed.</p>
</div>
</div>
</div>
<script>
(function(){
const S=window.SP,root=document.getElementById('programs');
if(!root||!S)return;
const R=S.reduced;
if(R)root.classList.add('sp-rm');
S.rise(root.querySelector('.sp-h2'));
S.rise(root.querySelector('.sp-h3'));
const rows=[...root.querySelectorAll('.sp-row')];
const imgs=[...root.querySelectorAll('.sp-frame__in img')];
const segs=[...root.querySelectorAll('.sp-prog i')];
const cap=root.querySelector('.sp-cap');
const cur=root.querySelector('.sp-count__cur');
const lbl=root.querySelector('.sp-count__lbl');
const caps=['Fig. 01 — Print bay, Halle 3','Fig. 02 — Studio floor, north light','Fig. 03 — Tuesday whiteboard'];
const short=['Machine shop & wet lab','Prototype budget','Supply-chain intros','Regulatory path','Fundraise prep','First 5 engineers','Pilot customers','IP & patents','Grant writing','Demo Day'];
const pad=n=>String(n).padStart(2,'0');
let active=-1,shown=0,z=3,desk=false;
function showImg(n,dir){
if(n===shown)return;
const el=imgs[n];
imgs.forEach(i=>i.classList.remove('is-top'));
el.style.zIndex=++z;
shown=n;
if(R){cap.textContent=caps[n];return}
gsap.killTweensOf(el);
gsap.fromTo(el,{clipPath:dir>0?'inset(100% 0% 0% 0%)':'inset(0% 0% 100% 0%)',scale:1.14},
{clipPath:'inset(0% 0% 0% 0%)',duration:1,ease:'expo.inOut',overwrite:true});
gsap.to(el,{scale:1,duration:1.6,ease:'expo.out',delay:.05});
S.scramble(cap,{duration:.5,text:caps[n]});
}
function setActive(i){
if(i===active)return;
const dir=i>active?1:-1;
rows.forEach((r,k)=>{const on=k===i;r.classList.toggle('is-on',on);r.firstElementChild.setAttribute('aria-expanded',on)});
segs.forEach((s,k)=>s.classList.toggle('is-lit',k<=i));
active=i;
if(R){cur.textContent=pad(i+1);lbl.textContent=short[i]}
else{rollDigits(pad(i+1));S.scramble(lbl,{duration:.45,text:short[i]})}
showImg(+rows[i].dataset.img,dir);
}
/* counter: flick through random digits for a beat, then land on the real number */
let rollT=null;
function rollDigits(final){
clearInterval(rollT);let n=0;
rollT=setInterval(()=>{
if(++n>=6){clearInterval(rollT);cur.textContent=final;return}
cur.textContent=final.split('').map((c,k)=>k<Math.floor(n/3)?c:(Math.random()*10|0)).join('');
},45);
}
function closeAll(){
rows.forEach(r=>{r.classList.remove('is-on');r.firstElementChild.setAttribute('aria-expanded','false')});
active=-1;
}
/* desktop: the row crossing the viewport centre is active */
function pick(){
const cy=innerHeight/2;
let idx=-1;
for(let k=0;k<rows.length;k++){const b=rows[k].getBoundingClientRect();if(b.top<=cy&&b.bottom>cy){idx=k;break}}
if(idx<0){
const first=rows[0].getBoundingClientRect(),last=rows[rows.length-1].getBoundingClientRect();
idx=cy<first.top?0:cy>=last.bottom?rows.length-1:active;
}
if(idx>=0)setActive(idx);
}
rows.forEach((r,k)=>{
r.firstElementChild.addEventListener('click',()=>{
if(desk){
const b=r.getBoundingClientRect();
const y=b.top+scrollY-innerHeight/2+Math.min(b.height,120)/2;
if(S.lenis)S.lenis.scrollTo(y,{duration:1.1});else scrollTo({top:y,behavior:'auto'});
}else if(k===active){closeAll()}
else setActive(k);
});
});
setActive(0);
const mm=gsap.matchMedia();
mm.add('(min-width: 900px)',()=>{
desk=true;
const st=ScrollTrigger.create({trigger:root.querySelector('.sp-list'),start:'top bottom',end:'bottom top',
onUpdate:pick,onRefresh:pick});
pick();
return()=>{desk=false;st.kill()};
});
mm.add('(max-width: 899px)',()=>{
/* mobile: image sits on top and follows whichever row is open; open the first by default */
if(active<0)setActive(0);
});
})();
</script>
</section>
```
## `sections/04-specimens.html`
```html
<section id="specimens" class="ss" aria-labelledby="ss-title">
<style>
.ss{padding:clamp(80px,11vw,160px) 0 0;background:var(--bg);color:var(--ink);overflow:hidden}
/* ---------- header ---------- */
.ss-head{display:grid;grid-template-columns:1fr;gap:28px;padding-bottom:clamp(32px,4vw,56px);border-bottom:1px solid var(--ink)}
.ss-title{font-size:clamp(76px,15vw,236px);margin-top:18px}
.ss-side{display:flex;flex-direction:column;gap:22px;align-items:flex-start}
.ss-intro{margin:0;max-width:40ch;color:var(--mute);font-size:16px}
.ss-intro strong{color:var(--ink);font-weight:600}
.ss-filter{display:flex;flex-wrap:wrap;gap:8px;margin:0;padding:0;list-style:none}
.ss-chip{display:inline-flex;align-items:center;gap:8px;height:38px;padding:0 14px;font-family:var(--mono);font-size:12px;font-weight:500;
letter-spacing:.07em;text-transform:uppercase;box-shadow:inset 0 0 0 1px var(--ink);background:var(--bg);color:var(--ink);
transition:background-color .3s var(--ease)}
.ss-chip sup{font-size:9px;letter-spacing:.04em;color:var(--mute);transform:translateY(-2px);transition:color .3s var(--ease)}
.ss-chip:hover{background:var(--spore-soft)}
.ss-chip[aria-pressed="true"]{background:var(--spore)}
.ss-chip[aria-pressed="true"] sup{color:var(--ink)}
.ss-meta{margin:0;display:flex;align-items:center;gap:14px;width:100%;font-family:var(--mono);font-size:12px;letter-spacing:.08em;text-transform:uppercase;color:var(--mute)}
.ss-meta::before{content:"";flex:1;height:1px;background:var(--line);order:2}
.ss-count{color:var(--ink);font-weight:600;min-width:13ch;order:1}
.ss-meta-r{order:3}
@media (min-width:900px){
.ss-head{grid-template-columns:1.25fr 1fr;align-items:end;gap:48px}
.ss-side{padding-bottom:14px}
}
/* ---------- grid ---------- */
.ss-grid{list-style:none;margin:0;padding:clamp(28px,4vw,56px) 0 0;display:grid;grid-template-columns:1fr;gap:56px 28px;position:relative}
@media (min-width:600px){.ss-grid{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media (min-width:1024px){.ss-grid{grid-template-columns:repeat(3,minmax(0,1fr));gap:72px clamp(24px,2.6vw,40px)}}
.ss-card{min-width:0}
.ss-card.is-out{display:none}
.ss-card-in{display:flex;flex-direction:column;height:100%}
/* phones: horizontal snap scroller instead of one very long column */
@media (max-width:599px){
.ss-grid{display:flex;gap:16px;overflow-x:auto;overscroll-behavior-x:contain;scroll-snap-type:x mandatory;
margin-inline:calc(var(--gut) * -1);padding-inline:var(--gut);scroll-padding-inline:var(--gut);scrollbar-width:none}
.ss-grid::-webkit-scrollbar{display:none}
.ss-card{flex:0 0 84%;scroll-snap-align:start}
.ss-card:last-child{margin-right:var(--gut)}
}
.ss-swipe{display:none}
@media (max-width:599px){.ss-swipe{display:flex;justify-content:space-between;margin-top:18px;font-family:var(--mono);font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:var(--mute)}}
/* specimen frame */
.ss-frame{border:1px solid var(--line);padding:8px;background:var(--bg)}
.ss-ph{position:relative;overflow:hidden;aspect-ratio:4/5;background:var(--fog)}
.ss-ph img{width:100%;height:100%;object-fit:cover;transition:transform 1.1s var(--ease);will-change:transform}
.ss-tag{position:absolute;left:8px;top:8px;z-index:2;display:inline-flex;align-items:center;gap:7px;padding:5px 8px;background:var(--bg);
font-family:var(--mono);font-size:10.5px;font-weight:500;letter-spacing:.08em;text-transform:uppercase}
.ss-tag i{width:6px;height:6px;border-radius:50%;background:var(--spore);box-shadow:0 0 0 1px var(--ink)}
/* the ink go-square */
.ss-go{position:absolute;right:0;bottom:0;z-index:3;width:52px;height:52px;display:grid;place-items:center;background:var(--ink);color:#fff;
transform:translate(101%,0);transition:transform .55s var(--ease)}
.ss-go svg{width:16px;height:16px;transition:transform .45s var(--ease)}
/* crosshair follower */
.ss-x{position:absolute;left:0;top:0;z-index:2;pointer-events:none;opacity:0;transition:opacity .25s var(--ease)}
.ss-x-plus{position:absolute;left:-13px;top:-13px;width:26px;height:26px}
.ss-x-plus::before,.ss-x-plus::after{content:"";position:absolute;background:var(--ink)}
.ss-x-plus::before{left:12.5px;top:0;width:1px;height:26px}
.ss-x-plus::after{top:12.5px;left:0;height:1px;width:26px}
.ss-x-read{position:absolute;left:14px;top:12px;white-space:nowrap;padding:4px 7px;background:var(--ink);color:#fff;
font-family:var(--mono);font-size:10px;letter-spacing:.06em}
.ss-x-read b{color:var(--spore);font-weight:500}
@media (hover:hover) and (pointer:fine){
.ss-ph{cursor:none}
.ss-card:hover .ss-ph img{transform:scale(1.06)}
.ss-card:hover .ss-go{transform:none}
.ss-card:hover .ss-x.is-live{opacity:1}
.ss-go:hover svg{transform:translate(2px,-2px)}
}
@media (hover:none){.ss-go{transform:none;width:44px;height:44px}}
/* label below the photo */
.ss-lab{padding:18px 2px 0;display:flex;flex-direction:column;flex:1}
.ss-no{margin:0;display:flex;justify-content:space-between;font-family:var(--mono);font-size:11px;letter-spacing:.1em;text-transform:uppercase;color:var(--mute)}
.ss-no span:first-child{color:var(--ink);font-weight:600}
.ss-name{font-size:clamp(38px,3.3vw,46px);margin-top:12px;white-space:nowrap;overflow:hidden;text-overflow:clip}
.ss-what{margin:10px 0 18px;color:var(--mute);font-size:15px;line-height:1.45;flex:1}
.ss-data{display:grid;grid-template-columns:1.2fr .9fr .8fr;margin:0;border-top:1px solid var(--ink);border-bottom:1px solid var(--line)}
.ss-data div{padding:10px 10px 11px;border-left:1px solid var(--line);min-width:0}
.ss-data div:first-child{border-left:0;padding-left:0}
.ss-data dt{font-family:var(--mono);font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:var(--mute)}
.ss-data dd{margin:4px 0 0;font-family:var(--mono);font-size:13.5px;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
/* ---------- lab-log marquee ---------- */
.ss-log{margin-top:clamp(72px,9vw,128px);border-top:1px solid var(--ink);border-bottom:1px solid var(--line);display:flex;align-items:stretch;background:var(--bg)}
.ss-log-lbl{flex:none;display:flex;align-items:center;gap:9px;padding:0 18px 0 var(--gut);font-family:var(--mono);font-size:11px;font-weight:600;
letter-spacing:.1em;text-transform:uppercase;background:var(--spore);color:var(--ink);position:relative;z-index:1}
.ss-log-lbl i{width:7px;height:7px;border-radius:50%;background:var(--ink);animation:ss-blink 1.6s steps(2,start) infinite}
.ss-log-win{flex:1;min-width:0;overflow:hidden;-webkit-mask-image:linear-gradient(90deg,transparent,#000 5%,#000 95%,transparent);mask-image:linear-gradient(90deg,transparent,#000 5%,#000 95%,transparent)}
.ss-log-track{display:flex;width:max-content;animation:ss-crawl 70s linear infinite}
.ss-log:hover .ss-log-track{animation-play-state:paused}
.ss-log-set{display:flex;align-items:center;margin:0;padding:0;list-style:none}
.ss-log-set li{display:flex;align-items:center;gap:18px;padding:17px 0 17px 18px;font-family:var(--mono);font-size:13px;letter-spacing:.02em;white-space:nowrap}
.ss-log-set li b{font-weight:600}
.ss-log-set li::after{content:"";width:7px;height:7px;border-radius:50%;background:var(--spore);box-shadow:0 0 0 1px var(--ink);flex:none}
@keyframes ss-crawl{to{transform:translateX(-50%)}}
@keyframes ss-blink{to{visibility:hidden}}
.ss--static .ss-log-track,.ss--static .ss-log-lbl i{animation:none}
@media (max-width:599px){.ss-log-lbl span{display:none}.ss-log-lbl{padding-right:14px}}
@media (prefers-reduced-motion: reduce){
.ss-log-track,.ss-log-lbl i{animation:none}
.ss-ph img,.ss-go,.ss-go svg{transition:none}
}
</style>
<div class="wrap">
<header class="ss-head">
<div>
<p class="kick">003 — Specimens</p>
<h2 id="ss-title" class="disp ss-title">Grown here</h2>
</div>
<div class="ss-side">
<p class="ss-intro">Six of <strong>41</strong> companies we have backed. Each walked into Halle 3 with a sketch and a stubborn thesis, and walked out with hardware that works outside the lab.</p>
<div class="ss-filter" role="group" aria-label="Filter specimens by sector">
<button type="button" class="ss-chip" data-f="all" aria-pressed="true">All<sup>06</sup></button>
<button type="button" class="ss-chip" data-f="climate" aria-pressed="false">Climate<sup>03</sup></button>
<button type="button" class="ss-chip" data-f="robotics" aria-pressed="false">Robotics<sup>01</sup></button>
<button type="button" class="ss-chip" data-f="health" aria-pressed="false">Health<sup>01</sup></button>
<button type="button" class="ss-chip" data-f="mobility" aria-pressed="false">Mobility<sup>01</sup></button>
</div>
<p class="ss-meta"><span class="ss-count" aria-live="polite">06 specimens</span><span class="ss-meta-r">Sorted by SPC no.</span></p>
</div>
</header>
<ul class="ss-grid">
<li class="ss-card" data-s="climate">
<article class="ss-card-in">
<div class="ss-frame ticks">
<div class="ss-ph">
<img src="assets/p-drone.jpg" width="1200" height="1800" loading="lazy" alt="A small white quadcopter drone hovering low over a green field with a tree line behind">
<span class="ss-tag"><i></i>Climate / Agri</span>
<span class="ss-x" aria-hidden="true"><span class="ss-x-plus"></span><span class="ss-x-read">X <b>000</b> · Y <b>000</b></span></span>
<span class="ss-go" aria-hidden="true"><svg viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M2 12 12 2M4 2h8v8"/></svg></span>
</div>
</div>
<div class="ss-lab">
<p class="ss-no"><span>SPC-01</span><span>of 06</span></p>
<h3 class="disp ss-name">Kestrel Fields</h3>
<p class="ss-what">Crop-scouting drones that map disease before it is visible.</p>
<dl class="ss-data">
<div><dt>Sector</dt><dd>Climate / Agri</dd></div>
<div><dt>Stage</dt><dd>Seed</dd></div>
<div><dt>Raised</dt><dd>€2.4M</dd></div>
</dl>
</div>
</article>
</li>
<li class="ss-card" data-s="climate">
<article class="ss-card-in">
<div class="ss-frame ticks">
<div class="ss-ph">
<img src="assets/p-sensor.jpg" width="1200" height="1796" loading="lazy" alt="A white sensor box mounted on a post among rows of flowering plants inside a greenhouse">
<span class="ss-tag"><i></i>Climate</span>
<span class="ss-x" aria-hidden="true"><span class="ss-x-plus"></span><span class="ss-x-read">X <b>000</b> · Y <b>000</b></span></span>
<span class="ss-go" aria-hidden="true"><svg viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M2 12 12 2M4 2h8v8"/></svg></span>
</div>
</div>
<div class="ss-lab">
<p class="ss-no"><span>SPC-02</span><span>of 06</span></p>
<h3 class="disp ss-name">Loam</h3>
<p class="ss-what">Greenhouse climate sensors that run five years on one cell.</p>
<dl class="ss-data">
<div><dt>Sector</dt><dd>Climate</dd></div>
<div><dt>Stage</dt><dd>Pre-seed</dd></div>
<div><dt>Raised</dt><dd>€1.1M</dd></div>
</dl>
</div>
</article>
</li>
<li class="ss-card" data-s="health">
<article class="ss-card-in">
<div class="ss-frame ticks">
<div class="ss-ph">
<img src="assets/p-hand.jpg" width="1200" height="1800" loading="lazy" alt="A raised hand wearing a black articulated exoskeleton glove against a plain white wall">
<span class="ss-tag"><i></i>Health</span>
<span class="ss-x" aria-hidden="true"><span class="ss-x-plus"></span><span class="ss-x-read">X <b>000</b> · Y <b>000</b></span></span>
<span class="ss-go" aria-hidden="true"><svg viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M2 12 12 2M4 2h8v8"/></svg></span>
</div>
</div>
<div class="ss-lab">
<p class="ss-no"><span>SPC-03</span><span>of 06</span></p>
<h3 class="disp ss-name">Tendon</h3>
<p class="ss-what">A haptic rehab glove for stroke patients.</p>
<dl class="ss-data">
<div><dt>Sector</dt><dd>Health</dd></div>
<div><dt>Stage</dt><dd>Seed</dd></div>
<div><dt>Raised</dt><dd>€3.8M</dd></div>
</dl>
</div>
</article>
</li>
<li class="ss-card" data-s="mobility">
<article class="ss-card-in">
<div class="ss-frame ticks">
<div class="ss-ph">
<img src="assets/p-bike.jpg" width="1200" height="1800" loading="lazy" alt="A grey electric cargo bike with a front basket and rear rack parked on a rooftop under a cloudy sky">
<span class="ss-tag"><i></i>Mobility</span>
<span class="ss-x" aria-hidden="true"><span class="ss-x-plus"></span><span class="ss-x-read">X <b>000</b> · Y <b>000</b></span></span>
<span class="ss-go" aria-hidden="true"><svg viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M2 12 12 2M4 2h8v8"/></svg></span>
</div>
</div>
<div class="ss-lab">
<p class="ss-no"><span>SPC-04</span><span>of 06</span></p>
<h3 class="disp ss-name">Pedal Freight</h3>
<p class="ss-what">Modular cargo e-bikes for last-mile logistics.</p>
<dl class="ss-data">
<div><dt>Sector</dt><dd>Mobility</dd></div>
<div><dt>Stage</dt><dd>Seed</dd></div>
<div><dt>Raised</dt><dd>€1.6M</dd></div>
</dl>
</div>
</article>
</li>
<li class="ss-card" data-s="climate">
<article class="ss-card-in">
<div class="ss-frame ticks">
<div class="ss-ph">
<img src="assets/p-solar.jpg" width="1200" height="1600" loading="lazy" alt="A compact solar panel on top of an amber signal beacon fixed to a railing">
<span class="ss-tag"><i></i>Climate</span>
<span class="ss-x" aria-hidden="true"><span class="ss-x-plus"></span><span class="ss-x-read">X <b>000</b> · Y <b>000</b></span></span>
<span class="ss-go" aria-hidden="true"><svg viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M2 12 12 2M4 2h8v8"/></svg></span>
</div>
</div>
<div class="ss-lab">
<p class="ss-no"><span>SPC-05</span><span>of 06</span></p>
<h3 class="disp ss-name">Heliotile</h3>
<p class="ss-what">Off-grid solar beacons for remote infrastructure.</p>
<dl class="ss-data">
<div><dt>Sector</dt><dd>Climate</dd></div>
<div><dt>Stage</dt><dd>Seed</dd></div>
<div><dt>Raised</dt><dd>€2.9M</dd></div>
</dl>
</div>
</article>
</li>
<li class="ss-card" data-s="robotics">
<article class="ss-card-in">
<div class="ss-frame ticks">
<div class="ss-ph">
<img src="assets/p-arm.jpg" width="1200" height="1798" loading="lazy" alt="A dark six-axis robot arm with a gripper mounted on a white mobile base against a grey backdrop">
<span class="ss-tag"><i></i>Robotics</span>
<span class="ss-x" aria-hidden="true"><span class="ss-x-plus"></span><span class="ss-x-read">X <b>000</b> · Y <b>000</b></span></span>
<span class="ss-go" aria-hidden="true"><svg viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M2 12 12 2M4 2h8v8"/></svg></span>
</div>
</div>
<div class="ss-lab">
<p class="ss-no"><span>SPC-06</span><span>of 06</span></p>
<h3 class="disp ss-name">Gripwise</h3>
<p class="ss-what">A €9k cobot arm that learns a task from one demonstration.</p>
<dl class="ss-data">
<div><dt>Sector</dt><dd>Robotics</dd></div>
<div><dt>Stage</dt><dd>Series A</dd></div>
<div><dt>Raised</dt><dd>€7.2M</dd></div>
</dl>
</div>
</article>
</li>
</ul>
<p class="ss-swipe" aria-hidden="true"><span>Swipe the tray</span><span>→</span></p>
</div>
<div class="ss-log" aria-label="Lab log">
<p class="ss-log-lbl" style="margin:0"><i></i><span>Lab log</span></p>
<div class="ss-log-win">
<div class="ss-log-track">
<ul class="ss-log-set">
<li><span><b>SPC-01</b> · flight 212 flagged leaf blight on row 14, nine days before the eye could</span></li>
<li><span><b>SPC-02</b> · node 0031 still running on its first cell, day 1,140</span></li>
<li><span><b>SPC-03</b> · glove v7 cleared 10,000 flex cycles, knuckle seam intact</span></li>
<li><span><b>SPC-04</b> · cargo module swap now 40 s with one hex key</span></li>
<li><span><b>SPC-05</b> · beacon logged a −31 °C night, uptime unbroken</span></li>
<li><span><b>SPC-06</b> · arm learned box folding from one demo, two retries</span></li>
<li><span><b>HALLE 3</b> · reflow oven recalibrated, profile B locked in</span></li>
<li><span><b>SHOP</b> · prototype no. 312 off the printer, still warm</span></li>
</ul>
<ul class="ss-log-set" aria-hidden="true">
<li><span><b>SPC-01</b> · flight 212 flagged leaf blight on row 14, nine days before the eye could</span></li>
<li><span><b>SPC-02</b> · node 0031 still running on its first cell, day 1,140</span></li>
<li><span><b>SPC-03</b> · glove v7 cleared 10,000 flex cycles, knuckle seam intact</span></li>
<li><span><b>SPC-04</b> · cargo module swap now 40 s with one hex key</span></li>
<li><span><b>SPC-05</b> · beacon logged a −31 °C night, uptime unbroken</span></li>
<li><span><b>SPC-06</b> · arm learned box folding from one demo, two retries</span></li>
<li><span><b>HALLE 3</b> · reflow oven recalibrated, profile B locked in</span></li>
<li><span><b>SHOP</b> · prototype no. 312 off the printer, still warm</span></li>
</ul>
</div>
</div>
</div>
<script>
(function(){
const root=document.getElementById('specimens');if(!root)return;
const SPx=window.SP||{reduced:true,rise(){},scramble(el,o){if(o&&o.text!=null)el.textContent=o.text}};
const R=SPx.reduced;
if(R)root.classList.add('ss--static');
const grid=root.querySelector('.ss-grid');
const cards=[...root.querySelectorAll('.ss-card')];
const chips=[...root.querySelectorAll('.ss-chip')];
const countEl=root.querySelector('.ss-count');
SPx.rise(root.querySelector('.ss-title'));
/* --- cards: staggered clip-path reveal from the bottom --- */
if(!R){
gsap.set(cards,{clipPath:'inset(100% 0% 0% 0%)'});
ScrollTrigger.batch(cards,{start:'top 92%',once:true,onEnter:batch=>{
gsap.to(batch,{clipPath:'inset(0% 0% 0% 0%)',duration:1.15,ease:'expo.out',stagger:.12,clearProps:'clipPath'});
gsap.from(batch.map(c=>c.querySelector('.ss-ph img')),{scale:1.22,duration:1.6,ease:'expo.out',stagger:.12,clearProps:'transform'});
}});
}
/* --- filtering with Flip --- */
let busy=null;
function setFilter(f){
chips.forEach(c=>c.setAttribute('aria-pressed',String(c.dataset.f===f)));
const keep=cards.filter(c=>f==='all'||c.dataset.s===f);
const n=keep.length;
const txt=String(n).padStart(2,'0')+(n===1?' specimen':' specimens');
if(R){cards.forEach(c=>c.classList.toggle('is-out',!keep.includes(c)));countEl.textContent=txt;return}
SPx.scramble(countEl,{text:txt,duration:.55});
if(busy)busy.progress(1);
// make sure nothing is still mid-reveal before measuring
cards.forEach(c=>{gsap.set(c,{clearProps:'clipPath'})});
const state=Flip.getState(cards,{props:'opacity'});
cards.forEach(c=>c.classList.toggle('is-out',!keep.includes(c)));
if(grid.scrollLeft)grid.scrollLeft=0;
busy=Flip.from(state,{
duration:.8,ease:'expo.inOut',absolute:true,stagger:.035,
onEnter:els=>gsap.fromTo(els,{opacity:0,scale:.9},{opacity:1,scale:1,duration:.6,ease:'expo.out',delay:.25,clearProps:'transform,opacity'}),
onLeave:els=>gsap.to(els,{opacity:0,scale:.9,duration:.4,ease:'power2.in'}),
onComplete:()=>{busy=null;ScrollTrigger.refresh()}
});
}
chips.forEach(c=>c.addEventListener('click',()=>{if(c.getAttribute('aria-pressed')==='true')return;setFilter(c.dataset.f)}));
/* --- hover: crosshair follower, name scramble --- */
const fine=matchMedia('(hover:hover) and (pointer:fine)').matches;
cards.forEach(card=>{
const ph=card.querySelector('.ss-ph'),x=card.querySelector('.ss-x'),read=card.querySelector('.ss-x-read'),
name=card.querySelector('.ss-name');
name.dataset.final=name.textContent;
let scr=false;
card.addEventListener('pointerenter',e=>{
if(e.pointerType!=='mouse'||scr)return;scr=true;
SPx.scramble(name,{duration:.6});setTimeout(()=>scr=false,700);
});
if(!fine)return;
const qx=R?(v=>gsap.set(x,{x:v})):gsap.quickTo(x,'x',{duration:.25,ease:'power3.out'});
const qy=R?(v=>gsap.set(x,{y:v})):gsap.quickTo(x,'y',{duration:.25,ease:'power3.out'});
const bs=read.querySelectorAll('b');
ph.addEventListener('pointerenter',e=>{
const r=ph.getBoundingClientRect();gsap.set(x,{x:e.clientX-r.left,y:e.clientY-r.top});x.classList.add('is-live');
});
ph.addEventListener('pointermove',e=>{
const r=ph.getBoundingClientRect(),px=e.clientX-r.left,py=e.clientY-r.top;
qx(px);qy(py);
// flip the readout to the left near the right edge so it never clips
read.style.transform=px>r.width-120?'translateX(calc(-100% - 28px))':'';
bs[0].textContent=String(Math.round(px/r.width*1000)).padStart(3,'0');
bs[1].textContent=String(Math.round(py/r.height*1000)).padStart(3,'0');
});
ph.addEventListener('pointerleave',()=>x.classList.remove('is-live'));
});
})();
</script>
</section>
```
## `sections/05-germination.html`
```html
<section id="germination" class="sg" aria-labelledby="sg-title">
<style>
/* ---------- 05 germination: the 16-week residency as a growing stem ---------- */
.sg{position:relative;background:var(--bg);color:var(--ink);border-top:1px solid var(--line)}
.sg-pin{position:relative}
.sg-head{display:flex;align-items:flex-end;justify-content:space-between;gap:24px;padding-top:clamp(72px,10vw,120px)}
.sg-title{font-size:clamp(52px,13vw,84px);margin-top:18px;max-width:10ch}
.sg-read{display:none}
.sg-ruler{display:none}
.sg-stage{position:relative}
/* ---- flow layout (mobile, reduced motion, no JS): vertical stem on the left ---- */
.sg-track{position:relative;max-width:1440px;margin-inline:auto;padding:48px var(--gut) clamp(72px,10vw,120px)}
.sg-svg{position:absolute;left:0;top:0;width:100%;height:100%;overflow:visible;pointer-events:none}
.sg-ghost{fill:none;stroke:var(--line);stroke-width:1.5;stroke-dasharray:2 7;stroke-linecap:round}
.sg-stem{fill:none;stroke:var(--ink);stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round}
.sg-lf{fill:none;stroke:var(--ink);stroke-width:var(--sg-sw,1.25);stroke-linecap:round;stroke-linejoin:round}
.sg-core{fill:var(--spore);stroke:var(--ink);stroke-width:var(--sg-sw,1);transform-box:fill-box;transform-origin:center}
.sg-dot{fill:var(--spore);stroke:var(--ink);stroke-width:1.25}
.sg-halo{fill:none;stroke:var(--spore);stroke-width:1.5;transform-box:fill-box;transform-origin:center;animation:sg-pulse 1.8s var(--ease) infinite}
@keyframes sg-pulse{0%{transform:scale(.6);opacity:.9}100%{transform:scale(1.9);opacity:0}}
@media (prefers-reduced-motion: reduce){.sg-halo{animation:none;opacity:0}}
.sg-intro{position:relative;margin:0 0 72px;padding-left:52px;color:var(--mute);font-size:12px;line-height:1.7;text-transform:uppercase;letter-spacing:.06em;max-width:40ch}
.sg-intro b{color:var(--ink);font-weight:500}
.sg-phase{position:relative;padding-left:52px;margin-bottom:96px}
.sg-phase:last-child{margin-bottom:0}
.sg-idx{margin:0 0 10px;font-size:11px;color:var(--mute);text-transform:uppercase;letter-spacing:.08em}
.sg-word{display:inline-block;font-size:clamp(80px,23vw,176px);line-height:.84;padding:.08em 0 .04em;color:transparent;
-webkit-text-stroke:1px var(--ink);
background-image:linear-gradient(var(--ink),var(--ink));background-repeat:no-repeat;background-position:0 0;background-size:100% 100%;
-webkit-background-clip:text;background-clip:text;will-change:transform}
.sg--js .sg-word{background-size:0% 100%}
.sg-low{display:grid;gap:22px;margin-top:22px}
.sg-range{margin:0 0 14px;display:flex;flex-wrap:wrap;align-items:center;gap:10px;text-transform:uppercase;letter-spacing:.06em}
.sg-verb{background:var(--spore-soft);padding:3px 8px;font-size:11px;font-weight:500}
.sg-list{list-style:none;margin:0;padding:0;border-top:1px solid var(--line)}
.sg-list li{position:relative;padding:9px 0 9px 24px;border-bottom:1px solid var(--line);font-size:15px;line-height:1.4}
.sg-list li::before{content:"";position:absolute;left:0;top:18px;width:12px;height:1px;background:var(--ink)}
.sg-note{margin:0;padding:14px 16px 16px;border:1px solid var(--line);background:var(--bg);max-width:420px}
.sg-note figcaption{font-size:11px;color:var(--mute);text-transform:uppercase;letter-spacing:.08em;margin-bottom:8px;display:flex;align-items:center;gap:8px}
.sg-note figcaption::before{content:"";width:6px;height:6px;background:var(--spore);flex:none}
.sg-note blockquote{margin:0;font-size:15px;line-height:1.45}
/* ---- horizontal pinned layout (desktop, motion on) ---- */
.sg--h .sg-pin{height:100vh;height:100svh;display:grid;grid-template-rows:auto minmax(0,1fr) auto;overflow:hidden;padding-top:var(--hdr)}
.sg--h .sg-head{width:100%;padding-top:clamp(20px,3.4vh,36px)}
.sg--h .sg-title{font-size:clamp(48px,min(5.4vw,9vh),92px);max-width:none;margin-top:12px}
.sg--h .sg-read{display:block;text-align:right;flex:none}
.sg-read-l{margin:0 0 6px;font-size:11px;color:var(--mute);text-transform:uppercase;letter-spacing:.08em}
.sg-read-n{margin:0;font-family:var(--mono);font-weight:500;font-size:clamp(36px,min(4.6vw,7.5vh),68px);line-height:1;letter-spacing:-.03em;white-space:nowrap}
.sg-read-n b{font-weight:500;display:inline-block;min-width:2ch;text-align:right;background:var(--spore);padding:0 .08em;margin-left:.12em;line-height:.92}
.sg-read-n i{font-style:normal;font-size:.34em;color:var(--mute);letter-spacing:0;margin-left:.3em;vertical-align:top}
.sg--h .sg-stage{overflow:hidden}
.sg--h .sg-track{position:absolute;left:0;top:0;height:100%;max-width:none;margin:0;padding:0;will-change:transform}
.sg--h .sg-intro{position:absolute;left:var(--gut);top:calc(var(--sg-y) + 44px);margin:0;padding:0;width:var(--sg-iw,340px)}
.sg--h .sg-phase{position:absolute;top:0;height:100%;margin:0;padding:0;width:var(--sg-pw,560px);display:grid;grid-template-rows:var(--sg-y) minmax(0,1fr)}
.sg--h .sg-up{align-self:end;padding-bottom:40px}
.sg--h .sg-word{font-size:min(14vw,21vh,220px)}
.sg--h .sg-low{align-self:start;margin:0;padding-top:clamp(28px,4.4vh,42px);grid-template-columns:minmax(0,1fr) minmax(0,200px);gap:26px;align-items:start}
@media (max-height:760px){.sg--h .sg-list li{padding-block:7px}.sg--h .sg-list li::before{top:16px}.sg--h .sg-range{margin-bottom:10px}}
.sg--h .sg-note{max-width:none}
.sg--h .sg-note blockquote,.sg--h .sg-list li{font-size:14px}
.sg--h .sg-ruler{display:block;position:relative;border-top:1px solid var(--line);padding-top:10px;padding-bottom:14px}
.sg-rw{position:relative}
.sg-rbar{position:absolute;left:var(--gut);right:var(--gut);top:-12px;height:3px}
.sg-rbar i{position:absolute;inset:0;background:var(--spore);transform-origin:0 50%;transform:scaleX(0)}
.sg-ruler ol{list-style:none;margin:0;padding:0;display:grid;grid-template-columns:repeat(16,minmax(0,1fr))}
.sg-ruler li{position:relative;padding-top:12px;font-family:var(--mono);font-size:11px;color:var(--mute);letter-spacing:.04em}
.sg-ruler li::before{content:"";position:absolute;left:0;top:0;width:1px;height:8px;background:var(--line)}
.sg-ruler li.sg-ph::before{height:12px;top:-4px}
.sg-ruler li span{display:inline-block;padding:1px 4px;margin-left:-4px}
.sg-ruler li.sg-past{color:var(--ink)}
.sg-ruler li.sg-past::before{background:var(--ink)}
.sg-ruler li.sg-now span{background:var(--spore);color:var(--ink)}
.sg-ruler li em{font-style:normal;color:var(--mute);margin-left:6px;text-transform:uppercase}
@media (max-width:1279px){.sg-ruler li em{display:none}}
@media (max-height:760px){.sg--h .sg-idx{display:none}}
@media (min-width:600px){
.sg-low{grid-template-columns:minmax(0,1fr) minmax(0,260px);gap:32px;align-items:start;max-width:900px}
.sg-intro,.sg-phase{padding-left:72px}
}
</style>
<div class="sg-pin">
<div class="sg-head wrap">
<div>
<p class="kick">004 — Germination</p>
<h2 class="sg-title disp" id="sg-title">16 weeks, soil to bloom</h2>
</div>
<div class="sg-read" aria-hidden="true">
<p class="sg-read-l mono" id="sg-read-l">Soil · Diagnose</p>
<p class="sg-read-n">Week<b id="sg-read-n">01</b><i>/16</i></p>
</div>
</div>
<div class="sg-stage">
<div class="sg-track">
<svg class="sg-svg" aria-hidden="true" focusable="false">
<path class="sg-ghost" d=""/>
<path class="sg-stem" d=""/>
<!-- soil: a first root and a hooked shoot -->
<g class="sg-node">
<path class="sg-lf" pathLength="1" d="M0 0C-2 7-7 11-9 18"/>
<path class="sg-lf" pathLength="1" d="M0 0C3 6 2 12 7 17"/>
<path class="sg-lf" pathLength="1" d="M-5 10C-9 11-12 10-15 13"/>
<path class="sg-lf" pathLength="1" d="M0 0C1-9-2-15 3-22C7-27 12-24 10-19"/>
</g>
<!-- root: two seed leaves -->
<g class="sg-node">
<path class="sg-lf" pathLength="1" d="M0 0C-3 8-1 15-5 23"/>
<path class="sg-lf" pathLength="1" d="M0 0C2-12-2-22 0-34"/>
<path class="sg-lf" pathLength="1" d="M0-30C-6-40-18-43-27-36C-18-28-7-27 0-30Z"/>
<path class="sg-lf" pathLength="1" d="M0-33C5-42 16-45 23-39C16-31 6-30 0-33Z"/>
</g>
<!-- sprout: true leaves -->
<g class="sg-node">
<path class="sg-lf" pathLength="1" d="M0 0C-3-18 4-34 0-54"/>
<path class="sg-lf" pathLength="1" d="M-1-17C-12-22-23-18-29-7C-17-5-7-9-1-17Z"/>
<path class="sg-lf" pathLength="1" d="M1-31C12-38 25-36 31-25C19-21 8-24 1-31Z"/>
<path class="sg-lf" pathLength="1" d="M0-54C-8-62-8-73-2-81C6-73 6-62 0-54Z"/>
<path class="sg-lf" pathLength="1" d="M-1-67L-1-60"/>
</g>
<!-- bloom: flower head -->
<g class="sg-node">
<path class="sg-lf" pathLength="1" d="M0 0C3-20-4-40 0-62"/>
<path class="sg-lf" pathLength="1" d="M0-21C10-29 22-28 29-17C17-13 7-15 0-21Z"/>
<path class="sg-lf" pathLength="1" d="M-1-36C-12-43-25-40-31-30C-19-26-8-29-1-36Z"/>
<path class="sg-lf" pathLength="1" d="M0-66C-7-73-6-83 0-86C6-83 7-73 0-66"/>
<path class="sg-lf" pathLength="1" transform="rotate(72 0 -66)" d="M0-66C-7-73-6-83 0-86C6-83 7-73 0-66"/>
<path class="sg-lf" pathLength="1" transform="rotate(144 0 -66)" d="M0-66C-7-73-6-83 0-86C6-83 7-73 0-66"/>
<path class="sg-lf" pathLength="1" transform="rotate(216 0 -66)" d="M0-66C-7-73-6-83 0-86C6-83 7-73 0-66"/>
<path class="sg-lf" pathLength="1" transform="rotate(288 0 -66)" d="M0-66C-7-73-6-83 0-86C6-83 7-73 0-66"/>
<circle class="sg-core" cx="0" cy="-66" r="4.5"/>
</g>
<circle class="sg-halo" r="11" cx="0" cy="0"/>
<circle class="sg-dot" r="5.5" cx="0" cy="0"/>
</svg>
<p class="sg-intro mono"><b>Germinate residency</b><br>2 cohorts a year · 8 teams each<br>One stem, four phases. Scroll and it grows.</p>
<article class="sg-phase">
<div class="sg-up">
<p class="sg-idx mono">Phase 01 / 04</p>
<h3 class="sg-word disp">Soil</h3>
</div>
<div class="sg-low">
<div>
<p class="sg-range mono">Weeks 01–03 <span class="sg-verb">Diagnose</span></p>
<ul class="sg-list">
<li>Tech risk audit: which law of physics could still say no</li>
<li>Twenty customer interviews before anyone opens CAD</li>
<li>Shop induction: CNC, reflow oven, wet bench, RF room</li>
</ul>
</div>
<figure class="sg-note ticks">
<figcaption class="mono">Field note · Wk 02</figcaption>
<blockquote>Cut two features after one interview. Cheapest prototype we'll ever build.</blockquote>
</figure>
</div>
</article>
<article class="sg-phase">
<div class="sg-up">
<p class="sg-idx mono">Phase 02 / 04</p>
<h3 class="sg-word disp">Root</h3>
</div>
<div class="sg-low">
<div>
<p class="sg-range mono">Weeks 04–08 <span class="sg-verb">Build</span></p>
<ul class="sg-list">
<li>Prototype sprint: one revision a week, built in Halle 3</li>
<li>Supply-chain map down to the second-tier vendor</li>
<li>First hires, with a partner sitting in on the interviews</li>
</ul>
</div>
<figure class="sg-note ticks">
<figcaption class="mono">Field note · Wk 06</figcaption>
<blockquote>Rev C survived the drop test. Rev B now props the workshop door open.</blockquote>
</figure>
</div>
</article>
<article class="sg-phase">
<div class="sg-up">
<p class="sg-idx mono">Phase 03 / 04</p>
<h3 class="sg-word disp">Sprout</h3>
</div>
<div class="sg-low">
<div>
<p class="sg-range mono">Weeks 09–13 <span class="sg-verb">Prove</span></p>
<ul class="sg-list">
<li>Pilot with a customer who pays. Letters of intent don't count</li>
<li>Regulatory path drafted: CE, MDR or FCC, whichever bites first</li>
<li>Data room assembled while the numbers are still boring</li>
</ul>
</div>
<figure class="sg-note ticks">
<figcaption class="mono">Field note · Wk 11</figcaption>
<blockquote>First invoice went out on a Tuesday. We printed it twice: one to file, one to pin up.</blockquote>
</figure>
</div>
</article>
<article class="sg-phase">
<div class="sg-up">
<p class="sg-idx mono">Phase 04 / 04</p>
<h3 class="sg-word disp">Bloom</h3>
</div>
<div class="sg-low">
<div>
<p class="sg-range mono">Weeks 14–16 <span class="sg-verb">Raise</span></p>
<ul class="sg-list">
<li>Investor room: warm one-to-ones with funds that back hardware</li>
<li>Demo Day in Halle 3: working devices on the bench, ten slides max</li>
<li>Canopy follow-on held in reserve for the round after</li>
</ul>
</div>
<figure class="sg-note ticks">
<figcaption class="mono">Field note · Wk 16</figcaption>
<blockquote>Nobody asked for the deck. Everybody asked to hold the device.</blockquote>
</figure>
</div>
</article>
</div>
</div>
<div class="sg-ruler" aria-hidden="true">
<div class="wrap sg-rw"><div class="sg-rbar"><i></i></div><ol></ol></div>
</div>
</div>
<script>
(function(){
const sec=document.getElementById('germination');
if(!sec||!window.gsap)return;
const SPR=window.SP||{reduced:false};
const stage=sec.querySelector('.sg-stage'),track=sec.querySelector('.sg-track'),svg=sec.querySelector('.sg-svg');
const stem=svg.querySelector('.sg-stem'),ghost=svg.querySelector('.sg-ghost'),dot=svg.querySelector('.sg-dot'),halo=svg.querySelector('.sg-halo');
const phases=[...sec.querySelectorAll('.sg-phase')],words=phases.map(p=>p.querySelector('.sg-word'));
const nodes=[...svg.querySelectorAll('.sg-node')];
const nodeParts=nodes.map(n=>[...n.querySelectorAll('.sg-lf')]);
const cores=nodes.map(n=>n.querySelector('.sg-core'));
const intro=sec.querySelector('.sg-intro');
const readN=document.getElementById('sg-read-n'),readL=document.getElementById('sg-read-l');
const rbar=sec.querySelector('.sg-rbar i'),ol=sec.querySelector('.sg-ruler ol');
const START=[1,4,9,14];
const PHASE=['Soil · Diagnose','Root · Build','Sprout · Prove','Bloom · Raise'];
/* hand-set wobble of the stem: lateral offsets (x amplitude) and uneven spacing between control points */
const WOB=[0,-.55,.35,-.15,.8,.2,-.7,-.05,.5,-.4,.25,.9,-.45,.05,-.8,.4,.12,-.6,.7,-.25,.3,-.9,.45,0,-.3,.65,-.5,.2,-.1,.55];
const GAP=[1,.8,1.15,.9,1.25,.85,1,1.1,.75,1.2,.95,1.05,.8,1.3,.9,1];
/* build the ruler ticks */
const ticks=[];
for(let w=1;w<=16;w++){
const li=document.createElement('li');
const s=document.createElement('span');s.textContent=String(w).padStart(2,'0');li.appendChild(s);
const pi=START.indexOf(w);
if(pi>-1){li.classList.add('sg-ph');const e=document.createElement('em');e.textContent=PHASE[pi].split(' ')[0];li.appendChild(e)}
ol.appendChild(li);ticks.push(li);
}
const clamp=(v,a=0,b=1)=>v<a?a:v>b?b:v;
const lerp=(a,b,t)=>a+(b-a)*t;
let geo=null;
/* Catmull-Rom through the points, emitted as cubic beziers */
function curve(p){
let d='M'+p[0][0].toFixed(1)+' '+p[0][1].toFixed(1);
for(let i=0;i<p.length-1;i++){
const a=p[i-1]||p[i],b=p[i],c=p[i+1],e=p[i+2]||c;
d+='C'+(b[0]+(c[0]-a[0])/6).toFixed(1)+' '+(b[1]+(c[1]-a[1])/6).toFixed(1)+' '
+(c[0]-(e[0]-b[0])/6).toFixed(1)+' '+(c[1]-(e[1]-b[1])/6).toFixed(1)+' '
+c[0].toFixed(1)+' '+c[1].toFixed(1);
}
return d;
}
/* sample the path so a coordinate along the growth axis maps back to a path length */
function sample(axis){
const L=stem.getTotalLength(),cs=[],ls=[];
for(let l=0;l<=L;l+=6){const q=stem.getPointAtLength(l);cs.push(axis?q.y:q.x);ls.push(l)}
const q=stem.getPointAtLength(L);cs.push(axis?q.y:q.x);ls.push(L);
return {L,cs,ls};
}
function lenAt(c){
const cs=geo.cs,ls=geo.ls;
if(c<=cs[0])return 0;if(c>=cs[cs.length-1])return geo.L;
let lo=0,hi=cs.length-1;
while(hi-lo>1){const m=(lo+hi)>>1;if(cs[m]<c)lo=m;else hi=m}
return lerp(ls[lo],ls[hi],(c-cs[lo])/((cs[hi]-cs[lo])||1));
}
function placeNodes(rot,scale){
geo.nodeC.forEach((c,i)=>{
const q=stem.getPointAtLength(lenAt(c));
nodes[i].setAttribute('transform','translate('+q.x.toFixed(1)+' '+q.y.toFixed(1)+') rotate('+rot+') scale('+scale+')');
nodes[i].style.setProperty('--sg-sw',(1.3/scale).toFixed(3));
});
}
/* horizontal geometry: weeks are evenly spaced along the track so the readout matches what you see */
function buildH(){
const vw=stage.clientWidth,sh=stage.clientHeight;
const u=Math.max(vw*.2,250),W0=Math.max(vw*.36,330);
const xEnd=W0+16*u,trackW=Math.round(xEnd+vw*.04+40);
track.style.width=trackW+'px';
track.style.setProperty('--sg-pw',Math.round(Math.min(600,vw*.56,3*u-120))+'px');
/* stem height: under the tallest phase word, with the spare stage height split above and below */
const upH=Math.max(...phases.map(p=>p.querySelector('.sg-up').offsetHeight));
const lowH=Math.max(...phases.map(p=>p.querySelector('.sg-low').offsetHeight));
const y=Math.round(upH+Math.max(4,(sh-upH-lowH)*.45));
track.style.setProperty('--sg-y',y+'px');
track.style.setProperty('--sg-iw',Math.round(Math.min(360,W0-80))+'px');
const lefts=START.map(w=>W0+(w-1)*u+44);
phases.forEach((p,i)=>p.style.left=lefts[i]+'px');
const amp=Math.min(34,sh*.05),base=Math.max(120,u*.5),pts=[];
let x=Math.max(20,vw*.03),k=0;
while(x<xEnd){pts.push([x,y+WOB[k%WOB.length]*amp]);x+=base*GAP[k%GAP.length];k++}
pts.push([xEnd,y+WOB[k%WOB.length]*amp*.5]);
const d=curve(pts);stem.setAttribute('d',d);ghost.setAttribute('d',d);
geo=Object.assign({mode:'h',vw,sh,u,W0,xEnd,trackW,dist:trackW-vw,y,lefts,nodeC:START.map(w=>W0+(w-1)*u),reach:Math.max(140,u*.7),ww:words.map(w=>w.offsetWidth)},sample(0));
stem.style.strokeDasharray=geo.L+' '+(geo.L+10);
placeNodes(0,Math.min(1.35,Math.max(1,sh/480)));
}
/* vertical geometry for the stacked timeline */
function buildV(){
const cs=getComputedStyle(track),pl=parseFloat(cs.paddingLeft)||16;
const H=track.offsetHeight,W=track.offsetWidth,wide=W>=600;
const x0=pl+(wide?26:18),y0=24,yEnd=H-parseFloat(cs.paddingBottom)+8;
const amp=wide?9:6,pts=[];let y=y0,k=0;
while(y<yEnd){pts.push([x0+WOB[k%WOB.length]*amp,y]);y+=110*GAP[k%GAP.length];k++}
pts.push([x0,yEnd]);
const d=curve(pts);stem.setAttribute('d',d);ghost.setAttribute('d',d);
const nodeC=phases.map(p=>p.offsetTop-40);
geo=Object.assign({mode:'v',y0,yEnd,nodeC,reach:150},sample(1));
stem.style.strokeDasharray=geo.L+' '+(geo.L+10);
placeNodes(90,wide?.85:.7);
}
/* grow the stem to coordinate c on its growth axis; leaves draw once the tip has passed their node */
function drawTo(c){
const len=lenAt(c);
stem.style.strokeDashoffset=(geo.L-len).toFixed(1);
const q=stem.getPointAtLength(len);
dot.setAttribute('cx',q.x.toFixed(1));dot.setAttribute('cy',q.y.toFixed(1));
halo.setAttribute('cx',q.x.toFixed(1));halo.setAttribute('cy',q.y.toFixed(1));
const vis=len>2?1:0;dot.style.opacity=vis;halo.style.visibility=vis?'visible':'hidden';
geo.nodeC.forEach((nc,i)=>{
const lp=clamp((c-nc)/geo.reach),parts=nodeParts[i],n=parts.length;
parts.forEach((pa,j)=>{const s=clamp((lp-j/n*.55)/.45);pa.style.strokeDashoffset=(1-s).toFixed(3)});
if(cores[i]){const s=clamp((lp-.85)/.15);cores[i].style.transform='scale('+s.toFixed(3)+')'}
});
}
function initParts(){nodeParts.forEach(ps=>ps.forEach(p=>{p.style.strokeDasharray='1 1.01';p.style.strokeDashoffset='1'}))}
let lastWeek=0;
function renderH(p){
if(!geo||geo.mode!=='h')return;
const g=geo,tx=-p*g.dist;
track.style.transform='translate3d('+tx.toFixed(1)+'px,0,0)';
/* tip rides near the middle of the screen, easing in from the left at the start and out to the end */
const s0=g.vw*.3,s1=g.vw*.5,s2=g.xEnd-g.dist;
const ts=p<.08?lerp(s0,s1,p/.08):p<.88?s1:lerp(s1,s2,(p-.88)/.12);
const tip=ts+p*g.dist;
drawTo(tip);
phases.forEach((ph,i)=>{
const left=g.lefts[i]+tx;
/* ink fills the outlined word in step with the growing tip (which rides the screen centre) */
/* the phase word lags behind the bullets a little */
const px=Math.min(140,Math.max(0,g.vw*.95-left)*.09);
const f=clamp((tip-g.lefts[i]-px+12)/(g.ww[i]+24));
words[i].style.backgroundSize=(f*100).toFixed(1)+'% 100%';
words[i].style.transform='translate3d('+px.toFixed(1)+'px,0,0)';
});
intro.style.transform='translate3d('+Math.min(60,p*g.dist*.08).toFixed(1)+'px,0,0)';
const wf=1+(tip-g.W0)/g.u,wk=clamp(Math.floor(wf),1,16);
rbar.style.transform='scaleX('+clamp((wf-1)/16).toFixed(4)+')';
if(wk!==lastWeek){
lastWeek=wk;
readN.textContent=String(wk).padStart(2,'0');
let pi=0;START.forEach((s,i)=>{if(wk>=s)pi=i});
readL.textContent=PHASE[pi];
ticks.forEach((t,i)=>{t.classList.toggle('sg-past',i+1<=wk);t.classList.toggle('sg-now',i+1===wk)});
gsap.fromTo(readN,{yPercent:35,opacity:.2},{yPercent:0,opacity:1,duration:.35,ease:'power3.out',overwrite:true});
}
}
function renderV(p){
if(!geo||geo.mode!=='v')return;
drawTo(lerp(geo.y0,geo.yEnd,p));
}
function clearInline(){
track.style.cssText='';phases.forEach(p=>p.style.left='');
words.forEach(w=>{w.style.transform='';w.style.backgroundSize=''});
intro.style.transform='';
}
/* reduced motion: stacked layout, everything drawn and filled, no pin */
if(SPR.reduced){
const full=()=>{buildV();drawTo(geo.yEnd+999)};
full();
window.addEventListener('load',full);
let rt;window.addEventListener('resize',()=>{clearTimeout(rt);rt=setTimeout(full,150)});
return;
}
sec.classList.add('sg--js');
initParts();
const mm=gsap.matchMedia();
mm.add('(min-width: 900px)',()=>{
sec.classList.add('sg--h');
lastWeek=0;
const proxy={p:0};
const rebuild=()=>{track.style.transform='';buildH()};
rebuild();
const tw=gsap.to(proxy,{p:1,ease:'none',onUpdate:()=>renderH(proxy.p),
scrollTrigger:{trigger:sec,start:'top top',end:()=>'+='+Math.round(geo.dist*1.15),pin:true,scrub:.8,anticipatePin:1,invalidateOnRefresh:true}});
const after=()=>renderH(proxy.p);
ScrollTrigger.addEventListener('refreshInit',rebuild);
ScrollTrigger.addEventListener('refresh',after);
renderH(0);
return ()=>{
ScrollTrigger.removeEventListener('refreshInit',rebuild);
ScrollTrigger.removeEventListener('refresh',after);
tw.scrollTrigger&&tw.scrollTrigger.kill();
sec.classList.remove('sg--h');clearInline();
};
});
mm.add('(max-width: 899.98px)',()=>{
const proxy={p:0};
const rebuild=()=>buildV();
rebuild();
gsap.to(proxy,{p:1,ease:'none',onUpdate:()=>renderV(proxy.p),
scrollTrigger:{trigger:track,start:'top 68%',end:'bottom 68%',scrub:.6,invalidateOnRefresh:true}});
words.forEach(w=>gsap.fromTo(w,{backgroundSize:'0% 100%'},{backgroundSize:'100% 100%',ease:'none',
scrollTrigger:{trigger:w,start:'top 88%',end:'top 42%',scrub:.4}}));
const after=()=>renderV(proxy.p);
ScrollTrigger.addEventListener('refreshInit',rebuild);
ScrollTrigger.addEventListener('refresh',after);
renderV(0);
return ()=>{
ScrollTrigger.removeEventListener('refreshInit',rebuild);
ScrollTrigger.removeEventListener('refresh',after);
clearInline();
};
});
})();
</script>
</section>
```
## `sections/06-voices.html`
```html
<section id="voices" class="sv" aria-labelledby="sv-title">
<style>
.sv{padding:clamp(88px,10vw,168px) 0 clamp(88px,10vw,160px);background:var(--bg);position:relative}
.sv-sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}
/* ---------- head ---------- */
.sv-head{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,360px);gap:24px 48px;align-items:end;
padding-bottom:clamp(28px,3.5vw,48px);border-bottom:1px solid var(--line);margin-bottom:clamp(32px,4vw,64px)}
.sv-title{font-size:clamp(56px,9.4vw,164px);margin-top:22px}
.sv-title .w,.sv-fh .w{padding-top:.14em;margin-top:-.14em}
.sv-title em{font-style:normal;color:var(--spore-deep)}
.sv-lede{margin:0;color:var(--mute);font-size:15px;max-width:36ch}
.sv-lede b{color:var(--ink);font-weight:500}
/* ---------- testimonial stage ---------- */
.sv-stage{display:grid;grid-template-columns:minmax(0,5fr) minmax(0,7fr);gap:clamp(32px,5vw,96px);align-items:stretch;outline-offset:8px}
.sv-frame{border:1px solid var(--line);padding:10px;align-self:start;background:var(--bg)}
.sv-pic{position:relative;aspect-ratio:1/1;overflow:hidden;background:var(--fog)}
.sv-pic img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;opacity:0}
.sv-pic img.is-on{opacity:1}
.sv-cells{position:absolute;inset:0;pointer-events:none;z-index:2}
.sv-cell{position:absolute;width:calc(12.5% + 1px);height:calc(12.5% + 1px);background:var(--bg);transform:scale(0);will-change:transform}
.sv-tag{position:absolute;left:10px;top:10px;z-index:3;background:var(--ink);color:#fff;font-family:var(--mono);font-size:11px;
letter-spacing:.08em;text-transform:uppercase;padding:6px 9px;display:inline-flex;gap:8px;align-items:center}
.sv-tag::before{content:"";width:6px;height:6px;background:var(--spore)}
.sv-cap{display:flex;justify-content:space-between;gap:12px;padding-top:10px;font-family:var(--mono);font-size:11px;
letter-spacing:.08em;text-transform:uppercase;color:var(--mute)}
.sv-cap span:last-child{color:var(--ink);text-align:right}
.sv-copy{display:flex;flex-direction:column;min-width:0}
.sv-mark{width:52px;height:52px;background:var(--spore);display:grid;place-items:center;flex:none;margin-bottom:clamp(20px,2.4vw,36px)}
.sv-mark svg{width:22px;height:22px}
.sv-slides{display:grid;flex:1}
.sv-slide{grid-area:1/1;visibility:hidden;display:flex;flex-direction:column;justify-content:space-between;gap:32px}
.sv-slide.is-on{visibility:visible}
.sv-quote{margin:0;font-family:var(--body);font-weight:400;font-size:clamp(24px,2.6vw,40px);line-height:1.18;letter-spacing:-.022em;text-wrap:pretty}
.sv-quote mark{background:none;color:inherit}
.sv-quote mark .w>span,.sv-quote mark:not(:has(.w)){background:linear-gradient(var(--spore-soft),var(--spore-soft)) 0 88%/100% .42em no-repeat}
.sv-quote mark .w+.w>span,.sv-quote mark .w:not(:last-child)>span{padding-right:.28em;margin-right:-.28em}
.sv-who{display:grid;grid-template-columns:auto 1fr;gap:4px 20px;align-items:end;padding-top:22px;border-top:1px solid var(--line)}
.sv-name{font-size:clamp(34px,3.4vw,54px);grid-row:span 2}
.sv-name .w{padding-top:.16em;margin-top:-.16em}
.sv-role{font-family:var(--mono);font-size:12px;letter-spacing:.08em;text-transform:uppercase;color:var(--ink)}
.sv-co{font-family:var(--mono);font-size:12px;letter-spacing:.02em;color:var(--mute)}
.sv-ctrl{display:flex;align-items:center;gap:20px;margin-top:clamp(28px,3vw,44px)}
.sv-btns{display:flex;gap:8px}
.sv-nav{width:56px;height:56px;display:grid;place-items:center;box-shadow:inset 0 0 0 1px var(--ink);background:var(--bg);color:var(--ink);
transition:background-color .3s var(--ease)}
.sv-nav svg{width:18px;height:18px;transition:transform .35s var(--ease)}
.sv-nav:hover{background:var(--spore)}
.sv-nav--prev:hover svg{transform:translateX(-3px)}
.sv-nav--next:hover svg{transform:translateX(3px)}
.sv-count{font-family:var(--mono);font-size:13px;letter-spacing:.06em;white-space:nowrap;min-width:6.5ch}
.sv-count b{font-weight:600}
.sv-count span{color:var(--mute)}
.sv-bar{flex:1;display:flex;gap:6px;min-width:60px}
.sv-seg{flex:1;height:3px;background:var(--line);position:relative;overflow:hidden}
.sv-seg i{position:absolute;inset:0;background:var(--spore);transform-origin:0 50%;transform:scaleX(0)}
.sv-state{font-family:var(--mono);font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:var(--mute);white-space:nowrap;min-width:6ch;text-align:right}
/* ---------- FAQ ---------- */
.sv-faq{display:grid;grid-template-columns:minmax(0,4fr) minmax(0,8fr);gap:clamp(32px,5vw,96px);
margin-top:clamp(96px,11vw,176px);padding-top:clamp(32px,4vw,56px);border-top:1px solid var(--ink)}
.sv-stick{position:sticky;top:calc(var(--hdr) + 32px)}
.sv-fh{font-size:clamp(56px,7.4vw,124px);margin:18px 0 22px}
.sv-still{margin:0;color:var(--mute);line-height:1.7}
.sv-still a{color:var(--ink);text-decoration:none;background:linear-gradient(var(--spore),var(--spore)) 0 100%/100% 2px no-repeat;
transition:background-size .35s var(--ease);padding:1px 0}
.sv-still a:hover{background-size:100% 100%}
.sv-list{border-top:1px solid var(--line)}
.sv-row{border-bottom:1px solid var(--line)}
.sv-qh{margin:0}
.sv-q{width:100%;display:grid;grid-template-columns:44px minmax(0,1fr) 44px;gap:16px;align-items:center;text-align:left;padding:22px 0}
.sv-qn{font-family:var(--mono);font-size:12px;letter-spacing:.06em;color:var(--mute);transition:color .3s var(--ease)}
.sv-qt{font-family:var(--body);font-weight:500;font-size:clamp(18px,1.45vw,21px);line-height:1.3;letter-spacing:-.012em;transition:transform .45s var(--ease)}
.sv-ic{width:44px;height:44px;background:var(--spore);position:relative;transition:background-color .3s var(--ease)}
.sv-ic::before,.sv-ic::after{content:"";position:absolute;left:50%;top:50%;width:16px;height:1.5px;margin:-.75px 0 0 -8px;background:var(--ink);
transition:transform .5s var(--ease),background-color .3s var(--ease)}
.sv-ic::after{transform:rotate(90deg)}
.sv-q[aria-expanded="true"] .sv-ic::before{transform:rotate(135deg)}
.sv-q[aria-expanded="true"] .sv-ic::after{transform:rotate(225deg)}
.sv-q:hover .sv-qt{transform:translateX(6px)}
.sv-q:hover .sv-ic{background:var(--ink)}
.sv-q:hover .sv-ic::before,.sv-q:hover .sv-ic::after{background:var(--spore)}
.sv-q[aria-expanded="true"] .sv-qn{color:var(--ink)}
.sv-q:focus-visible{outline-offset:-2px}
.sv-a{overflow:hidden}
.sv-ai{padding:0 60px 28px 60px;color:var(--mute);max-width:68ch}
.sv-ai p{margin:0}
.sv-ai b{color:var(--ink);font-weight:500}
@media (max-width:899px){
.sv-head{grid-template-columns:1fr}
.sv-stage{grid-template-columns:1fr;gap:28px}
.sv-frame{max-width:560px;width:100%}
.sv-faq{grid-template-columns:1fr;gap:28px}
.sv-stick{position:static}
}
@media (max-width:560px){
.sv-who{grid-template-columns:1fr}
.sv-name{grid-row:auto;margin-bottom:6px}
.sv-ctrl{flex-wrap:wrap;gap:14px 16px}
.sv-bar{order:3;flex-basis:100%}
.sv-state{margin-left:auto}
.sv-nav{width:48px;height:48px}
.sv-q{grid-template-columns:minmax(0,1fr) 40px;gap:14px;padding:18px 0}
.sv-qn{display:none}
.sv-ic{width:40px;height:40px}
.sv-ai{padding:0 0 24px}
}
@media (prefers-reduced-motion: reduce){
.sv-nav,.sv-nav svg,.sv-qt,.sv-ic,.sv-ic::before,.sv-ic::after,.sv-still a{transition:none}
}
</style>
<div class="wrap">
<header class="sv-head">
<div>
<span class="kick">005 — Founders</span>
<h2 class="disp sv-title" id="sv-title">Ask the ones<br>who <em>grew</em></h2>
</div>
<p class="sv-lede" data-sp-reveal>Three founders from past cohorts, lightly edited for swearing. We asked one question: <b>what actually helped?</b></p>
</header>
<div class="sv-stage" tabindex="0" role="region" aria-roledescription="carousel" aria-label="Founder testimonials. Use the left and right arrow keys to switch.">
<figure class="sv-frame ticks" style="margin:0">
<div class="sv-pic">
<img class="is-on" src="assets/f1.jpg" width="900" height="900" loading="lazy" alt="Amara Okafor, CEO of Loam, smiling in front of a whiteboard covered in handwritten notes">
<img src="assets/f2.jpg" width="900" height="900" loading="lazy" alt="Tomás Ferreira, CTO of Gripwise, seated at a white desk in front of a painted brick wall">
<img src="assets/f3.jpg" width="900" height="900" loading="lazy" alt="Lena Voss, co-founder of Tendon, pointing towards the camera in a white shirt">
<span class="sv-tag" aria-hidden="true">Specimen <span class="sv-tagn">Loam</span></span>
<div class="sv-cells" aria-hidden="true"></div>
</div>
<figcaption class="sv-cap" aria-hidden="true"><span class="sv-capl">Fig. 05.1</span><span class="sv-capr">Climate · Pre-seed</span></figcaption>
</figure>
<div class="sv-copy">
<div class="sv-mark" aria-hidden="true"><svg viewBox="0 0 22 22" fill="var(--ink)"><path d="M0 22V12L5 0h4L6 10h4v12zM12 22V12L17 0h4l-3 10h4v12z"/></svg></div>
<div class="sv-slides">
<article class="sv-slide is-on" role="group" aria-roledescription="slide" aria-label="1 of 3" data-co="Loam" data-meta="Climate · Pre-seed">
<blockquote class="sv-quote">Week nine, 3am, our sensor board browned out at minus four in the cold room. Nobody told us to go home. A shop tech talked me through a <mark>full respin</mark>, and fresh boards were on the reflow bench before the first U-Bahn.</blockquote>
<div class="sv-who">
<p class="disp sv-name" style="margin:0">Amara Okafor</p>
<span class="sv-role">CEO</span>
<span class="sv-co">Loam — greenhouse sensors, five years on one cell</span>
</div>
</article>
<article class="sv-slide" role="group" aria-roledescription="slide" aria-label="2 of 3" aria-hidden="true" data-co="Gripwise" data-meta="Robotics · Series A">
<blockquote class="sv-quote">Every investor called a €9k arm a toy. Spore gave us bench time and one intro to a Neukölln bakery that became our <mark>first paying pilot</mark>. The arm learned to box pretzels from a single demo. Nobody says toy anymore.</blockquote>
<div class="sv-who">
<p class="disp sv-name" style="margin:0">Tomás Ferreira</p>
<span class="sv-role">CTO</span>
<span class="sv-co">Gripwise — cobot arm that learns from one demonstration</span>
</div>
</article>
<article class="sv-slide" role="group" aria-roledescription="slide" aria-label="3 of 3" aria-hidden="true" data-co="Tendon" data-meta="Health · Seed">
<blockquote class="sv-quote">CE marking under the MDR is where medical hardware goes to die. They sat with us through the technical file, found a notified body with an actual slot, and made us rewrite our clinical evaluation twice. <mark>Both times they were right.</mark></blockquote>
<div class="sv-who">
<p class="disp sv-name" style="margin:0">Lena Voss</p>
<span class="sv-role">Co-founder</span>
<span class="sv-co">Tendon — haptic rehab glove for stroke patients</span>
</div>
</article>
</div>
<div class="sv-ctrl">
<div class="sv-btns">
<button class="sv-nav sv-nav--prev" type="button" aria-label="Previous founder"><svg viewBox="0 0 18 18" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M16 9H2M8 3 2 9l6 6"/></svg></button>
<button class="sv-nav sv-nav--next" type="button" aria-label="Next founder"><svg viewBox="0 0 18 18" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M2 9h14M10 3l6 6-6 6"/></svg></button>
</div>
<p class="sv-count" style="margin:0" aria-hidden="true"><b class="sv-cur">01</b> <span>/ 03</span></p>
<div class="sv-bar" aria-hidden="true"><span class="sv-seg"><i></i></span><span class="sv-seg"><i></i></span><span class="sv-seg"><i></i></span></div>
<span class="sv-state" aria-hidden="true">Auto</span>
</div>
<p class="sv-sr" aria-live="polite" aria-atomic="true"></p>
</div>
</div>
<div class="sv-faq">
<div class="sv-side">
<div class="sv-stick">
<span class="kick">006 — FAQ</span>
<h2 class="disp sv-fh" id="sv-faq-title">Questions</h2>
<p class="sv-still mono">Still stuck?<br><a href="mailto:[email protected]">[email protected]</a></p>
</div>
</div>
<div class="sv-list" aria-labelledby="sv-faq-title" role="list">
<div class="sv-row" role="listitem">
<h3 class="sv-qh"><button class="sv-q" type="button" id="sv-q1" aria-expanded="true" aria-controls="sv-a1"><span class="sv-qn">01</span><span class="sv-qt">Who is Germinate for?</span><span class="sv-ic" aria-hidden="true"></span></button></h3>
<div class="sv-a" id="sv-a1" role="region" aria-labelledby="sv-q1"><div class="sv-ai"><p>Technical teams of two to four building something you could drop on your foot: robots, climate hardware, medical devices, sensors with a brain. Bring a working bench prototype, however ugly, and a reason to think someone will pay for the next version. <b>Eight teams per cohort</b>, so we read every application twice.</p></div></div>
</div>
<div class="sv-row" role="listitem">
<h3 class="sv-qh"><button class="sv-q" type="button" id="sv-q2" aria-expanded="false" aria-controls="sv-a2"><span class="sv-qn">02</span><span class="sv-qt">Do you take equity?</span><span class="sv-ic" aria-hidden="true"></span></button></h3>
<div class="sv-a" id="sv-a2" role="region" aria-labelledby="sv-q2" hidden><div class="sv-ai"><p>Yes. Germinate comes with a <b>€150k cheque for roughly 7%</b>, on terms we publish before you apply. That covers the money, 16 weeks in Halle 3, machine shop and wet lab time, and the people who run them. No service fees, no invoices disguised as "studio credits".</p></div></div>
</div>
<div class="sv-row" role="listitem">
<h3 class="sv-qh"><button class="sv-q" type="button" id="sv-q3" aria-expanded="false" aria-controls="sv-a3"><span class="sv-qn">03</span><span class="sv-qt">Can we join from outside Berlin?</span><span class="sv-ic" aria-hidden="true"></span></button></h3>
<div class="sv-a" id="sv-a3" role="region" aria-labelledby="sv-q3" hidden><div class="sv-ai"><p>Most of our teams do. We ask you to <b>relocate to Berlin for the 16 weeks</b>: hardware is a contact sport and the reflow oven will not come to you. We write the visa letters, help you find a flat near Wedding, and know which Bürgeramt appointments are worth fighting for.</p></div></div>
</div>
<div class="sv-row" role="listitem">
<h3 class="sv-qh"><button class="sv-q" type="button" id="sv-q4" aria-expanded="false" aria-controls="sv-a4"><span class="sv-qn">04</span><span class="sv-qt">What stage should we be at?</span><span class="sv-ic" aria-hidden="true"></span></button></h3>
<div class="sv-a" id="sv-a4" role="region" aria-labelledby="sv-q4" hidden><div class="sv-ai"><p>Somewhere between <b>"it works on the bench"</b> and <b>"it works for one customer"</b>. Usually pre-seed, sometimes early seed. Still choosing which problem to solve? Come back in six months. Holding a Series A term sheet? You probably don't need us, but we'll happily buy the coffee.</p></div></div>
</div>
<div class="sv-row" role="listitem">
<h3 class="sv-qh"><button class="sv-q" type="button" id="sv-q5" aria-expanded="false" aria-controls="sv-a5"><span class="sv-qn">05</span><span class="sv-qt">Do you invest in software-only companies?</span><span class="sv-ic" aria-hidden="true"></span></button></h3>
<div class="sv-a" id="sv-a5" role="region" aria-labelledby="sv-q5" hidden><div class="sv-ai"><p>Rarely. The exception is <b>edge AI that lives or dies on its hardware</b>: models squeezed onto a sensor, a drone or a robot arm, where the silicon and the software get designed together. If your product ships as an app-store link, we are the wrong soil.</p></div></div>
</div>
<div class="sv-row" role="listitem">
<h3 class="sv-qh"><button class="sv-q" type="button" id="sv-q6" aria-expanded="false" aria-controls="sv-a6"><span class="sv-qn">06</span><span class="sv-qt">What happens after Demo Day?</span><span class="sv-ic" aria-hidden="true"></span></button></h3>
<div class="sv-a" id="sv-a6" role="region" aria-labelledby="sv-q6" hidden><div class="sv-ai"><p>You keep your badge. Alumni keep <b>access to the shop</b> in Halle 3, and our investment program <b>Canopy</b> holds follow-on capital in reserve for residency companies. It seems to work: 74% of our teams raised a follow-on round within 18 months.</p></div></div>
</div>
<div class="sv-row" role="listitem">
<h3 class="sv-qh"><button class="sv-q" type="button" id="sv-q7" aria-expanded="false" aria-controls="sv-a7"><span class="sv-qn">07</span><span class="sv-qt">When is the next intake?</span><span class="sv-ic" aria-hidden="true"></span></button></h3>
<div class="sv-a" id="sv-a7" role="region" aria-labelledby="sv-q7" hidden><div class="sv-ai"><p>The <b>Autumn cohort</b>. Applications close on <b>30 Nov 2026</b>. Every team hears back from a human, and shortlisted teams spend a day with us in Halle 3 before either side decides anything.</p></div></div>
</div>
</div>
</div>
</div>
<script>
(function(){
const SP=window.SP, root=document.getElementById('voices');
if(!root||!SP)return;
const R=SP.reduced;
/* ---------- heading ---------- */
SP.rise(root.querySelector('.sv-title'));
SP.rise(root.querySelector('.sv-fh'));
/* ---------- testimonial stage ---------- */
const stage=root.querySelector('.sv-stage');
const slides=[...root.querySelectorAll('.sv-slide')];
const imgs=[...root.querySelectorAll('.sv-pic img')];
const fills=[...root.querySelectorAll('.sv-seg i')];
const curEl=root.querySelector('.sv-cur'), stateEl=root.querySelector('.sv-state');
const tagN=root.querySelector('.sv-tagn'), capL=root.querySelector('.sv-capl'), capR=root.querySelector('.sv-capr');
const live=root.querySelector('.sv-sr');
const N=slides.length, DUR=7;
let cur=0, tl=null, timer=null;
// split each slide's quote + name into words once
const words=slides.map(s=>({q:SP.split(s.querySelector('.sv-quote')),n:SP.split(s.querySelector('.sv-name')),
m:[...s.querySelectorAll('.sv-role,.sv-co')]}));
// 8x8 white cell grid over the portrait
const G=8, cellBox=root.querySelector('.sv-cells'), cells=[];
for(let r=0;r<G;r++)for(let c=0;c<G;c++){
const d=document.createElement('span');d.className='sv-cell';
d.style.left=(c*100/G)+'%';d.style.top=(r*100/G)+'%';
d._r=r;d._c=c;d._j=Math.random();cellBox.appendChild(d);cells.push(d);
}
// diagonal wave with a little jitter; dir flips the corner it starts from
const order=dir=>(i)=>{const c=cells[i];const k=dir>0?(c._r+c._c):((G-1-c._r)+(G-1-c._c));return k*.032+c._j*.14};
function setMeta(i){
const s=slides[i];
const lbl='Fig. 05.'+(i+1);
SP.scramble(curEl,{text:String(i+1).padStart(2,'0'),duration:.4});
SP.scramble(capL,{text:lbl,duration:.5});SP.scramble(capR,{text:s.dataset.meta,duration:.6});
}
function paint(i){ // progress segments: done / current / todo
fills.forEach((f,k)=>gsap.set(f,{scaleX:k<i?1:0}));
}
function show(i,dir,manual){
if(i===cur)return;
if(tl){const old=tl;tl=null;old.progress(1).kill()}
const from=cur, a=slides[from], b=slides[i], wa=words[from], wb=words[i];
cur=i;
a.setAttribute('aria-hidden','true');b.removeAttribute('aria-hidden');
if(manual)live.textContent='Founder '+(i+1)+' of '+N+': '+b.querySelector('.sv-name').textContent;
setMeta(i);
if(R){
a.classList.remove('is-on');b.classList.add('is-on');
imgs[from].classList.remove('is-on');imgs[i].classList.add('is-on');
tagN.textContent=b.dataset.co;
restart();return;
}
gsap.set(wb.q.concat(wb.n),{yPercent:110});gsap.set(wb.m,{autoAlpha:0,y:10});
tl=gsap.timeline({onComplete:()=>{tl=null}});
// words rise out
tl.to(wa.q,{yPercent:-110,duration:.42,ease:'power3.in',stagger:{amount:.22}},0)
.to(wa.n,{yPercent:-110,duration:.4,ease:'power3.in',stagger:.04},0.05)
.to(wa.m,{autoAlpha:0,y:-8,duration:.3,ease:'power2.in'},0)
.add(()=>{a.classList.remove('is-on');b.classList.add('is-on')},.66)
.add(()=>{gsap.set(wa.q.concat(wa.n),{yPercent:0});gsap.set(wa.m,{autoAlpha:1,y:0})},.67)
// words rise in
.to(wb.q,{yPercent:0,duration:1,ease:'expo.out',stagger:{amount:.38}},.68)
.to(wb.n,{yPercent:0,duration:.9,ease:'expo.out',stagger:.05},.72)
.to(wb.m,{autoAlpha:1,y:0,duration:.6,ease:'power3.out',stagger:.06},.9);
// pixel cover -> swap photo -> pixel uncover
tl.to(cells,{scale:1,duration:.26,ease:'power2.in',stagger:order(dir)},0)
.add(()=>{imgs[from].classList.remove('is-on');imgs[i].classList.add('is-on');tagN.textContent=b.dataset.co},.78)
.to(cells,{scale:0,duration:.34,ease:'power2.out',stagger:order(dir)},.8);
restart();
}
/* ---------- auto-advance with pause reasons ---------- */
const paused={hover:false,focus:false,view:true,user:R};
function isPaused(){return paused.hover||paused.focus||paused.view||paused.user}
function restart(){
if(timer){timer.kill();timer=null}
paint(cur);
if(R){gsap.set(fills[cur],{scaleX:1});stateEl.textContent='Manual';return}
timer=gsap.fromTo(fills[cur],{scaleX:0},{scaleX:1,duration:DUR,ease:'none',
onComplete:()=>{if(cur===N-1){show(0,1,false)}else show(cur+1,1,false)}});
sync();
}
function sync(){
if(!timer)return;
const p=isPaused();
p?timer.pause():timer.resume();
stateEl.textContent=paused.user?'Manual':(p?'Paused':'Auto');
}
function pause(key,v){paused[key]=v;sync()}
const go=d=>show((cur+d+N)%N,d,true);
root.querySelector('.sv-nav--prev').addEventListener('click',()=>go(-1));
root.querySelector('.sv-nav--next').addEventListener('click',()=>go(1));
stage.addEventListener('keydown',e=>{
if(e.key==='ArrowRight'){e.preventDefault();go(1)}
else if(e.key==='ArrowLeft'){e.preventDefault();go(-1)}
});
stage.addEventListener('pointerenter',e=>{if(e.pointerType==='mouse')pause('hover',true)});
stage.addEventListener('pointerleave',e=>{if(e.pointerType==='mouse')pause('hover',false)});
stage.addEventListener('focusin',e=>{if(e.target.matches(':focus-visible'))pause('focus',true)});
stage.addEventListener('focusout',e=>{if(!stage.contains(e.relatedTarget))pause('focus',false)});
// swipe
let sx=0,sy=0,st=0;
stage.addEventListener('touchstart',e=>{const t=e.touches[0];sx=t.clientX;sy=t.clientY;st=Date.now()},{passive:true});
stage.addEventListener('touchend',e=>{
const t=e.changedTouches[0],dx=t.clientX-sx,dy=t.clientY-sy;
if(Math.abs(dx)>44&&Math.abs(dx)>Math.abs(dy)*1.3&&Date.now()-st<800)go(dx<0?1:-1);
},{passive:true});
// only tick while the stage is on screen
ScrollTrigger.create({trigger:stage,start:'top 85%',end:'bottom 10%',
onToggle:self=>pause('view',!self.isActive)});
// first entrance: portrait assembles out of white cells
if(!R){
gsap.set(cells,{scale:1});
ScrollTrigger.create({trigger:stage,start:'top 75%',once:true,
onEnter:()=>gsap.to(cells,{scale:0,duration:.4,ease:'power2.out',stagger:order(1)})});
}
restart();
/* ---------- FAQ accordion ---------- */
const qs=[...root.querySelectorAll('.sv-q')];
let refreshT=0;
const refresh=()=>{clearTimeout(refreshT);refreshT=setTimeout(()=>ScrollTrigger.refresh(),60)};
function setOpen(btn,open){
const panel=document.getElementById(btn.getAttribute('aria-controls'));
if((btn.getAttribute('aria-expanded')==='true')===open)return;
btn.setAttribute('aria-expanded',String(open));
gsap.killTweensOf(panel);
if(open){
panel.hidden=false;
if(R){gsap.set(panel,{height:'auto'});refresh();return}
gsap.fromTo(panel,{height:0},{height:'auto',duration:.65,ease:'expo.out',onComplete:refresh});
gsap.fromTo(panel.firstElementChild,{y:14,autoAlpha:0},{y:0,autoAlpha:1,duration:.6,ease:'power3.out',delay:.08});
}else{
if(R){panel.hidden=true;refresh();return}
gsap.to(panel,{height:0,duration:.45,ease:'power3.inOut',onComplete:()=>{panel.hidden=true;gsap.set(panel,{clearProps:'height'});refresh()}});
}
}
qs.forEach(btn=>btn.addEventListener('click',()=>{
const willOpen=btn.getAttribute('aria-expanded')!=='true';
if(willOpen)qs.forEach(o=>{if(o!==btn)setOpen(o,false)});
setOpen(btn,willOpen);
}));
if(!R){
gsap.from(root.querySelectorAll('.sv-row'),{y:24,autoAlpha:0,duration:.8,ease:'expo.out',stagger:.06,
scrollTrigger:{trigger:root.querySelector('.sv-list'),start:'top 85%',once:true}});
}
})();
</script>
</section>
```
## `sections/07-footer.html`
```html
<section id="apply" class="sf-apply" aria-labelledby="sf-title">
<style>
/* ================= 07 — APPLY ================= */
.sf-apply{background:var(--bg);padding-block:clamp(88px,11vw,168px) clamp(72px,9vw,128px);position:relative}
.sf-apply__top{display:flex;justify-content:space-between;align-items:center;gap:16px;flex-wrap:wrap;padding-bottom:18px;border-bottom:1px solid var(--line);margin-bottom:clamp(28px,4vw,56px)}
.sf-apply__top .mono{color:var(--mute)}
.sf-apply__grid{display:grid;grid-template-columns:minmax(0,1fr);gap:clamp(40px,5vw,88px)}
.sf-apply__left{position:relative;min-width:0}
.sf-apply__h{font-size:clamp(104px,15.5vw,248px);position:relative;z-index:2;margin-bottom:-.2em;pointer-events:none}
.sf-apply__h .sf-dot{display:inline-block;width:.13em;height:.13em;background:var(--spore);margin-left:.04em;vertical-align:baseline}
/* specimen frame */
.sf-fig{margin:0;border:1px solid var(--line);padding:clamp(8px,1vw,14px);background:var(--bg)}
.sf-fig__clip{position:relative;overflow:hidden;aspect-ratio:3/2;background:var(--fog)}
.sf-fig__clip img{width:100%;height:100%;object-fit:cover;transform-origin:50% 45%}
.sf-fig__cap{display:flex;justify-content:space-between;gap:12px;padding-top:12px;color:var(--mute)}
.sf-fig__cap b{font-weight:500;color:var(--ink)}
.sf-fig__mark{position:absolute;right:14px;bottom:14px;display:flex;align-items:center;gap:8px;background:var(--bg);padding:6px 9px;font-family:var(--mono);font-size:11px;letter-spacing:.06em;text-transform:uppercase;border:1px solid var(--line)}
.sf-fig__mark i{width:7px;height:7px;background:var(--spore);display:block}
/* right column */
.sf-apply__right{min-width:0;container-type:inline-size;display:flex;flex-direction:column;gap:clamp(28px,3vw,40px)}
.sf-apply__lede{font-size:clamp(17px,1.35vw,19px);max-width:34ch;margin:14px 0 0;color:var(--ink)}
.sf-apply__close{display:block;margin-top:6px}
/* countdown */
.sf-cd{--sf-cdf:clamp(24px,calc((100cqi - 84px) / 9.6),52px);display:flex;align-items:flex-start;gap:10px;padding:18px 0;border-top:1px solid var(--ink);border-bottom:1px solid var(--line)}
.sf-cd__g{display:flex;flex-direction:column;gap:8px}
.sf-cd__n{display:flex;gap:3px}
.sf-cd__d{display:block;overflow:hidden;background:var(--fog);width:.95em;text-align:center;font-family:var(--mono);font-weight:500;font-size:var(--sf-cdf);line-height:1.3;font-variant-numeric:tabular-nums}
.sf-cd__d>span{display:block}
.sf-cd__sep{font-family:var(--mono);font-size:var(--sf-cdf);width:.6em;text-align:center;line-height:1.3;color:var(--line)}
.sf-cd__l{font-family:var(--mono);font-size:11px;letter-spacing:.1em;text-transform:uppercase;color:var(--mute)}
.sf-cd.is-closed .sf-cd__d{background:var(--bg);box-shadow:inset 0 0 0 1px var(--line)}
.sf-sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
/* form */
.sf-formwrap{position:relative}
.sf-form{display:grid;grid-template-columns:minmax(0,1fr);column-gap:28px;row-gap:6px}
.sf-field{position:relative;padding-top:20px;min-width:0}
.sf-field input,.sf-field select,.sf-field textarea{display:block;width:100%;margin:0;border:0;border-bottom:1px solid var(--line);border-radius:0;background:transparent;
padding:10px 0 11px;font-size:17px;line-height:1.4;outline:none;-webkit-appearance:none;appearance:none;transition:border-color .3s}
.sf-field textarea{resize:none;min-height:50px;overflow:hidden}
.sf-field select{padding-right:28px;cursor:pointer}
.sf-field select:invalid{color:var(--mute)}
.sf-field label{position:absolute;left:0;top:13px;font-family:var(--mono);font-size:13px;letter-spacing:.05em;text-transform:uppercase;color:var(--mute);
transform-origin:0 0;transition:transform .4s var(--ease),color .3s;pointer-events:none;white-space:nowrap;max-width:100%;overflow:hidden;text-overflow:ellipsis}
.sf-field:focus-within label,.sf-field.is-filled label,.sf-field--sel label,
.sf-field input:not(:placeholder-shown)~label,.sf-field textarea:not(:placeholder-shown)~label{transform:translateY(-30px) scale(.82);color:var(--ink)}
.sf-field--sel label{transform:translateY(-30px) scale(.82)}
.sf-field__bar{position:absolute;left:0;right:0;height:2px;background:var(--spore);transform:scaleX(0);transform-origin:0 50%;transition:transform .55s var(--ease)}
.sf-field__line{position:relative;display:block}
.sf-field__line .sf-field__bar{bottom:0}
.sf-field:focus-within .sf-field__bar{transform:scaleX(1)}
.sf-field :focus-visible{outline:none;box-shadow:none}
.sf-field__chev{position:absolute;right:2px;bottom:18px;width:12px;height:12px;pointer-events:none;transition:transform .35s var(--ease)}
.sf-field:focus-within .sf-field__chev{transform:rotate(180deg)}
.sf-field__err{display:flex;align-items:center;gap:8px;min-height:22px;padding-top:6px;font-family:var(--mono);font-size:11px;letter-spacing:.04em;color:var(--ink)}
.sf-field.is-err input,.sf-field.is-err select,.sf-field.is-err textarea{border-bottom-color:var(--ink)}
.sf-field.is-err .sf-field__err::before{content:"";width:6px;height:6px;flex:none;background:var(--ink)}
.sf-field.is-err label{color:var(--ink)}
.sf-form__foot{display:flex;flex-wrap:wrap;align-items:center;gap:14px 22px;padding-top:14px}
.sf-form__note{color:var(--mute);font-size:12px;max-width:28ch}
.sf-form .btn[disabled]{pointer-events:none}
.sf-form[hidden]{display:none}
.sf-done h3:focus{outline:none;box-shadow:none}
/* success state */
.sf-done{padding:8px 0 0}
.sf-done[hidden]{display:none}
.sf-done__row{display:flex;align-items:flex-end;gap:clamp(18px,3vw,32px);border-top:1px solid var(--ink);padding-top:22px}
.sf-done svg{width:clamp(92px,10vw,124px);height:auto;flex:none;overflow:visible}
.sf-done svg path,.sf-done svg line{fill:none;stroke:var(--ink);stroke-width:1.6;stroke-linecap:square;stroke-linejoin:miter}
.sf-done svg .sf-g{stroke:var(--line)}
.sf-done svg .sf-leaf{fill:var(--spore);fill-opacity:1}
.sf-done h3{font-family:var(--disp);font-stretch:62%;font-weight:800;text-transform:uppercase;line-height:.86;font-size:clamp(48px,5vw,76px)}
.sf-done p{margin:14px 0 0;font-size:18px;max-width:30ch}
.sf-done .mono{color:var(--mute);display:block;margin-top:18px}
@media (min-width:640px) and (max-width:899px),(min-width:1200px){
.sf-form{grid-template-columns:repeat(2,minmax(0,1fr))}
.sf-field--wide{grid-column:1/-1}
}
@media (min-width:900px){
.sf-apply__grid{grid-template-columns:minmax(0,7fr) minmax(0,5fr);align-items:end}
}
@media (max-width:520px){
.sf-fig__mark{display:none}
.sf-cd__sep{display:none}
.sf-cd{justify-content:space-between}
}
</style>
<div class="wrap">
<div class="sf-apply__top">
<span class="kick">007 — Apply</span>
<span class="mono">Germinate · autumn cohort · 8 places</span>
</div>
<div class="sf-apply__grid">
<div class="sf-apply__left">
<h2 class="disp sf-apply__h" id="sf-title">Plant<br>yours<span class="sf-dot" aria-hidden="true"></span></h2>
<figure class="sf-fig ticks">
<div class="sf-fig__clip">
<img src="assets/seeds.jpg" width="2000" height="1333" loading="lazy" alt="A single dandelion seed head against a pale sky, every seed still attached.">
<span class="sf-fig__mark" aria-hidden="true"><i></i>Ready to disperse</span>
</div>
<figcaption class="sf-fig__cap mono"><b data-sp-scramble>Specimen 042 — yours?</b><span>Fig. 07 · 1:1</span></figcaption>
</figure>
</div>
<div class="sf-apply__right">
<div>
<span class="kick">Autumn cohort</span>
<span class="mono sf-apply__close" id="sf-close">Applications close 30 Nov 2026 · 23:59 CET</span>
<p class="sf-apply__lede">Eight teams. Sixteen weeks in Halle 3. A first cheque from €150k. Bring a prototype — or a very convincing sketch of one.</p>
</div>
<div class="sf-cd" id="sf-cd" aria-describedby="sf-close">
<span class="sf-sr" id="sf-cd-sr">Applications close on 30 November 2026.</span>
<div class="sf-cd__g" aria-hidden="true"><div class="sf-cd__n" data-u="d"></div><span class="sf-cd__l">Days</span></div>
<span class="sf-cd__sep" aria-hidden="true">:</span>
<div class="sf-cd__g" aria-hidden="true"><div class="sf-cd__n" data-u="h"></div><span class="sf-cd__l">Hrs</span></div>
<span class="sf-cd__sep" aria-hidden="true">:</span>
<div class="sf-cd__g" aria-hidden="true"><div class="sf-cd__n" data-u="m"></div><span class="sf-cd__l">Min</span></div>
<span class="sf-cd__sep" aria-hidden="true">:</span>
<div class="sf-cd__g" aria-hidden="true"><div class="sf-cd__n" data-u="s"></div><span class="sf-cd__l">Sec</span></div>
</div>
<div class="sf-formwrap">
<form class="sf-form" data-atom-form novalidate aria-label="Germinate application">
<div class="sf-field">
<span class="sf-field__line">
<input id="sf-team" name="team" type="text" required placeholder=" " autocomplete="organization" aria-describedby="sf-team-e" data-req="Your team needs a name — a working title is fine.">
<label for="sf-team">Team name</label>
<span class="sf-field__bar"></span>
</span>
<span class="sf-field__err" id="sf-team-e" aria-live="polite"></span>
</div>
<div class="sf-field">
<span class="sf-field__line">
<input id="sf-mail" name="email" type="email" required placeholder=" " autocomplete="email" inputmode="email" aria-describedby="sf-mail-e" data-req="We need somewhere to send the reply.">
<label for="sf-mail">Email</label>
<span class="sf-field__bar"></span>
</span>
<span class="sf-field__err" id="sf-mail-e" aria-live="polite"></span>
</div>
<div class="sf-field sf-field--wide sf-field--sel">
<span class="sf-field__line">
<select id="sf-sector" name="sector" required aria-describedby="sf-sector-e" data-req="Pick the closest fit — we'll sort out nuance later.">
<option value="" selected disabled>Choose one</option>
<option>Robotics</option>
<option>Climate hardware</option>
<option>Health devices</option>
<option>Edge AI</option>
</select>
<label for="sf-sector">Sector</label>
<svg class="sf-field__chev" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true"><path d="M1.5 4 6 8.5 10.5 4"/></svg>
<span class="sf-field__bar"></span>
</span>
<span class="sf-field__err" id="sf-sector-e" aria-live="polite"></span>
</div>
<div class="sf-field sf-field--wide">
<span class="sf-field__line">
<textarea id="sf-what" name="building" rows="1" required placeholder=" " maxlength="240" aria-describedby="sf-what-e" data-req="One sentence. The rest can wait for the call."></textarea>
<label for="sf-what">What are you building? (one sentence)</label>
<span class="sf-field__bar"></span>
</span>
<span class="sf-field__err" id="sf-what-e" aria-live="polite"></span>
</div>
<div class="sf-form__foot sf-field--wide">
<button class="btn" type="submit">Send application <svg class="arr" viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true"><path d="M2 12 12 2M4 2h8v8"/></svg></button>
<span class="mono sf-form__note">Four fields. No deck needed at this stage.</span>
</div>
</form>
<div class="sf-done" hidden>
<div class="sf-done__row">
<svg viewBox="0 0 120 120" role="img" aria-label="A seed sprouting">
<line class="sf-g" x1="6" y1="92" x2="114" y2="92"/>
<path class="sf-s" d="M52 97c0-3.6 3.6-6 8-6s8 2.4 8 6-3.6 6-8 6-8-2.4-8-6z"/>
<path class="sf-s" d="M57 102c-2 6-6 10-11 13M63 102c1 6 4 10 9 12M60 103v13"/>
<path class="sf-s" d="M60 91C60 79 58 67 61 50"/>
<path class="sf-s sf-leaf" d="M60 66C49 66 39 60 35 48c11 0 21 5 25 18z"/>
<path class="sf-s sf-leaf" d="M61 57c4-13 14-19 27-19-2 13-13 19-27 19z"/>
</svg>
<h3 tabindex="-1">Received.</h3>
</div>
<p>We read every one — expect a reply within 10 days.</p>
<span class="mono sf-done__ref">Ref. <span class="sf-ref">SPR-26A-0000</span> · kept on this page only</span>
</div>
</div>
</div>
</div>
</div>
</section>
<footer class="sf-foot">
<style>
/* ================= FOOTER ================= */
.sf-foot{background:var(--bg);padding-top:clamp(24px,3vw,40px);position:relative}
.sf-cols{display:grid;grid-template-columns:minmax(0,1fr);gap:1px;background:var(--line);border:1px solid var(--line)}
.sf-col{background:var(--bg);padding:clamp(22px,2.4vw,34px) clamp(18px,2vw,28px) clamp(26px,2.8vw,40px);min-width:0}
.sf-col__t{font-family:var(--mono);font-size:12px;font-weight:500;letter-spacing:.08em;text-transform:uppercase;color:var(--mute);margin-bottom:20px}
.sf-col p{margin:0 0 18px;max-width:34ch}
.sf-col address{font-style:normal}
.sf-col address span{display:block}
.sf-mail{font-size:17px;text-decoration:none;border-bottom:1px solid var(--ink);padding-bottom:2px;transition:border-color .3s}
.sf-mail:hover{border-color:var(--spore)}
.sf-live{display:inline-flex;align-items:center;gap:10px;margin-top:22px;color:var(--mute)}
.sf-live__dot{width:8px;height:8px;border-radius:50%;background:var(--spore);flex:none;position:relative}
.sf-live__dot::after{content:"";position:absolute;inset:-4px;border-radius:50%;border:1px solid var(--spore);opacity:0}
@media (prefers-reduced-motion: no-preference){
.sf-live__dot{animation:sf-blink 1.6s steps(1,end) infinite}
.sf-live__dot::after{animation:sf-ping 1.6s var(--ease) infinite}
}
@keyframes sf-blink{0%{opacity:1}70%{opacity:.25}}
@keyframes sf-ping{0%{transform:scale(.4);opacity:.9}70%,100%{transform:scale(1.6);opacity:0}}
.sf-list{list-style:none;margin:0;padding:0;display:grid;gap:10px}
.sf-link{display:inline-flex;align-items:center;font-size:clamp(18px,1.5vw,22px);text-decoration:none;line-height:1.25}
.sf-link::before{content:"";width:0;height:9px;margin-right:0;background:var(--spore);flex:none;transition:width .4s var(--ease),margin .4s var(--ease)}
.sf-link:hover::before,.sf-link:focus-visible::before{width:9px;margin-right:10px}
.sf-link small{font-family:var(--mono);font-size:11px;color:var(--mute);margin-left:10px;letter-spacing:.04em}
.sf-news{margin-top:4px}
.sf-news__row{display:flex;align-items:stretch;border-bottom:1px solid var(--ink);position:relative}
.sf-news__row::after{content:"";position:absolute;left:0;right:48px;bottom:-1px;height:2px;background:var(--spore);transform:scaleX(0);transform-origin:0 50%;transition:transform .55s var(--ease)}
.sf-news__row:focus-within::after{transform:scaleX(1)}
.sf-news input{flex:1;min-width:0;border:0;border-radius:0;background:transparent;padding:12px 12px 12px 0;font-size:16px;outline:none}
.sf-news input::placeholder{color:var(--mute);opacity:1}
.sf-news input:focus-visible{outline:none;box-shadow:none}
.sf-news__btn{width:48px;height:48px;flex:none;background:var(--spore);color:var(--ink);display:grid;place-items:center;position:relative;overflow:hidden;isolation:isolate;transition:color .35s var(--ease)}
.sf-news__btn::after{content:"";position:absolute;inset:0;background:var(--ink);transform:translateY(101%);transition:transform .45s var(--ease);z-index:-1}
.sf-news__btn:hover{color:var(--bg)}
.sf-news__btn:hover::after{transform:none}
.sf-news__btn svg{width:16px;height:16px;transition:transform .45s var(--ease)}
.sf-news__btn:hover svg{transform:translate(2px,-2px)}
.sf-news__msg{display:flex;align-items:center;gap:8px;min-height:22px;margin-top:10px;font-family:var(--mono);font-size:11px;letter-spacing:.04em;color:var(--mute)}
.sf-news__msg.is-ok::before{content:"";width:7px;height:7px;background:var(--spore);flex:none}
.sf-news__msg.is-err{color:var(--ink)}
.sf-news__msg.is-err::before{content:"";width:6px;height:6px;background:var(--ink);flex:none}
/* giant wordmark */
.sf-mark{position:relative;overflow-x:clip;padding:clamp(28px,4vw,64px) 0 clamp(8px,1vw,16px);margin:0;user-select:none}
.sf-mark__line{display:flex;width:max-content;white-space:nowrap;font-family:var(--disp);font-weight:800;text-transform:uppercase;line-height:.8;letter-spacing:0;
font-size:calc((min(100vw,1440px) - 2 * var(--gut) - 20px) * .205);transform-origin:0 50%;padding-top:.04em}
.sf-mark__c{display:block;font-variation-settings:"wdth" 62,"wght" 800;font-stretch:62%}
.sf-mark__c>span{display:block;will-change:transform}
.sf-mark__c--sl{color:var(--spore)}
/* bottom bar */
.sf-bar{display:flex;flex-wrap:wrap;justify-content:space-between;align-items:center;gap:12px 28px;border-top:1px solid var(--line);padding:18px 0 24px;margin-top:clamp(10px,1.4vw,18px);color:var(--mute)}
.sf-bar__l{display:flex;flex-wrap:wrap;gap:8px 22px;align-items:center}
.sf-bar a{text-decoration:none;transition:color .25s}
.sf-bar a:hover{color:var(--ink)}
.sf-top{display:inline-flex;align-items:center;gap:10px;font-family:var(--mono);font-size:12px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;color:var(--ink);
height:40px;padding:0 14px;box-shadow:inset 0 0 0 1px var(--ink);position:relative;overflow:hidden;isolation:isolate}
.sf-top::after{content:"";position:absolute;inset:0;background:var(--spore);transform:translateY(101%);transition:transform .45s var(--ease);z-index:-1}
.sf-top:hover::after{transform:none}
.sf-top span{display:inline-block;transition:transform .45s var(--ease)}
.sf-top:hover span{transform:translateY(-3px)}
@media (min-width:600px){
.sf-cols{grid-template-columns:repeat(2,minmax(0,1fr))}
}
@media (min-width:1024px){
.sf-cols{grid-template-columns:1.35fr 1fr 1fr 1.35fr}
}
@media (max-width:599px){
.sf-cols{grid-template-columns:repeat(2,minmax(0,1fr))}
.sf-col--about,.sf-col--news{grid-column:1/-1}
.sf-bar{flex-direction:column;align-items:flex-start}
}
</style>
<div class="wrap">
<div class="sf-cols">
<div class="sf-col sf-col--about">
<h2 class="sf-col__t">Spore Labs</h2>
<p>A venture studio for hard-tech founders. We fund, house and grow companies from first prototype to first purchase order.</p>
<address>
<span>Halle 3 · Berlin-Wedding</span>
<span class="mono" style="color:var(--mute)">Former tram depot · 2,400 m²</span>
</address>
<p style="margin:18px 0 0"><a class="sf-mail" href="mailto:[email protected]">[email protected]</a></p>
<span class="sf-live mono"><span class="sf-live__dot" aria-hidden="true"></span>Studio open · Mon–Fri 08–20</span>
</div>
<nav class="sf-col" aria-label="Programs">
<h2 class="sf-col__t">Programs</h2>
<ul class="sf-list">
<li><a class="sf-link" href="#programs">Germinate<small>16 wks</small></a></li>
<li><a class="sf-link" href="#programs">Canopy<small>€150k+</small></a></li>
<li><a class="sf-link" href="#germination">Demo Day</a></li>
</ul>
</nav>
<nav class="sf-col" aria-label="Studio">
<h2 class="sf-col__t">Studio</h2>
<ul class="sf-list">
<li><a class="sf-link" href="#thesis">Thesis</a></li>
<li><a class="sf-link" href="#specimens">Portfolio</a></li>
<li><a class="sf-link" href="#germination">Process</a></li>
<li><a class="sf-link" href="#voices">Founders</a></li>
</ul>
</nav>
<div class="sf-col sf-col--news">
<h2 class="sf-col__t">Field notes, monthly</h2>
<p>What broke in the shop, what shipped, who's hiring. One email a month, no growth hacks.</p>
<form class="sf-news" data-atom-form novalidate aria-label="Newsletter sign-up">
<label class="sf-sr" for="sf-news-mail">Email for field notes</label>
<div class="sf-news__row">
<input id="sf-news-mail" name="email" type="email" required placeholder="[email protected]" autocomplete="email" aria-describedby="sf-news-msg">
<button class="sf-news__btn" type="submit" aria-label="Subscribe"><svg viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true"><path d="M2 12 12 2M4 2h8v8"/></svg></button>
</div>
<span class="sf-news__msg" id="sf-news-msg" aria-live="polite"></span>
</form>
</div>
</div>
<p class="sf-mark">
<span class="sf-sr">Spore Labs</span>
<span class="sf-mark__line" aria-hidden="true"><span class="sf-mark__c"><span>S</span></span><span class="sf-mark__c"><span>P</span></span><span class="sf-mark__c"><span>O</span></span><span class="sf-mark__c"><span>R</span></span><span class="sf-mark__c"><span>E</span></span><span class="sf-mark__c sf-mark__c--sl"><span>/</span></span><span class="sf-mark__c"><span>L</span></span><span class="sf-mark__c"><span>A</span></span><span class="sf-mark__c"><span>B</span></span><span class="sf-mark__c"><span>S</span></span></span>
</p>
<div class="sf-bar mono">
<div class="sf-bar__l">
<span>© 2026 Spore Labs GmbH</span>
<a href="#" data-sf-legal>Imprint</a>
<a href="#" data-sf-legal>Privacy</a>
</div>
<button class="sf-top" type="button">Back to top <span aria-hidden="true">↑</span></button>
</div>
</div>
<script>
(function(){
const SP=window.SP||{reduced:true};
const R=!!SP.reduced;
const hasG=typeof gsap!=='undefined';
const apply=document.getElementById('apply');
const foot=document.querySelector('.sf-foot');
if(!apply||!foot)return;
/* ---------- heading + specimen frame ---------- */
const h=apply.querySelector('.sf-apply__h');
if(SP.rise&&h)SP.rise(h,{stagger:.09,duration:1.2});
const fig=apply.querySelector('.sf-fig');
const clip=apply.querySelector('.sf-fig__clip');
const img=clip&&clip.querySelector('img');
if(!R&&hasG&&clip&&img){
gsap.set(clip,{clipPath:'inset(16% 20% 16% 20%)'});
gsap.set(img,{scale:1.2});
gsap.timeline({scrollTrigger:{trigger:fig,start:'top 92%',end:'center 55%',scrub:.8}})
.to(clip,{clipPath:'inset(0% 0% 0% 0%)',ease:'none'},0)
.to(img,{scale:1,ease:'none'},0);
}
/* ---------- countdown ---------- */
const END=Date.parse('2026-11-30T23:59:00+01:00');
const cd=apply.querySelector('#sf-cd');
const units={};
cd.querySelectorAll('.sf-cd__n').forEach(n=>units[n.dataset.u]=n);
let cdSeen=false;
if('IntersectionObserver' in window){
new IntersectionObserver(es=>es.forEach(e=>cdSeen=e.isIntersecting)).observe(cd);
}
function flip(inner,ch){
clearInterval(inner._sfIv);
let n=0;
inner._sfIv=setInterval(()=>{
if(++n>3){clearInterval(inner._sfIv);inner.textContent=ch;return}
inner.textContent=(Math.random()*10)|0;
},45);
gsap.fromTo(inner,{yPercent:-65,opacity:0},{yPercent:0,opacity:1,duration:.42,ease:'expo.out',overwrite:true});
}
function setUnit(el,val,anim){
const str=String(val).padStart(2,'0');
while(el.children.length<str.length){
const c=document.createElement('span');c.className='sf-cd__d';
const i=document.createElement('span');i.textContent='0';i.dataset.v='';c.appendChild(i);el.prepend(c);
}
while(el.children.length>str.length)el.firstElementChild.remove();
[...el.children].forEach((cell,k)=>{
const inner=cell.firstElementChild,ch=str[k];
if(inner.dataset.v===ch)return;
inner.dataset.v=ch;
if(anim&&!R&&hasG)flip(inner,ch);else inner.textContent=ch;
});
}
function render(anim){
const ms=Math.max(0,END-Date.now());
const s=Math.floor(ms/1000);
setUnit(units.d,Math.floor(s/86400),anim);
setUnit(units.h,Math.floor(s%86400/3600),anim);
setUnit(units.m,Math.floor(s%3600/60),anim);
setUnit(units.s,s%60,anim);
if(ms<=0&&!cd.classList.contains('is-closed')){
cd.classList.add('is-closed');
const c=apply.querySelector('#sf-close');
if(c)c.textContent='Applications closed · spring cohort opens soon';
}
return ms;
}
render(false);
(function tick(){
const left=render(cdSeen);
if(left>0)setTimeout(tick,1000-(Date.now()%1000)+8);
})();
/* ---------- application form ---------- */
const EMAIL=/^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/;
const form=apply.querySelector('.sf-form');
const wrap=apply.querySelector('.sf-formwrap');
const done=apply.querySelector('.sf-done');
const fields=[...form.querySelectorAll('.sf-field')];
let tried=false;
function ctl(f){return f.querySelector('input,select,textarea')}
function syncFilled(f){f.classList.toggle('is-filled',ctl(f).value.trim()!=='')}
function check(f){
const el=ctl(f),v=el.value.trim(),err=f.querySelector('.sf-field__err');
let msg='';
if(el.required&&!v)msg=el.dataset.req||'Required.';
else if(el.type==='email'&&!EMAIL.test(v))msg='That address looks off — check the @ and the dot.';
f.classList.toggle('is-err',!!msg);
el.setAttribute('aria-invalid',msg?'true':'false');
err.textContent=msg;
return !msg;
}
function grow(t){t.style.height='auto';t.style.height=t.scrollHeight+'px'}
fields.forEach(f=>{
const el=ctl(f);
syncFilled(f);
const on=()=>{syncFilled(f);if(el.tagName==='TEXTAREA')grow(el);if(tried||f.classList.contains('is-err'))check(f)};
el.addEventListener('input',on);
el.addEventListener('change',on);
el.addEventListener('blur',()=>{if(el.value.trim())check(f)});
});
form.addEventListener('submit',e=>{
e.preventDefault(); /* nothing is sent anywhere */
tried=true;
const bad=fields.filter(f=>!check(f));
if(bad.length){
ctl(bad[0]).focus({preventScroll:false});
if(!R&&hasG)gsap.fromTo(bad.map(f=>f.querySelector('.sf-field__line')),{x:0},{keyframes:{x:[-6,5,-3,2,0]},duration:.4,ease:'none',stagger:.04});
return;
}
success();
});
function success(){
const btn=form.querySelector('button[type=submit]');
btn.disabled=true;
const ref='SPR-26A-'+String(1000+((Math.random()*9000)|0));
done.querySelector('.sf-ref').textContent=ref;
const paths=[...done.querySelectorAll('.sf-s,.sf-g')];
const heading=done.querySelector('h3');
if(R||!hasG){
form.hidden=true;form.reset();done.hidden=false;heading.focus({preventScroll:true});return;
}
const h0=wrap.offsetHeight;
wrap.style.height=h0+'px';wrap.style.overflow='hidden';
const kids=[...form.children];
gsap.timeline()
.to(kids,{y:-18,autoAlpha:0,duration:.45,ease:'power3.in',stagger:.06})
.add(()=>{
form.hidden=true;form.reset();fields.forEach(syncFilled);
done.hidden=false;
paths.forEach(p=>{const L=p.getTotalLength();p.style.strokeDasharray=L;p.style.strokeDashoffset=L});
gsap.set(done.querySelectorAll('.sf-leaf'),{fillOpacity:0});
gsap.set([heading,done.querySelector('p'),done.querySelector('.sf-done__ref')],{y:20,autoAlpha:0});
const h1=done.offsetHeight+8;
gsap.to(wrap,{height:h1,duration:.6,ease:'expo.out',onComplete:()=>{wrap.style.height='';wrap.style.overflow=''}});
heading.focus({preventScroll:true});
const tl=gsap.timeline({defaults:{ease:'power2.inOut'}});
tl.to(done.querySelector('.sf-g'),{strokeDashoffset:0,duration:.5})
.to(paths[1],{strokeDashoffset:0,duration:.5},'-=.2')
.to(paths[2],{strokeDashoffset:0,duration:.6},'-=.1')
.to(paths[3],{strokeDashoffset:0,duration:.7,ease:'power3.out'})
.to([paths[4],paths[5]],{strokeDashoffset:0,duration:.6,stagger:.15})
.to(done.querySelectorAll('.sf-leaf'),{fillOpacity:1,duration:.4,stagger:.1},'-=.2')
.to([heading,done.querySelector('p'),done.querySelector('.sf-done__ref')],{y:0,autoAlpha:1,duration:.8,ease:'expo.out',stagger:.1},.35)
.add(()=>{if(SP.scramble)SP.scramble(done.querySelector('.sf-ref'),{text:ref,duration:.7})},.7);
});
}
/* ---------- newsletter (no network) ---------- */
const news=foot.querySelector('.sf-news');
const nIn=news.querySelector('input'),nMsg=news.querySelector('.sf-news__msg');
news.addEventListener('submit',e=>{
e.preventDefault();
const v=nIn.value.trim();
nMsg.classList.remove('is-ok','is-err');
if(!EMAIL.test(v)){
nMsg.classList.add('is-err');
nMsg.textContent=v?'That address looks off.':'Add an email first.';
nIn.setAttribute('aria-invalid','true');
nIn.focus();
return;
}
nIn.setAttribute('aria-invalid','false');
nIn.value='';
nMsg.classList.add('is-ok');
nMsg.textContent="You're on the list. First notes land in October.";
if(!R&&SP.scramble)SP.scramble(nMsg,{text:nMsg.textContent,duration:.6});
});
/* ---------- legal placeholders + back to top ---------- */
foot.querySelectorAll('[data-sf-legal]').forEach(a=>a.addEventListener('click',e=>e.preventDefault()));
foot.querySelector('.sf-top').addEventListener('click',()=>{
if(SP.lenis)SP.lenis.scrollTo(0,{duration:1.8});
else window.scrollTo({top:0,behavior:R?'auto':'smooth'});
});
/* ---------- giant wordmark: fit, rise, proximity swell ---------- */
const mark=foot.querySelector('.sf-mark');
const line=mark.querySelector('.sf-mark__line');
const cells=[...line.querySelectorAll('.sf-mark__c')];
const W0=62,W1=100,G0=800,G1=900;
const cur=cells.map(()=>0),tgt=cells.map(()=>0);
let wRest=[],wMax=[],centers=[],avail=0,running=false,fitted=false;
function vary(c,t){c.style.fontVariationSettings='"wdth" '+(W0+(W1-W0)*t).toFixed(2)+',"wght" '+(G0+(G1-G0)*t).toFixed(1)}
function fit(){
line.style.transform='none';
line.style.fontSize='';
cells.forEach(c=>vary(c,0));
avail=mark.clientWidth;
let w=line.getBoundingClientRect().width;
if(!w||!avail)return;
const fs=parseFloat(getComputedStyle(line).fontSize)*avail/w;
line.style.fontSize=fs.toFixed(2)+'px';
wRest=cells.map(c=>c.getBoundingClientRect().width);
cells.forEach(c=>vary(c,1));
wMax=cells.map(c=>c.getBoundingClientRect().width);
const total=wRest.reduce((a,b)=>a+b,0);
let acc=0;centers=wRest.map(x=>{const m=(acc+x/2)/total;acc+=x;return m});
avail=total;
cells.forEach((c,i)=>vary(c,cur[i]));
apply_();
fitted=true;
}
function apply_(){
let sum=0;
cells.forEach((c,i)=>{vary(c,cur[i]);sum+=wRest[i]+(wMax[i]-wRest[i])*cur[i]});
line.style.transform=sum>0?'scaleX('+(avail/sum).toFixed(4)+')':'none';
}
function loop(){
let moving=false;
for(let i=0;i<cells.length;i++){
const d=tgt[i]-cur[i];
if(Math.abs(d)>.002){cur[i]+=d*.14;moving=true}else cur[i]=tgt[i];
}
apply_();
if(!moving){gsap.ticker.remove(loop);running=false}
}
function kick(){if(!running&&fitted){running=true;gsap.ticker.add(loop)}}
function aimAt(x,fy){
const rad=.17;
for(let i=0;i<cells.length;i++){
let t=Math.max(0,1-Math.abs(x-centers[i])/rad);
t=t*t*(3-2*t);
tgt[i]=t*fy;
}
kick();
}
if(document.fonts&&document.fonts.ready)document.fonts.ready.then(fit);else fit();
let rz;window.addEventListener('resize',()=>{clearTimeout(rz);rz=setTimeout(fit,120)});
if(!R&&hasG){
const inners=cells.map(c=>c.firstElementChild);
gsap.set(line,{clipPath:'inset(-20% -5% 0% -5%)'});
let pointerIn=false;
gsap.from(inners,{yPercent:105,duration:1.2,ease:'expo.out',stagger:.055,
scrollTrigger:{trigger:mark,start:'top 95%',once:true},
onComplete:()=>{
line.style.clipPath='none';
if(pointerIn)return;
const p={x:-.2};
gsap.to(p,{x:1.2,duration:1.9,ease:'power1.inOut',
onUpdate:()=>{if(!pointerIn)aimAt(p.x,.75)},
onComplete:()=>{if(!pointerIn){tgt.fill(0);kick()}}});
}});
if(matchMedia('(hover:hover)').matches){
foot.addEventListener('pointermove',e=>{
if(e.pointerType!=='mouse'||!fitted)return;
pointerIn=true;
const r=mark.getBoundingClientRect();
const x=(e.clientX-r.left)/r.width;
const cy=r.top+r.height*.55,dy=Math.abs(e.clientY-cy)/r.height;
const fy=Math.max(0,Math.min(1,1-(dy-.45)/1.4));
aimAt(x,fy);
});
foot.addEventListener('pointerleave',()=>{pointerIn=false;tgt.fill(0);kick()});
}
}
})();
</script>
</footer>
```
## `template.html`
```html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' fill='%23fff'/%3E%3Ccircle cx='16' cy='16' r='5' fill='%232EE06F'/%3E%3Cg stroke='%230C0D0F' stroke-width='2'%3E%3Cpath d='M16 2v6M16 24v6M2 16h6M24 16h6M6 6l4 4M22 22l4 4M26 6l-4 4M10 22l-4 4'/%3E%3C/g%3E%3C/svg%3E">
<title>Spore Labs — Venture studio for hard-tech founders</title>
<meta name="description" content="Spore Labs is a Berlin venture studio that funds, houses and grows early-stage robotics, climate and health-hardware founders. 16-week residency, €150k–€600k first cheques.">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Archivo:wdth,[email protected],300..900&family=Geist:[email protected]&family=Geist+Mono:wght@400;500;600&display=swap" rel="stylesheet">
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/gsap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/ScrollTrigger.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/Flip.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/lenis.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<style>
/* ---------- SPORE LABS locked palette (white + ink base, one accent) ---------- */
:root{
--bg:#FFFFFF; /* page */
--fog:#EEF0F2; /* cool grey panels / cards (never cream) */
--line:#D6D9DE; /* hairlines on white */
--ink:#0C0D0F; /* text, 1px rules, ink buttons — never a section background */
--mute:#5E636B; /* secondary text */
--spore:#2EE06F; /* the one accent: fills, dots, progress, highlights (ink text on it) */
--spore-deep:#13A94C; /* accent hover / accent text on white at large sizes only */
--spore-soft:#DDF9E7; /* accent tint for chips / selections */
--disp:'Archivo', 'Arial Narrow', sans-serif; /* condensed display: font-stretch 62% */
--body:'Geist', system-ui, sans-serif;
--mono:'Geist Mono', ui-monospace, monospace;
--ease:cubic-bezier(.2,.8,.2,1);
--ease-io:cubic-bezier(.7,0,.2,1);
--gut:clamp(16px,3vw,40px);
--hdr:64px;
}
*,*::before,*::after{box-sizing:border-box}
html.lenis,html.lenis body{height:auto}
.lenis.lenis-smooth{scroll-behavior:auto !important}
.lenis.lenis-stopped{overflow:hidden}
html{scroll-padding-top:var(--hdr)}
body{margin:0;background:var(--bg);color:var(--ink);font-family:var(--body);font-size:16px;line-height:1.55;overflow-x:hidden;-webkit-font-smoothing:antialiased}
::selection{background:var(--spore);color:var(--ink)}
img{display:block;max-width:100%;height:auto}
a{color:inherit}
button,input,textarea,select{font:inherit;color:inherit}
button{background:none;border:0;cursor:pointer;padding:0}
:focus-visible{outline:2px solid var(--ink);outline-offset:3px;box-shadow:0 0 0 5px var(--spore)}
h1,h2,h3,h4{margin:0;font-weight:inherit}
/* ---------- shared atoms ---------- */
.wrap{max-width:1440px;margin-inline:auto;padding-inline:var(--gut)}
/* condensed display type */
.disp{font-family:var(--disp);font-stretch:62%;font-weight:800;text-transform:uppercase;line-height:.84;letter-spacing:-.01em}
/* mono label with a small accent square */
.kick{font-family:var(--mono);font-size:12px;font-weight:500;letter-spacing:.08em;text-transform:uppercase;display:inline-flex;align-items:center;gap:10px}
.kick::before{content:"";width:8px;height:8px;background:var(--spore);flex:none}
.mono{font-family:var(--mono);font-size:13px;letter-spacing:.02em}
/* buttons: square, mono, uppercase */
.btn{display:inline-flex;align-items:center;gap:12px;height:48px;padding:0 22px;background:var(--spore);color:var(--ink);
font-family:var(--mono);font-size:13px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;text-decoration:none;
position:relative;overflow:hidden;isolation:isolate;transition:color .35s var(--ease)}
.btn::after{content:"";position:absolute;inset:0;background:var(--ink);transform:translateY(101%);transition:transform .45s var(--ease);z-index:-1}
.btn:hover{color:#fff}
.btn:hover::after{transform:none}
.btn .arr{width:14px;height:14px;flex:none}
.btn--ink{background:var(--ink);color:#fff}
.btn--ink::after{background:var(--spore)}
.btn--ink:hover{color:var(--ink)}
.btn--line{background:transparent;box-shadow:inset 0 0 0 1px var(--ink)}
/* corner ticks around a box: add class .ticks to any position:relative element */
.ticks{position:relative}
.ticks::before,.ticks::after{content:"";position:absolute;width:10px;height:10px;pointer-events:none;border:0 solid var(--ink)}
.ticks::before{top:-1px;left:-1px;border-top-width:1px;border-left-width:1px}
.ticks::after{bottom:-1px;right:-1px;border-bottom-width:1px;border-right-width:1px}
/* word split */
.w{display:inline-block;overflow:hidden;vertical-align:top;padding-bottom:.08em;margin-bottom:-.08em}
.w>span{display:inline-block;will-change:transform}
/* reveal-on-scroll default state is set by JS only (no-JS = visible) */
</style>
</head>
<body>
<script>
/* Shared runtime: window.SP
SP.reduced true under prefers-reduced-motion
SP.lenis smooth scroll (null when reduced), synced to ScrollTrigger
SP.split(el) wrap each word in <span class="w"><span>..</span></span>, returns inner spans
SP.rise(el,opts) split + words rise in when el enters the viewport (once)
SP.scramble(el,o) decode el's text from random glyphs, left to right. o: {duration, delay, text}
SP.scrambleOnView(el,o) same, triggered once when el enters
SP.count(el) counts el from 0 to data-to (supports data-prefix, data-suffix, data-dec) when it enters
SP.magnet(el,s) element follows the pointer a little while hovered (s = strength px)
Elements with [data-sp-reveal] fade up automatically on enter; [data-sp-scramble] scramble on enter. */
window.SP=(function(){
gsap.registerPlugin(ScrollTrigger,Flip);
const reduced=matchMedia('(prefers-reduced-motion: reduce)').matches;
let lenis=null;
if(!reduced){
lenis=new Lenis({duration:1.15,easing:t=>Math.min(1,1.001-Math.pow(2,-10*t))});
lenis.on('scroll',ScrollTrigger.update);
gsap.ticker.add(t=>lenis.raf(t*1000));
gsap.ticker.lagSmoothing(0);
document.addEventListener('click',e=>{const a=e.target.closest('a[href^="#"]');if(!a)return;const id=a.getAttribute('href');
if(id.length<2)return;const t=document.querySelector(id);if(!t)return;e.preventDefault();lenis.scrollTo(t,{offset:0,duration:1.4})});
}
function split(el){
const walk=n=>{[...n.childNodes].forEach(c=>{
if(c.nodeType===3){const f=document.createDocumentFragment();
c.textContent.split(/(\s+)/).forEach(t=>{if(!t)return;if(/^\s+$/.test(t)){f.appendChild(document.createTextNode(t));return}
const o=document.createElement('span');o.className='w';const i=document.createElement('span');i.textContent=t;o.appendChild(i);f.appendChild(o)});
c.replaceWith(f)}
else if(c.nodeType===1&&c.tagName!=='BR'&&!c.classList.contains('w'))walk(c)})};
walk(el);return [...el.querySelectorAll('.w>span')];
}
function rise(el,opts={}){
const s=split(el);if(reduced)return s;
gsap.from(s,{yPercent:110,duration:1,ease:'expo.out',stagger:.045,...opts,scrollTrigger:{trigger:el,start:'top 88%',once:true}});
return s;
}
const GLYPHS='01<>/\\[]{}#*+=-_:;';
function scramble(el,o={}){
const final=o.text!=null?o.text:(el.dataset.final||(el.dataset.final=el.textContent));
if(reduced){el.textContent=final;return}
const dur=(o.duration||.9)*1000,delay=(o.delay||0)*1000;let t0=null;
const step=ts=>{if(t0==null)t0=ts;const p=Math.min(1,(ts-t0)/dur);const n=Math.floor(p*final.length);let s='';
for(let i=0;i<final.length;i++){const ch=final[i];s+=(i<n||ch===' ')?ch:GLYPHS[(Math.random()*GLYPHS.length)|0]}
el.textContent=s;if(p<1)requestAnimationFrame(step);else el.textContent=final};
setTimeout(()=>requestAnimationFrame(step),delay);
}
function scrambleOnView(el,o={}){
if(reduced)return;
ScrollTrigger.create({trigger:el,start:'top 90%',once:true,onEnter:()=>scramble(el,o)});
}
function count(el){
const to=parseFloat(el.dataset.to),dec=+(el.dataset.dec||0),pre=el.dataset.prefix||'',suf=el.dataset.suffix||'';
const fmt=v=>pre+v.toLocaleString('en-US',{minimumFractionDigits:dec,maximumFractionDigits:dec})+suf;
if(reduced){el.textContent=fmt(to);return}
el.textContent=fmt(0);const o={v:0};
ScrollTrigger.create({trigger:el,start:'top 90%',once:true,onEnter:()=>gsap.to(o,{v:to,duration:1.6,ease:'power3.out',onUpdate:()=>el.textContent=fmt(o.v)})});
}
function magnet(el,s=10){
if(reduced||matchMedia('(hover:none)').matches)return;
el.addEventListener('pointermove',e=>{const r=el.getBoundingClientRect();
gsap.to(el,{x:((e.clientX-r.left)/r.width-.5)*s*2,y:((e.clientY-r.top)/r.height-.5)*s*2,duration:.4,ease:'power3.out'})});
el.addEventListener('pointerleave',()=>gsap.to(el,{x:0,y:0,duration:.6,ease:'elastic.out(1,.4)'}));
}
document.addEventListener('DOMContentLoaded',()=>{
document.querySelectorAll('[data-sp-scramble]').forEach(el=>scrambleOnView(el));
if(reduced)return;
document.querySelectorAll('[data-sp-reveal]').forEach(el=>gsap.from(el,{y:40,autoAlpha:0,duration:1,ease:'expo.out',
scrollTrigger:{trigger:el,start:'top 90%',once:true}}));
});
return {reduced,lenis,split,rise,scramble,scrambleOnView,count,magnet};
})();
</script>
<!-- @include sections/01-hero.html -->
<main>
<!-- @include sections/02-thesis.html -->
<!-- @include sections/03-programs.html -->
<!-- @include sections/04-specimens.html -->
<!-- @include sections/05-germination.html -->
<!-- @include sections/06-voices.html -->
</main>
<!-- @include sections/07-footer.html -->
<script>window.addEventListener('load',()=>ScrollTrigger.refresh())</script>
</body>
</html>
```