The element-level transition blanket never could work here: the theme
lives in custom properties consumed through color-mix() inside
GRADIENTS (background-image is not animatable), inherited colors, and
per-frame inline writes. The tokens are now registered as typed
<color> @property rules and the .theme-anim class transitions the
VARIABLES on the root over 0.75s — every consumer re-resolves each
animated frame, gradients and scrims included, with zero per-element
transitions to fight hover states or the ruler's per-frame color
writes. A forced reflow commits the transition property before the
token flip, and watchThemeColors now derives twice (at flip and after
settle) so WebGL color targets land on the true final values instead
of a mid-fade sample.
Claude-Session: https://claude.ai/code/session_01YBoaBEt3gynzTwogBFtn4B
The word formation is now truly one surface: a single shared offset
(animated to zero) carries all seven letters, so nothing ever moves
relative to anything else — no overlapping R mid-flight. The block
starts tucked a tunable extra distance behind the blade edge (Blade
tuck) so no sliver peeks past the clip. Word stagger knob retired.
Theme switches cross-fade over 0.75s everywhere: the toggle stamps a
transient .theme-anim class so every themed property (colors,
backgrounds, hairlines, SVG fills — the logo-A rides the same
currentColor as the letters) transitions together, lifted after so
hover states and per-frame color writes stay instant. The galaxy's
WebGL colors lerp toward their targets at the same settle so the
canvas fades in step with the DOM.
The contrast scrim behind the center stack grows slightly and becomes
tunable (pads + peak strength, Wordmark group). Baked logo optics: cap
0.7, lift -0.006. And the tuner grows per-section ⧉ copy / ↺ reset in
every section header (resetGroup in the store), documented in TUNER.md.
Claude-Session: https://claude.ai/code/session_01YBoaBEt3gynzTwogBFtn4B
The letters now EXTRUDE from behind the mark: un-released letters ride
hidden with the gliding logo, released ones travel from wherever the
logo currently is to their slots, and a clip at the logo's right edge
(pre-transform local inset per letter) reveals them only as they clear
it — one surface sliding out, no fades, no overlap, no independent
letters. The old center-fade formation is gone.
The logo's optical fit becomes a live Wordmark tuner group (cap height,
baseline lift, side-bearing pad): the browser aligns an inline SVG's
BOX bottom to the text baseline while glyph ink is placed from font
metrics, so the sync is an eye-dialed correction, not something CSS can
derive. Knobs re-derive fontPx and re-measure on change; SSR renders
defaults so hydration stays exact.
Bakes: intro cards beat 1.0, return speed 1x (full intro for repeat
visits), wave dur 4s, wave flare 0.2 (range widened), flare lag 0.4,
FPS floor 30, target tau 0.25.
Claude-Session: https://claude.ai/code/session_01YBoaBEt3gynzTwogBFtn4B
The chromatic flare front now launches Flare lag seconds (0.2) after
the reveal front and sweeps the already-revealed field behind it as a
second pass, instead of riding fused to the reveal edge.
Claude-Session: https://claude.ai/code/session_01YBoaBEt3gynzTwogBFtn4B
The streaming-train rollout still swept far cards across most of the
spiral (too much rotation). New model per direction: cards start a
small tunable chunk (12%) short of their spread positions, and a
reveal WAVE expands from the logo uncovering them in radius order —
fade + rack-from-blur as the front crosses each card, with a strong
dispersion flare riding the front itself. Meanwhile the conveyor opens
at burst speed (3x) easing down to its normal drift, so the field
arrives moving and settles seamlessly. No card ever travels far; the
emanation is the wave. Knobs: Wave dur / Roll chunk / Burst x / Burst
ease / Wave band / Wave flare.
Claude-Session: https://claude.ai/code/session_01YBoaBEt3gynzTwogBFtn4B
Per-card path interpolation made the intro chaotic: a card destined
for the far edge crossed nearly the whole spiral in the time a near
card crawled a stub, and the speed spread churned the neighbor sizing
into rapid scale pumping. The rollout is now a single global offset R
running -1 to 0 with one ease, each card's cycle = max(0, target + R):
the conveyor itself pours out of the center as one train — every
moving card has the identical path speed at any instant, train spacing
is the true slot spacing (so sizing stays calm), unborn cards wait
invisibly at the origin behind the birth fade, and the whole field
settles into live drift simultaneously. Roll stagger knob retired; the
roll duration now covers the whole stream (default 1.4s).
Claude-Session: https://claude.ai/code/session_01YBoaBEt3gynzTwogBFtn4B
The tuner grows a generic action-button registry (registerTunerAction,
rendered in the panel header), and the intro registers 'Intro ↻': it
rewinds the master clock to zero at full-length pacing and fires a
replay bus that every one-shot consumer re-arms on — the ruler tick
cascade rebuilds its timeline, the navbar reveal replays, the hero
copy re-hides and waits for the copy beat, and the word formation
re-runs from the lone centered logo. Clock-driven pieces (the galaxy
rollout, the linework fade) replay automatically when t rewinds.
Reduced-motion visitors are guarded out of the replay entirely.
Claude-Session: https://claude.ai/code/session_01YBoaBEt3gynzTwogBFtn4B
A master intro clock (lib/intro.ts + IntroConductor) sequences the
whole page opening: the logo arrives alone at the center → the word
slides out from behind it (the leading A is now the brand mark itself,
an inline currentColor SVG glyph that rides the entire ruler heading
lifecycle) → the instrument assembles (ruler tick cascade + galaxy
linework + navbar contents, all on the same beat) → the copy stack
reveals → the galaxy cards roll out from the center along their arms
to their conveyor positions, passing through the birth styling they
already have, handing off to live drift with no seam.
Any input fast-forwards the whole timeline (never a jump cut); repeat
visitors (localStorage flag) get the full sequence compressed; reduced
motion opens settled. The word formation lives in the masthead so it
plays on every device, with a `forming` flag keeping HeadingFlow's
per-frame hero writes off until it completes. All beats live in the
new Intro tuner group; the galaxy's fade-intro knobs became rollout
knobs.
Claude-Session: https://claude.ai/code/session_01YBoaBEt3gynzTwogBFtn4B
All galaxy timers were keyed on the r3f canvas clock, and the
offscreen frameloop=never optimization stops that clock — r3f restarts
it from ZERO on resume, rewinding every timer: the intro cascade
replayed on each scroll back up, the linework re-faded, the governor's
grace period re-engaged, and ripple/punch ages went negative. The
scene now keeps its own monotonic time accumulated from frame deltas:
it only advances while rendering and never resets, so the intro plays
once (and resumes mid-cascade rather than skipping if the visitor
scrolls away during it).
Claude-Session: https://claude.ai/code/session_01YBoaBEt3gynzTwogBFtn4B
Two bugs from one root — writing font-weight on inline spans reflows
them. The rest branch reuses the flip pass with letter progress e = 1
(from == to), so the resting wordmark rendered at 620 and snapped to
900 the instant a flip began (the 'jumps to black'). And lighter
glyphs compressed the letters' natural flow positions while transforms
assumed the measured 900 layout, bunching the settled heading toward
the rail (the congestion).
The morph now keys on actual flip progress (rest stays 900/125), and
each letter span is pinned to its measured resting advance width with
centered glyphs — weight changes can never reflow the word, so poses
land exactly where the math puts them.
Claude-Session: https://claude.ai/code/session_01YBoaBEt3gynzTwogBFtn4B
The wordmark was Archivo Black — a separate single-weight cut — so it
stayed poster-bold after settling into the top stack while every other
heading sat at the display 620, and the mismatch read as off. It now
renders variable Archivo at the poster extreme (wght 900 / wdth 125%,
visually the Black cut), and the hero flip interpolates each letter's
weight and width down to the headings' 620 / 118% along its flight —
the change lands invisibly inside the motion, and the settled heading
matches its neighbors exactly. Reversible with scroll like everything
else, and the Archivo Black font download is gone entirely.
Claude-Session: https://claude.ai/code/session_01YBoaBEt3gynzTwogBFtn4B
Resolving one word's flip with a fixed ±4px margin could land the snap
just inside an adjacent word's flip zone — scrolling up out of GET
STARTED parked MADE WITH's lead letter in early flight. The snap now
computes each word's keep-flipped / keep-unflipped scroll bounds
(end-of-page words flip via either driver but unflip only when both
agree) and nudges the target inside the window where every other
currently-resolved word stays resolved. If the windows genuinely
conflict (the known overlap case), the original target stands — a
sliver beats a fight, and the arming invariant keeps it from looping.
Claude-Session: https://claude.ai/code/session_01YBoaBEt3gynzTwogBFtn4B
The arming invariant had a hole: 'snapping' clears early (the moment no
word is mid-flip, which happens mid-animation), so the velocity-based
re-arm read the snap's own animation tail as organic scrolling and let
the ping-pong chain re-arm itself. Velocity can't distinguish a snap's
motion from the user's.
Re-arming now lives exclusively on unambiguous input events — wheel,
touchmove, keydown, and rail thumb drags. Programmatic scrolls produce
none of these, so after a heading jump or a snap the system stays
disarmed until the user actually acts.
Claude-Session: https://claude.ai/code/session_01YBoaBEt3gynzTwogBFtn4B
Near the end of the page, MADE WITH's threshold flip zone and GET
STARTED's end-of-page flip zone overlap: no scroll position resolves
both words, so the snap ping-ponged forever — resolving one un-resolved
the other, and each snap's own settling triggered the next.
The snap system now carries an arming invariant: snaps and heading
jumps disarm it, and only organic scrolling (real velocity while no
programmatic animation is driving the page) re-arms it. A heading click
lands exactly where the user asked with no snap fighting it, a rest
mid-flip still resolves once as before, and no chain of snaps can ever
sustain itself.
Claude-Session: https://claude.ai/code/session_01YBoaBEt3gynzTwogBFtn4B
Section heading links get proper interactive states: hovering a word
brightens its letters from the dimmed ink-mix to full ink (eased, per
word), and clicking pulses it toward the accent as the jump takes off —
on top of the existing pointer cursor. The reduced-motion queue links
get an active accent state too.
The wordmark rendering under the top contrast pool was a stacking-
context trap: the hero content container's z-10 capped the masthead's
z-40 against the root-level fixed z-39 pools. The container itself now
carries z-40.
Claude-Session: https://claude.ai/code/session_01YBoaBEt3gynzTwogBFtn4B
The retention check treated the boosted rect (+margin) as the target's
territory and ran before the scan, so while an enlarged card overlapped
a neighbor, the pointer could never reach the neighbor — the lock held
across the whole 2x footprint. The pick is now a priority order that
treats the boost as a display overlay: the unboosted core retains
unconditionally (hysteresis intact), any other card under the pointer
wins outside that core, and the boosted rect retains only over empty
space. Sliding off a boosted card onto the one under it now switches
focus (and cancels the boost) the moment the pointer crosses into the
neighbor's territory.
Claude-Session: https://claude.ai/code/session_01YBoaBEt3gynzTwogBFtn4B
The baked Target tau of 10s was slowing the dispersion/blur/warp clear
along with everything else, so the focused card kept visible fringing.
The lock now runs two eases: a fixed fast optical clear (dispersion,
blur, curve warp, mouse warp, tilt, wash — clean in ~0.3s), while the
Target tau knob governs only the behavioral side (sizing pin, paint
order, decode priority, wobble kill).
Claude-Session: https://claude.ai/code/session_01YBoaBEt3gynzTwogBFtn4B
Clicking a card now also detonates a local displacement wave at the
exact click point in the card's own UV space: a soft gaussian ring
(the blurred-circle-drives-displacement compositing pattern, evaluated
inline in the fragment shader) pushes the texture radially outward as
it grows from the click point past the card edges and dies. Up to four
punches stack per card via a vec4 uniform array; only the clicked card
carries them — all other cards ignore the effect entirely. Click
coordinates map through the displayed (boost-included) size at click
time, and the ring radius/width scale with the live card size so a
boost mid-punch stays coherent. Knobs: Punch amp px / dur / width.
Claude-Session: https://claude.ai/code/session_01YBoaBEt3gynzTwogBFtn4B
The ripple WAS firing — its only output was a +0.03 aberration shift
lasting ~150ms per card, and on the clicked card itself it was zeroed
by the target lock's VFX clear. It now has a physical half: the
surface pops toward the camera as the ring passes (Ripple warp px,
normalized against the amp so tuning one doesn't change the other),
and the default dispersion amp rises to 0.05. The target stays clean —
its click feedback is the boost.
Clicking the held card toggles a 2× scale boost (Boost scale ×, eased
by Boost tau) that lasts only while the hold lasts: leaving the card
scales it back down first (the boost eases out faster than the spiral
resumes), and the next hold needs a fresh click. The boost enlarges
display size only — neighbors keep yielding against the base size, so
the enlarged card overlaps by design — and the hit test sees the
boosted rect so the lock can't drop while the pointer is inside the
big card.
Claude-Session: https://claude.ai/code/session_01YBoaBEt3gynzTwogBFtn4B
The edge ghosting was a feedback loop: the tug moves a card toward the
cursor, the hit test reads the tugged position, and the moment the lock
engaged the field hard-gated off — the card snapped ~14px back out from
under the pointer, released the lock, got tugged back in, every few
frames. Fixed on both ends: field strength (tug, tilt, warp, flare
pickup) now eases through a smoothed fieldK instead of a boolean gate,
and the lock is sticky — the current target retains while the pointer
stays inside its rect grown by the tug distance, so the release-slide
can never escape the lock.
The held card's scale pulse was neighbor wobble jittering its
nearest-neighbor separation with instant shrink. A locked card now
sizes on a smooth ease in both directions, and its NEIGHBORS yield the
exact remainder against its pinned height instead (2·gap − heldH), so
the collision guarantee moves to the cards around the hand rather than
the one being viewed.
Claude-Session: https://claude.ai/code/session_01YBoaBEt3gynzTwogBFtn4B
The global spin now brakes together with the rest of the spiral while
a card is held — the whole instrument stops under the hand, not just
the conveyor. The cursor field gains two missing physicalities: a real
translation tug (cards inside the field displace toward the pointer,
baked into positions before sizing so the collision guarantee sees
it), and surface weight — a vertex-shader bulge toward the camera at
the point of each nearby card closest to the mouse, riding on top of
the base curve warp. Both tunable (Tug px, Mouse warp px).
Bakes: flare add 0.032, curve 32, ticks/arm 16, card cap 0.25, hold
wobble 1.2.
Claude-Session: https://claude.ai/code/session_01YBoaBEt3gynzTwogBFtn4B
Pointer interactivity for the galaxy (1:1 camera makes it a world-space
point-in-rect sweep, no raycaster): near cards lean toward the cursor
like being gently pulled and pick up a dispersion flare that decays
behind sweeps. Landing ON a card locks it as the sole target — every
other card returns to usual, and the target racks clean: blur,
dispersion, and curve warp all clear as it straightens into a focused,
playing card (its clip outranks the decode budget) lifted above the
field. The hold brake freezes its conveyor motion almost instantly and
eases the rest of the spiral to a stop just behind it, with a wobble
swell while held — the hand holds the instrument still; releasing
resumes from wherever things stand.
Clicks spawn expanding rings of dispersion from the click point that
stack across multiple clicks. And clip assignment goes least-recently-
shown: a card reborn at the center picks the clip the field has gone
longest without, instead of keeping its round-robin deal forever. All
twelve knobs live in the new Galaxy pointer tuner group.
Claude-Session: https://claude.ai/code/session_01YBoaBEt3gynzTwogBFtn4B
The contrast pools move to their own z-39 layer, so the hero wordmark's
z-40 letters (and everything else — rail, section words) draw above
them; their masks now complete the fade inside the pool's own bounds so
the rectangle edge never reads as a cut. Pool presence follows real
occupancy, driven from the frame loop: the top pool fades in as the
wordmark settles into the stack, the bottom pool fades out as the last
queued word climbs onto the rail.
The rail frost's inner feather becomes a generated power-curve gradient
easing in toward the outer edge, with Frost solid % and Frost ease
knobs. And the galaxy's aligned-ring spawn is fixed: card index order
is arm-aligned (round-robin deal), so the intro stagger and the
governor's shed/regrow now walk a golden-ratio permutation instead —
plus a 4s governor grace period over the load stutter, which was
shedding the intro and regrowing it as visible rings.
Claude-Session: https://claude.ai/code/session_01YBoaBEt3gynzTwogBFtn4B
Heading dimming stops being translucency: dimmed words (queue, pile,
riding) now render a solid ink-toward-paper color-mix at the same
visual level, so they mask the footage passing beneath instead of
letting it bleed through 35%-alpha gray.
The rail frost's inner edge goes back to a feather, but on a
smoothstep-like multi-stop curve so no seam reads while letters fly in
and out (the hard cutoff + hairline read as visually split). The
contrast pools become part of the same frosted world: backdrop blur at
the rail's level with a matching non-linear corner mask, and their
size derives from the stacks' worst-case bounding boxes (full pile +
current heading up top, full queue below, longest label wide) plus a
Pool pad knob — no more viewport-height guessing. The reduced-motion
queue pool gets the same frost. Tuned @ Mpx defaults to the reference
screen's real 1.65 CSS-megapixels.
Claude-Session: https://claude.ai/code/session_01YBoaBEt3gynzTwogBFtn4B
Bakes the ultrawide tuning session (64 cards, wobble back on at 10,
softer nebula, decoders from 14% of the journey) and makes the count
viewport-responsive: the knob is tuned at a reference area (Tuned @
Mpx) and smaller viewports get proportionally fewer cards — the
neighbor-gap sizing grows the survivors so the field stays filled.
A perf governor watches the real frame-time EMA: sustained drops below
the FPS floor knob shed the highest-index cards (and their decode
pressure) quickly and regrow slowly, with shed cards growing back from
zero instead of popping. 0 disables it.
The rail frost loses its unintended inner gradient: hard cutoff, with
a hairline on the inner edge — the same edge treatment as the cards'
frames. The corner contrast pools get Pool alpha / Pool size knobs
(live — HeadingFlow re-renders on tuner changes), applied to the
static-mode queue pool too.
Claude-Session: https://claude.ai/code/session_01YBoaBEt3gynzTwogBFtn4B
Bakes the tuning session (6 arms, 32 cards, faster conveyor, -50°/min
spin, deeper curve warp, stronger dispersion) and adds the size
gradient: the cap itself ramps over the journey (Inner cap knob), so
inner cards stay smaller than outer ones even when space would allow
more. Newborn cards are rounded squares that morph into 16:9 as they
grow (Square until knob) — the cover-fit window now follows each
card's live aspect per frame.
The wordmark gets a focus pocket: a feathered backdrop blur over its
bounding box, softening the nebula right under the type. The ruler
stops clipping content: the reserved gutter is gone, content flows
under the rail, and a masked frost underlay (backdrop blur + theme
tint, feathering toward the page) keeps the instrumentation readable.
Page-bg radial contrast pools sit behind the top stack and bottom
queue (and the static-mode nav) so the sectional navbar always reads
over passing content.
Claude-Session: https://claude.ai/code/session_01YBoaBEt3gynzTwogBFtn4B
The galaxy's sizing moves from fixed birth-to-full scaling to the real
ask: every frame each card takes a tunable density fraction of the
exact axis-aligned gap to its nearest neighbor (O(N²) over ~20 cards),
so the field packs as tight as the Density knob allows and can never
overlap — growth is eased so freed space fills organically, shrinking
is instant so the guarantee never breaks. Wobble is now collision-safe
(sizing measures wobbled positions) but defaults to 0.
Cards travel past the viewport corner before wrapping, so the death is
never on screen; the exit fade is gone (birth fade stays) and the arm
linework extends past the exit. Corners get a tunable radius via a
rounded-rect SDF in the card shader, with the hairline frame drawn as
a band riding the same edge (replacing the rectangular EdgesGeometry
frames). Clip playback start points are staggered across each clip's
duration so loop resets never line up across the wall.
Claude-Session: https://claude.ai/code/session_01YBoaBEt3gynzTwogBFtn4B
Second hero iteration, replacing the etched SVG spiral: the wordmark
moves to the center of a radial poster (headline, CTAs, and proof
stacked beneath it) and becomes the emanation point of a WebGL galaxy.
Showcase cards are born small and blurred at the origin — a nebula
behind the brand mark — and grow, solidify, and unblur as they swirl
outward along multiple spiral arms, fading off past the edge while new
ones surface. Scroll scrubs the conveyor reversibly; idle drift and a
slow global spin keep it alive; per-card wobble keeps the cloud from
reading as beads on wires.
Each card is one draw call: poisson blur driven by journey position
(doubling as the loading placeholder resolving into footage), a slight
vertex bow toward the camera, and chromatic dispersion toward card
edges. A hairline underlay of arm guides, ticks, and a dashed
construction circle keeps a faint echo of the blueprint language,
tunable to zero. Decode budget from the old wall carries over: only
sharp outer cards hold live decoders; inner blurred cards freeze on a
primed first frame. All constants live in three galaxy tuner groups.
Claude-Session: https://claude.ai/code/session_01YBoaBEt3gynzTwogBFtn4B
The landing viewport becomes a single-stage poster: the justified
ARTCRAFT masthead (still the ruler's hero heading), headline, subline,
CTAs, and proof line all sell before the first scroll, over a new
blueprint spiral — an Archimedean curve drawn in SVG hairlines with
degree ticks, mono annotations, a dashed construction circle, and
Seedance render cards pinned along the winding. The spiral is an
emitter: cards are born faint at the origin and grow as they wind
outward; scroll scrubs the conveyor (reversible) and an idle drift
keeps it alive. Cards fade out of the copy block's corner.
Everything is SSR-complete (deterministic geometry on a 1600x900
cover stage — container-query math, no JS measurement), so no-JS and
reduced-motion visitors get the finished drawing. The WebGL render
wall and its tunables are retired; all spiral constants live in three
new tuner groups with info hovers.
Claude-Session: https://claude.ai/code/session_01YBoaBEt3gynzTwogBFtn4B
Tuner (see TUNER.md): a draggable, collapsible dev panel rendering every
group registered via defineTunables() as slider + number-input rows.
- Live: physics/light/material/halo values are read per frame; formation
values trigger a debounced re-sample
- Values persist to localStorage; modified labels tint accent; Copy puts a
JSON snapshot on the clipboard for baking back into defaults; Reset clears
- Shown in dev builds or with ?tuner=1; drag position and section collapse
state persist
- Policy (documented): every new tweakable constant registers with the tuner
by default
- Hero groups: Formation, Physics, Cursor & Light, Material, Halo
Formation: the hex lattice read too uniform. Balls are now placed by
multi-pass dart throwing over the glyph mask — big balls claim spots first,
smaller ones settle into leftover gaps, random depth within the slab, 3D
overlap rejection via spatial hash — a faked settled-pile impression, not a
physics solve. Stroke-margin test keeps big balls inside the letterforms so
the type stays crisp.
- Ball sizes now vary organically: smooth spatial value noise drifts size
clusters across the word, white noise roughens per ball; the neighbor cap
keeps every draw collision-safe
- Radial halo billboard tracks the cursor BEHIND the text plane, so balls
silhouette against the glow (additive in dark, soft accent wash in light)
- Cursor point light switched to physical inverse-square falloff (decay 2):
balls near the cursor are genuinely brighter; the light dims to 30% when
the pointer leaves so the resting state stays moody
- Dark mode is an eclipse rig: near-black lacquer balls (clearcoat physical
material), almost no front light, cool key + counter rim from behind, and
no environment map — scene.environment floods instanced materials at full
strength regardless of envMapIntensity, which was washing the whole word
to gray; light mode keeps the procedural room reflections
The hero brand mark is now the signature interactive moment: 'artcraft'
rasterized per-glyph (manual advances so no ligature shaping), sampled onto a
jittered close-packed lattice 2-3 layers deep, and rendered as instanced 3D
spheres under a perspective camera fitted so world units equal CSS pixels at
the text plane.
- Cursor is both a point light and a push force (radial + swirl, quadratic
falloff, toward-viewer pop); balls scatter, then rejoin on home springs
- Per-ball radii capped at half the nearest-neighbor gap, plus a 3D
spatial-hash collision pass, so spheres never clip through each other
- Mount plays an assemble: balls fly in from a scattered shell
- Idle: light wanders a slow Lissajous path; balls breathe subtly
- Fallbacks: static display-type wordmark for SSR/crawlers, reduced motion,
and WebGL failure; theme-aware colors via shared token watcher (extracted
to lib/theme-colors, now also used by the blocking/render comparator)
- Tagline 'Controllable AI for artists.' remains the crawlable h1
Standalone Next 16 app at frontend/apps/artcraft-website-next (excluded from
the npm workspace like artcraft-dashboard; React 19 + R3F 9 + GSAP + Lenis +
Tailwind 4). Branched from design/prototype0; the Marathon-style navbar is
ported to next/link with theme-aware tokens plus a light/dark toggle.
- Design system: hairline-grid 'instrument panel' language; light (blueprint
paper) + dark (viewport terminal) themes via CSS tokens, system-pref default
with persisted override applied pre-paint
- Landing: hero, capability ticker, feature grid (real product footage),
ownership, made-with (click-to-load YouTube), CTA, footer — all crawlable
SSR HTML; GSAP reveals are progressive enhancement only
- 3D hero: interactive blocking↔render comparator — wireframe R3F set clipped
over the product reel by a cursor-driven scan line; degrades to plain video
on reduced-motion/coarse-pointer/WebGL failure
- SEO: metadata + OG, sitemap.ts, robots.ts, SoftwareApplication JSON-LD
## Fixes
- Slow paste in prompt box: `execCommand("insertText")` is quadratic per
line in Chromium - now inserts the clipboard as a single text node
(linear)
- Model selector submenu not scrolling in focus mode: the modal's scroll
lock swallows wheel/touch on body-portaled panels - flyout now stops
propagation (same pattern as mention dropdown)
- Cap the always-expanded deck panel at container width so cards wrap
into rows; scroll past 3 rows instead of squeezing the editor
- Drop shrink-0 on the expanded deck root (min-w-0 max-w-full) so it
shrinks inside the webapp's flex-row wrapper
- Collapsed fan badge shows total ref count instead of +hidden