Build a single-file HTML landing page for "Throughline", a fictional AI delivery platform that routes between models, grounds them in your corpus, gates releases on evals and ships behind a canary. A shader fans glowing threads behind the hero; as you scroll they converge into a line that carries on down the page, past a stack of glass cards whose icons are cut clean through them. GSAP 3.12.5 and ScrollTrigger from cdnjs, Source Code Pro from Google Fonts, raw WebGL2 for the shader, everything else inline, no image files anywhere. Say in the footer that the product and every number on the page are invented. THE DESIGN SYSTEM — one spectrum, three appearances - Two surfaces only: #131417 and white at low alpha (.09 hairlines, .16 borders, .4 and .64 text). #0e0f12 for anything recessed. - One colour: a four-stop spectrum, linear-gradient(-45deg, #912acd 20%, #e6b71d, #16a242, #03b0ea 70%). It appears exactly three ways — clipped to text, as the SVG stroke on the throughlines, and as a 1.5px ring around a card — and nowhere else. Discipline is the whole look. - Clipped text needs filter: brightness(2) over #131417 or the middle stops go muddy. - Source Code Pro for everything, 15px/1.65 body. A landing page set entirely in mono reads as a page for people who live in a terminal, which is the point. THE CARDS — the icon is a hole, not a picture Five cards, each exactly 300x450 with a 20px radius, stacked down a 740px rig with alternating horizontal offsets above 640px. - Behind each card: a frosted plate (rgba(0,0,0,.28) + backdrop-filter: blur(8px)) and a spectrum ring. Draw the ring as an inline-SVG data URI: a rounded rect path followed by its own 1.5px-inset copy with fill-rule="evenodd", filled with a linearGradient. That is a gradient border with no extra element. - The card fill is black at the corners, not grey. A grey radial (the obvious choice) turns every card into a grey plate on a black page; black makes it a well, and the spectrum ring plus the frosted backdrop are all the separation it needs. Same rule as the slab below. - Behind all five, one tilted slab on a sticky wrapper. Make it DARKER than the page, never lighter: a pale panel at low opacity does not read as a plane behind the cards, it reads as a patch of grey background, and it is the first thing anyone points at. A deeper black still catches its own edges and the page stays black. It is also 150vh tall sticking to the bottom of its rig, so it slides a screen and a half past the stack and lays itself across the section below — clip the stack section with overflow: clip, never overflow: hidden, which makes the section a scroll container and kills the sticky outright. - The card itself is masked so its glyph is a hole cut through to whatever is behind it. Build that mask as ONE data URI containing a — a white 300x450 rect with the glyph painted black over it, applied to a second white rect — rather than as two mask layers with mask-composite: subtract. Self-contained subtraction needs nothing but plain mask-image support, so it works in every engine that can mask at all. - A second data URI holds the same glyph stroked at 7px and not filled; use it to mask a ::before painted with the spectrum. Everything inside the hole is gone, so what survives is a hairline of colour tracing the cut edge. - Author the glyphs in the card's own 300x450 coordinate space, in the lower third: a ring with a dot at its centre, a terminal window with a chevron and an underscore left solid inside the hole, three stacked discs, three bars, an arrow. Keep them chunky — a thin glyph reads as a scratch rather than a cut. THE THROUGHLINES — one line material for the whole page Four serpentine paths down a 740x3600 SVG, each a sine wave sampled at 36 points and smoothed into cubics (Catmull-Rom to Bezier), with different amplitudes and phases. Stroke them with the spectrum gradient, round caps, 20-40px wide, and give each a dasharray that is mostly one enormous gap — "20 50 120 50 20 50 300 50 20 50 150 50 20 20000" — so each line shows a short caravan of dots and dashes travelling its whole length instead of a repeating dotted line. Do not blur the SVG: a filter on the group re-rasterises a 740x3600 layer on every frame, and the dash offset already changes every frame, so it costs 21fps on the hero and 8 over the stack for a 1px softening on a 20-40px stroke. Position it against the DOCUMENT, not inside the card stack: it starts at the top of the page and runs past the last card, so one run of line serves the hero and the stack both. z-index 1 puts it above the slab and below the cards and the hero copy. It has two motions, and they are ADDED rather than switched between: - idle: a constant ~28px/second drift plus a sway and a small tilt, so while you are sitting on the hero the marks visibly circle it under their own power. - scroll: the big travel, about 3400 dash-pixels over the combined height of hero and stack. Because the scroll term starts at zero and the idle term never stops, the handover at the bottom of the hero has nothing to jump: you just start adding to a line that was already moving. Switch between the two sources instead and the dash pattern snaps the moment you cross over. Damp the sway by the hero's own scroll progress so the line orbits the headline and then settles into a rail for the cards, and ramp the whole thing from 45% to 100% opacity across the same range. THE POSTER The hero is a poster, not a paragraph: three stacked display lines in a heavy condensed face, uppercase, leading under 0.9, sized so the longest line sets the measure and the block runs almost the full width. A lime blob sits under the middle line and bleeds off the left edge. The support sentence tucks into the gap the short first line leaves beside it, and drops below the block once there is no gap left. The button is extruded rather than blurred - a hard offset shadow that shortens on hover and collapses on press. Set it on its own navy panel that falls to the page's black at the foot, so the poster reads as one object and the rest of the page carries on dark. Check for a stale centred rule further down the sheet before wondering why a left-aligned hero keeps centring itself. THE BLOB The green is the hero's ground, not an ornament beside the type: one blob filling the whole panel, sliced rather than fitted so it bleeds past every edge, with the poster sitting on top of it. It is not a border-radius shape, it is circles run through a gooey filter: a Gaussian blur followed by a hard alpha contrast (feColorMatrix, alpha row 20 and -9). Anywhere two circles overlap comes back above the threshold and welds, so the cluster reads as one liquid mass with necks and bulges you cannot author out of four border-radius percentages. Set color-interpolation-filters to sRGB or the default linearRGB washes the greens to a pale mint. Give every circle the same gradient with gradientUnits="userSpaceOnUse" and one continuous ramp runs across the whole mass instead of restarting inside each circle. Hover the headline and the droplets burst out and evaporate, and the core draws in behind them, so the blob comes apart and shrinks to a bead rather than simply fading. Four things decide whether that reads at all. The core has to be a COMPLETE blob, authored in the markup — the droplets are generated, so with no JS or under reduced motion a skimpy core leaves a bead hiding behind a letterform. Use the individual translate and scale properties rather than one transform, because they need different clocks: collapse a droplet while it is still travelling and the goo threshold eats it before it clears the mass, so you see a ripple and never a break-up. Fly out hard, hold full size while they cross the gap, then shrink. Per-droplet stagger goes in a custom property the transition reads, because an inline transition-delay flattens both halves back onto one clock. Budget the filter before you scale it. Gooeyness is relative, so a blob that fills the hero wants a blur proportional to it - and a 45px blur over a 1512x874 surface measured 24fps in-run against 53 with the blob hidden. Droplet count turned out to matter more than blur: at the same radius, halving the droplets bought 7fps and below that the curve flattens. Fewer, larger droplets and a smaller blur put it back at the page's own idle. will-change and contain bought nothing - the cost is the raster, not the layer. Over a light-on-dark poster the mass also has to come down to about a third opacity or it eats the headline. Aim the spray at whatever is actually empty. When the blob was a small shape behind the headline a radial burst sent half the droplets where the type covered them, and the answer was to aim the spray at the one open column; a blob that fills the panel has open ground on every side and comes apart evenly. And widen the filter region hard (x/y -110%, 320% square) or the beads are guillotined mid-flight at the default -10%/120%. Trigger on the headline with :has(), not on the whole poster. The poster's box is most of the hero, so :hover there meant the blob was dissolved for as long as anyone was reading it. The blob is the headline's previous sibling, which is why the parent needs :has(). Give the CTA and meta rows position and a z-index too, or the droplets — positioned, z-index 1, in a parent that is not a stacking context — paint over the buttons. THE WATERLINE The poster does not end on a straight edge. Four bands of procedural wave roll across the foot of the hero and hand the page over to the sections below it, and each band carries one stop of the throughline's own gradient on its crest - violet, amber, green, cyan, back to front - so the wires that start under the hero are announced by the water above them. Build the geometry in script, not by hand. Each band is a sum of sines whose cycle counts are whole numbers across one tile width, so the function repeats exactly: sample it over two tiles, scroll the group exactly one tile on a linear loop, and the seam is not merely hidden, it does not exist. Randomise each component's amplitude and phase per load and no two crests are ever stamped from the same shape. Give the bands different durations and alternate their direction and the stack reads as depth. Prove the periodicity by measuring the crest path with getPointAtLength at 0, half and full length - the three y values must be identical, not close. Two things decide whether it looks like water or like four stray lines. First, the fill gradients: a body path runs from its own crest down to the bottom of the box, so an objectBoundingBox gradient is already most of the way to black before the wave even starts. Use userSpaceOnUse and key each one to where its crest actually sits, each starting a step darker than the band above it. Second, do not let the water reach the page colour until the last band - that one is flat page black, and it is the only thing allowed to be, because it is the handover. Reverse the hero's own fall of colour to meet it: the ground gets lighter as it nears the water, and its last stop has to equal the top band's first stop exactly or a seam shows along the highest crest. Non-scaling strokes on the crests, or squashing the viewBox to the band height thins them out. The band is absolutely positioned, so add its height to the hero's bottom padding or it sits on the meta row. THE LINE STARTS WHERE THE POSTER ENDS Do not put a separate effect behind the copy. A shader fan in the hero and an SVG stroke below it are two different materials, and no amount of cross-fading hides the swap at the fold — you can see one thing stop and another start. The throughline belongs to the sections under the poster, so measure its birth off the hero's bottom edge rather than off anything inside it - a real boundary, not a guess. Clear it by more than the mask's fade distance, or the first wires light up inside the poster. Below that, the four paths gather toward the centre of the stack and splay apart as they run down, both terms unwinding to zero so every line ends up on the geometry it was authored with. Add a float on its own period and a lean toward the pointer, scaled the same way. Find each path's crossing by binary-searching getPointAtLength on the referenced path — a cannot be measured, so ask the it points at — and re-measure whenever layout moves, because the headline shifts when the webfont lands. Under reduced motion there is no rig to refresh, so hook the font promise and resize directly or the birth point is stale. Then reveal it with a mask rather than an opacity: nothing exists above the birth, the reveal starts inside the letterforms so the line shows through the counters of the last word, and a tip travels down the page as you scroll with a soft trailing edge for the head of the line. That is the whole trick — the line is not fading in, it is being pulled out of the text. Vary the gauge with it too: about a fifth of the authored stroke-width on the hero, thickening to full as the fan converges, so the wires read as wires up top and as the throughline below. Write stroke-width only when it actually changes — it re-rasterises the SVG, and the idle float must stay transform-only or the hero re-rasters sixty times a second. Do not drive it from a scrubbed scroll handler either: that handler never fires at progress 0, so a page loaded at the top keeps whatever the pre-rig fallback last set. Track the pointer on the hero section rather than on the line, because the headline sits on top and the line takes no events. Over the hero it must frame the copy, not cross it. Give the snake a top fade under the nav and a nested child with a radial-gradient mask that is transparent in the middle — a hole the headline sits in. Two masks on two nested elements, not two layers and mask-composite: one mask per element is portable, compositing two of them is not. THE SLAB A 3D plate behind everything: rotateY(20deg), 150vh tall, a dark gradient at 25-30% opacity with an inset darker panel inside it. Make it position: sticky inside the stack section rather than position: fixed — fixed leaks into every other section of the page and you end up toggling its opacity from JS. Drift it vertically with a second custom property at roughly 40% of the scroll rate. Pause the whole rig with an IntersectionObserver when the snake scrolls off, and skip it while document.hidden. WHAT WILL BREAK, IN THE ORDER YOU WILL HIT IT 1. Per-character opacity does nothing inside a background-clip: text parent. The gradient is painted by the PARENT and clipped to the union of its descendants' glyphs, so a character at opacity 0 still cuts its own shape out of that paint and stays fully visible — screenshot the same frame twice and they come back byte-identical. Fix: after splitting, give every character its own copy of the gradient with background-size set to the whole block and background-position set to minus the character's offset within it. The slices line up into one continuous spectrum, opacity starts working, and the parent's background is switched off once it has run. Repaint on resize and after document.fonts.ready, since the offsets are measured. 2. Never set scroll-behavior: smooth on html with ScrollTrigger. They fight over the scroll position and every scrubbed value stops matching the page. Let in-page links call scrollIntoView({ behavior: "smooth" }) themselves. 3. Do not put the page's own script behind the CDN tags. A slow request left the copy unsplit and the nav dead for as long as it took. Run your script first, load GSAP with defer after it, and wire the rig on window load only if window.gsap exists — the page must be readable and clickable when the CDN never answers at all. 4. A card already on screen at load never crosses its trigger, so reveal whatever is in view outright and re-check on ScrollTrigger refresh. SECTIONS Sticky blurred nav with the wordmark, four links, a burger under 880px and a spectrum CTA; the poster hero closed by the wave band; the card stack; four stage cards reusing the same glyphs as ordinary inline SVG; a four-cell numbers strip; three pricing tiers where the recommended one is the only element on the page with a spectrum border; a five-row FAQ built from native details/summary with the marker swapped for a + that becomes a minus; a closer; and a footer with the fiction disclaimer. RESPONSIVE AND A11Y FLOOR Under 640px the snake and the slab are hidden and the cards centre in one column — the rig is decoration and it is not worth a horizontal scrollbar. Stage cards go 4 to 2 to 1, tiers stack at 900px. No horizontal page scroll at 390 or 1512. Focus rings in cyan. Under prefers-reduced-motion nothing scrubs: every character is already on, the marks sit parked part-way along their lines so the stack still reads as a diagram, and the slab does not drift.