QubeTX // Design system

The system of record.

Every token, component, motion primitive, and law behind qubetx.com — documented with the thing itself. The specimens on this page are the real components, imported live: if it renders here, it ships. This is the central design system for every QubeTX project from here on out, written for the people and the agents who will build them.

Spec of record
DESIGN_SYSTEM.md in the qubetx.com repo — this page renders it live
Stack
Next.js 16 · React 19 · TypeScript · anime.js v4 · Framer Motion 12 · Lenis
Kit
Sidebar → Download kit (.zip): tokens, fonts, source modules, agent docs
01 // Principles

The seven principles

Seven sentences govern every QubeTX surface. Each one is enforceable — by a test, a lint rule, a review grep, or a Lighthouse score — because a principle that can't fail a build is just a mood.

PRINCIPLE / 01

Detail is the product

The site is the portfolio piece: the type, the motion, the dots reacting under the cursor are the same attention we sell. If it ships under the name, it ships finished.
PRINCIPLE / 02

Server HTML is the final state

Every surface renders complete and readable before JavaScript exists. Animation is a curtain over real content — never the source of it.
PRINCIPLE / 03

One owner per property

Every animated property on every node has exactly one writer. Fighting animations are an ownership bug, not a tuning problem.
PRINCIPLE / 04

Reduced motion is the final state, instantly

prefers-reduced-motion never gets a slower version, a fade, or an opt-out — it gets the end state, immediately, everywhere.
PRINCIPLE / 05

The terminal is honest

Timestamps are real clocks. Progress parks at 99% and says why. Statuses reflect publish-time reality. The diagnostic flavor works because nothing in it is fake.
PRINCIPLE / 06

Motion earns its place

Things move when meaning changes — a value, a state, an arrival. Decoration moves once (entrances) or never. The budget is a frame: 60fps is a promise, not a goal.
PRINCIPLE / 07

The laws outlive the code

No ResizeObserver. anime through the seam. Triggers are IO, scrubbing is Lenis. Sentence case in storage. Every law here was paid for — breaking one re-runs the incident that created it.
02 // Signature

The signature

A stroke-drawn cube, a heavyweight wordmark, mono metadata in the corners, and a deep-void field with exactly one gradient. The signature is geometric, wireframed, and quiet — drawn, never filled.

The cube + the wordmark
QubeTX
The marks
MarkTreatment
The cubeStroke-only isometric SVG (viewBox 0 0 218 233), drawable paths — the mark un-draws and redraws for brand moments
The wordmarkQUBETX in Makira Black — solid in chrome, stroke-outlined (--color-border-bright) when oversized (the footer treatment)
Corner metadataMono micro-text pinned to surface corners: build ids, node ids, versions — the machine-report frame
The void stackSolid #05070f → static 28px dot texture → corner glow → ScrollTrace → content. Deliberately static behind the live field
The gradient ruleA 1px blue→violet hairline under headings — drawn on entry, never animated again
RULE / 01

Drawn, never filled

The cube is strokes (createDrawable-compatible). No fills, no solid variants, no drop shadows. Recolor through currentColor only.
RULE / 02

The wordmark carries weight

Makira Black, uppercase via CSS, tight tracking. Oversized usage switches to the stroke outline so scale adds structure, not shouting.
RULE / 03

Corners hold the metadata

Version strings, node ids, build info live in corner-pinned mono micro-text — the frame that makes any surface read as an instrument.
RULE / 04

One signature moment per surface

The boot screen, the logo redraw, the dot field’s load beat — brand theater happens once per surface, at arrival. (Some sequences are not documented here; the old codes still work.)
03 // Registers

The two registers

One token set, two voices. The landing register persuades — editorial sections, choreographed entrances, the dot field. The technical register operates — terminals, tables, install blocks. Both are v3; the difference is posture, not palette.

Choosing a register
SurfaceRegister & sections
Marketing / landing surfaceLanding register: hero + dot field, editorial sections, cards, the full entrance choreography
Product / tool pageTechnical register: TerminalFrame hero, CapabilityRows, CommandTable, InstallBlock + DownloadCard (§13–14)
Documentation surfaceThis page’s chrome: grouped sidebar + numbered sections + live specimens + the rail
Dashboard / live dataTechnical register + live slot rolls on changing values (§12, §17) — no entrance theater
04 // Color

Color

A deep-void navy field, one electric blue, and a blue-to-violet gradient. Hover any row — token names decode, because even a palette table gets the terminal treatment.

Surfaces & structure
--color-void#05070fPage / hero / header / cell background — the void everything sits on
--color-background#0a0f1cAlt background (legacy alias --dark-bg)
--color-surface#0d1117Cards, terminal chrome, panels (alias --card-bg)
--color-surface-raised#111827Hovered cells, dropdown panel
--color-border#1a22361px hairlines, grid-cell gaps, pills
--color-border-bright#2c3a5cHovered borders, code chips, wordmark stroke
Accent & gradient
--primary-blue#0066FFTHE accent: eyebrows, active nav, focus ring, status ACTIVE (alias --color-primary)
--color-arrival#3385ffSlot-roll arrival flash, accent terminal lines≈5.7:1 AA on void — exists because #0066FF is only ~4.3:1 as text
--gradient-brand#2563eb → #7c3aedHeadline line 3, stat numerals, progress bar, hairline rules
--glow-bluergba(37,99,235,.25)Button hover glow shadows
Text inks
--text-primary#ffffffPrimary text
--text-secondary#94a3b8Body copy, descriptions
--color-text-dim#76869fMono labels, metadata, corner texttuned to ≥4.5:1 AA on void — never darken it
Status & stripes
status green#22c55e[OK] flashes, STABLE chips (hardcoded at use sites)
stripe alt#070a14Alternating product-row background (hardcoded in row modules)
RULE / 01

Tokens, never hex

Component CSS references custom properties exclusively. The only hex values in the codebase live in the token blocks of globals.css (and the two documented hardcoded exceptions above).
RULE / 02

The dim ink is load-bearing

--color-text-dim sits exactly at AA on the void. Darkening it for taste breaks the accessibility floor; brightening it breaks the hierarchy.
RULE / 03

Arrival is transient

--color-arrival exists for moments — slot-roll flashes, accent terminal lines. It never persists as a resting text color; resting accents use --primary-blue on non-text or large text only.
RULE / 04

The gradient is earned

One gradient, used sparingly: the third headline line, stat numerals, the progress bar. A surface full of gradients is off-register.
Recipe — consuming the palette in a new project
/* Copy the :root token block from the kit's tokens/qubetx-tokens.css,
   then write component CSS against the tokens only: */
.panel {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  color: var(--text-secondary);
}
.panel:hover { border-color: var(--color-border-bright); }
05 // Typography

Typography

Two families carry everything: Makira (sans + display, 400/500/700/900) and IBM Plex Mono (400/500/600). Both are local woff2 via next/font — never referenced by literal family name.

The display ramp — live at real tokens
hero h1 · clamp(2.25rem, 8cqw, 5.75rem) · Makira 900 · container-query sized
Solid code.
--text-h2 · clamp(1.75rem, 1rem + 3vw, 3rem) · Makira 900 · uppercase · lh 1.05 · ls −0.02em
What we build
--text-h3 · clamp(1.125rem, 1rem + .75vw, 1.5rem) · Makira 700 · card titles
API Infrastructure
--text-body · clamp(.9375rem, .875rem + .35vw, 1.0625rem) · Makira 400 · lh 1.6–1.7
High-performance web solutions and scalable infrastructure that power your business today—and expand your potential for tomorrow.
--text-mono-label · 0.7rem · Plex Mono 500 · ls 0.12em · uppercase · pills, nav, metadata
04 // About us · response time: < 24h
RULE / 01

Sentence case in storage

Display text is stored as "Get Started", never "GET STARTED" — UPPERCASE is always text-transform. Data stays readable; the brand stays a stylesheet decision.
RULE / 02

Never literal font names

next/font rewrites family names (__makira_a1b2c3). Always reference var(--font-sans) / var(--font-mono) / var(--font-display). This is also why Pretext resolves COMPUTED names.
RULE / 03

Container queries fit headlines

The hero h1 is sized in cqw against its own column (the longest line is ~12.2em of Makira Black) — viewport units cannot know column width. --text-display stays as the no-CQ fallback.
RULE / 04

Mono is machinery

Plex Mono marks the terminal register: labels, nav, eyebrows, tags, commands, metadata. Prose never sets in mono; mono never exceeds a line or two.
RULE / 05

Letter-spaced text is never measured

Canvas measurement ignores letter-spacing — tracked mono labels are banned from Pretext and from slot-roll width assumptions beyond what the sizer cells handle.
Recipe — wiring the fonts in a new project (next/font/local)
// src/fonts/index.ts — copy from the kit (fonts/ ships the woff2 files)
import localFont from 'next/font/local'

export const makira = localFont({
  src: [
    { path: './Makira-Regular.woff2', weight: '400' },
    { path: './Makira-Medium.woff2', weight: '500' },
    { path: './Makira-Bold.woff2', weight: '700' },
    { path: './Makira-Black.woff2', weight: '900' },
  ],
  variable: '--font-makira',
  display: 'swap',
})
// layout.tsx: <body className={`${makira.variable} ${plexMono.variable}`}>
// globals.css maps the stacks:
//   --font-stack-sans: var(--font-makira), ui-sans-serif, system-ui, sans-serif;
06 // Spacing

Spacing & structure

An 8px grid, clamp()-based rhythm, and one container token everything hangs from. The width of each bar below is the literal value.

The ladder — width is the value
--space-xs8px
--space-sm16px
--space-md24px
--space-lg32px
--space-xl48px
--space-2xl64px
--space-3xl96px
Structural tokens
TokenValue / law
--grid-unit8px — the base everything multiplies from
--container-max1440px; widens to 1800px at ≥2560px (the TV tier — sections, the cqw headline, and the trace gutter all derive from it)
--container-padding-xclamp(16px, 4vw, 32px) — horizontal page padding
--section-spacingclamp(48px, 10vw, 96px) — vertical rhythm between sections
--touch-target-min44px minimums — enforced ONLY under @media (pointer: coarse); fine pointers use padding-based hit areas
radius scale2px chips · 4px pills/bars · 6px panels/buttons · 999px tab pills — subtle, never rounded-friendly
RULE / 01

Multiples of 8, clamps for rhythm

Fixed gaps come off the ladder; page-level rhythm (section spacing, paddings) uses clamp() so small screens compress and TVs breathe — no breakpoint soup.
RULE / 02

One container token

Every section caps at --container-max. The TV tier works because NOTHING hardcodes 1440 — change the token, the whole site re-fits.
RULE / 03

Touch targets are conditional

The 44px minimum applies under coarse pointers only. Desktop keeps tighter, padding-defined hit areas — density is a feature of the register.
RULE / 04

Hairlines define structure

1px borders in --color-border do the work shadows would do elsewhere. Elevation by surface color change, not blur.
07 // Motion tokens

Motion tokens

One house curve in three notations, one overshoot reserved for the slot roll, five durations, five staggers, three springs. Click any curve to replay it — the dot rides (time, eased value); the block below it is the same ease as pure feel.

The curves — live on the real anime engine
Durations (Framer seconds / anime milliseconds)
TokenValue / use
DUR.micro / MS.micro0.18s / 180ms — hovers, presses, chip flips
DUR.fast / MS.fast0.3s / 300ms — reveals of small elements, sweeps
DUR.base / MS.base0.55s / 550ms — standard entrances (cards, text rises)
DUR.slow / MS.slow0.8s / 800ms — large surfaces, section-scale moves
DUR.hero / MS.hero1.1s / 1100ms — the entrance choreography ceiling
Staggers
TokenValue / use
STAGGER_MS.chars18ms — per-character (wordmark rise, letter rolls)
STAGGER_MS.words40ms — per-word (RevealText default)
STAGGER_MS.lines90ms — per-line (hero headline)
STAGGER_MS.cards80ms — per-card (grids)
STAGGER_MS.nav60ms — per-nav-item (header entrance)
Springs — press the pads (Framer Motion presets)
RULE / 01

One curve, three notations

EASE (FM tuple), EASE_CSS (string), and EASE_ANIME (function) are the same curve. anime 4.4 REMOVED string cubic-beziers — always import the function form.
RULE / 02

The overshoot is reserved

EASE_SLOT_CSS belongs to the slot roll's landing. Everything else stays on the no-overshoot house curve — restraint is what makes the roll's bounce read as an event.
RULE / 03

Tokens, not numbers

Durations and staggers come from tokens.ts. A 437ms animation is a review comment; MS.base is a decision already made.
RULE / 04

Under 400ms for micro

Micro-interactions (hovers, flashes, rolls) land well under 400ms total. The slow end of the scale is for entrances the user watches once.
Recipe — the tokens in each engine
import { EASE, EASE_ANIME, EASE_CSS, MS, DUR, STAGGER_MS, SPRING } from '@/lib/motion'

// anime.js (via the seam):
animate(targets, { y: ['110%', '0%'], duration: MS.base, ease: EASE_ANIME })

// Framer Motion:
<motion.div transition={{ duration: DUR.fast, ease: EASE }} />
<motion.button whileTap={{ scale: 0.96 }} transition={SPRING.press} />

/* CSS: */
transition: color 0.2s var(--ease-out); /* --ease-out === EASE_CSS */
08 // Iconography

Iconography & glyphs

One icon library (Lucide), one rendering rule (20px, strokeWidth 1.5, aria-hidden), one logo (the stroke cube), and a small set of mono glyphs that carry the terminal flavor.

The registry — SERVICE_ICONS + chrome icons (20px / 1.5 stroke)
code
wrench
network
cloud
shield
ArrowRight
ArrowUpRight
ChevronDown
Menu
X
Download
Copy
The cube — stroke-only, drawable paths
Mono glyph set
GlyphWhere it lives
Braille block — the services TextLink glyph (texture, not language)
Block cursor — terminal prompts (CSS blink, aria-hidden)
↗ / ↑External visit chips / back-to-top arrow
[ ]Bracket hovers on header nav — pseudo-elements, never typed
// ·Mono separators: "04 // About us", "v3.1.0 · June 2026"
RULE / 01

20px, 1.5 stroke, aria-hidden

Every Lucide icon renders at 20px with strokeWidth 1.5 and aria-hidden — the stroke weight matches the cube. Icons never carry meaning alone; the adjacent text does.
RULE / 02

String-keyed registry

Content data stores icon KEYS (icon: 'cloud'); SERVICE_ICONS[key] resolves the component. content.ts stays serializable.
RULE / 03

The cube is stroke, never fill

QubeTXLogo paths are svg.createDrawable-friendly (the redraw moment depends on it). Recolor via currentColor/props — never add fills.
RULE / 04

Glyphs are texture

The mono glyphs (⠿ ▮ //) are decoration in the terminal register — always aria-hidden, never load-bearing for meaning.
Recipe — icons in a new project
import { SERVICE_ICONS, ArrowRight } from '@/components/ui/icons'

const Icon = SERVICE_ICONS[service.icon]   // key from content.ts
<Icon size={20} strokeWidth={1.5} aria-hidden="true" />

// New icon? Add it to the registry file — never import lucide-react
// directly in components (one seam keeps the inventory auditable).
09 // Buttons & links

Buttons & links

Three interactive text primitives: the outlined CTA, the mono text link, and the letter-roll label. All three obey the destination rule — external links announce themselves on hover, internal links confirm on click.

OutlineButton — md + magnetic + hoverLabel (external: hover-rolls the teaser, mouse only)
TextLink — glyph + flashLabel (internal: click-flash + Lenis smooth scroll)
RollingLabel — the hover letter roll for link lists (hover it)
RULE / 01

The destination rule

Leaving the site? The label slot-rolls a teaser on hover (hoverLabel). Staying on the site? It flashes on interaction (flashLabel). Mouse pointers only — coarse pointers never hover-roll.
RULE / 02

Quiet rolls on gradient faces

OutlineButton's hovered face is the brand gradient, so its rolls are ink-only (color: null) — the arrival blue would vanish. TextLink rolls keep the arrival blue (it sits on the void).
RULE / 03

Width is reserved

Both components render invisible sizer stacks so the widest label defines the box — a roll never resizes the button (hover shifts are NOT CLS-exempt).
RULE / 04

Press is a spring

FM whileTap squash (scaleX 1.04 / scaleY 0.92, SPRING.press) on the anchor; the slot cells live two levels down — one owner per property holds.
RULE / 05

External means external

https?:// hrefs auto-get target="_blank" rel="noopener noreferrer" + an sr-only "(opens in a new tab)" — enforced by tests.
RULE / 06

Magnetic is a wrapper

<Magnetic strength={6}> owns its node's transform; magnetic on the button only sets data-magnetic so the cursor ring docks. Never stack other transforms on the wrapper.
Recipe — CTAs under the destination rule
// External destination → hover teaser
<OutlineButton href="https://app.youform.com/…" hoverLabel="Open the form" magnetic>
  Get Started
</OutlineButton>

// Internal destination → click flash (+ Lenis smooth scroll on TextLink)
<TextLink href="#services" glyph="⠿" flashLabel="Navigating…">
  Explore Our Services
</TextLink>
10 // Pills & labels

Pills & labels

Every region of a QubeTX surface is named by a small mono label: pills, bars, eyebrows, status chips. The heading above this paragraph is the SectionHeading composite — pill decode, word-rise title, gradient rule — documenting itself.

LabelPill — pill and bar variants
04 // About usWeb Development & Digital Infrastructure
Status chips — the product-row states (source of truth: ProductCard.module.css)
STABLEACTIVE
RULE / 01

One label spec

--text-mono-label: 0.7rem Plex Mono, 0.12em tracking, uppercase via CSS, --color-text-dim ink. Pills add a 1px border; bars add a 2px blue left rule and blue ink.
RULE / 02

Never Pretext-measured

Letter-spaced labels are banned from Pretext (canvas measurement ignores tracking). Pills size themselves with padding; they never shrinkwrap.
RULE / 03

Status is binary and earned

STABLE (green) and ACTIVE (blue) are the only product states. A status chip reflects reality at publish time — it is data, not decoration.
RULE / 04

SectionHeading is the one heading system

label + title + optional subtitle/aside. The pill decodes on first view, the title rises word-by-word, the rule draws — one IO trigger for the composite; reduced motion renders it static.
Recipe — labels and headings
<LabelPill>02 // Product line</LabelPill>
<LabelPill variant="bar">Web Development & Digital Infrastructure</LabelPill>

<SectionHeading
  label="01 // Services"
  title="What we build"
  subtitle="Web development, infrastructure, and everything that keeps both running."
/>
11 // Cards

Cards

Three card species, each with its own pointer response: service cells glow before you reach them, product rows compound-hover as one link, project cards tilt under the cursor. All live below — these are the production components with the production data.

ServiceCard — proximity glow (move the pointer around the grid)
01

Web Development

Modern, responsive website design and development with cutting-edge technologies. From static sites to complex web applications, we create tailored solutions that elevate your digital presence.

02

Maintenance

Comprehensive website maintenance including security updates, performance optimization, content updates, and technical support. Keep your digital platform running at peak efficiency.

RULE / 01

One pointer response per card

Service cells glow (proximity), product rows compound-hover (CSS), project cards tilt (rAF vars). A card never does two — the response IS the card’s identity.
RULE / 02

Glow lives on the grid

useProximityGlow attaches to the cards' CONTAINER and writes --mx/--my; each card's ::before gradient does the distance falloff in CSS. One listener, any number of cards.
RULE / 03

Tilt owns its own node

ProjectCard’s tilt writes CSS vars consumed by the inner article; the FM entrance variants live on the outer anchor — different nodes, no transform fights.
RULE / 04

Whole row, one link

ProductCard is a single anchor — code chip, copy, tags, status, and the VISIT arrow all hover together via descendant selectors. No nested interactive elements.
RULE / 05

Descriptions are Pretext blocks

Card body text wraps through PretextBlock (min-height reservation; shrinkwrap only when left-aligned) so async font swaps never shift card heights.
Recipe — a glowing card grid
const glowRef = useProximityGlow<HTMLDivElement>()

<div ref={glowRef} className={styles.grid}>
  {items.map((service, i) => (
    <ServiceCard key={service.id} index={i} {...service} />
  ))}
</div>
/* Card CSS reads the vars the hook writes:
   .card[data-glow]::before {
     background: radial-gradient(240px at var(--mx) var(--my), …);
   } */
12 // Stats & KPIs

Stats & KPIs

A QubeTX stat doesn't fade in — it counts. The numeral climbs from zero in decelerating slot-roll steps and lands in arrival blue; hovering re-verifies it (terminal flavor: drain to zero, climb back, same number). Scroll these into view, then hover them — and try leaving mid-count.

StatValue — count-up entrance + hover re-verify (the About-section band)
07Client Projects
05Products Shipped
100%In-House
RULE / 01

The entrance is a count made of rolls

First view: the numeral climbs 0 → value in exponential-approach steps — big jumps when far, single ticks at the end — quiet intermediates, arrival-blue landing. Digit count and prefix/suffix hold: "100%" counts as three zero-padded digits + %.
RULE / 02

Re-verify on hover

Mouse hover drains the number briskly to zero, then climbs back to the SAME value while the label scramble-decodes. Repeatable, and it never changes the number — that’s the point.
RULE / 03

The count always converges

The displayed number chases a single goal (zero or the value), so a pointer leaving mid-countdown just turns the count around from wherever it is. No stuck states, no snapping — spam-hover all you like.
RULE / 04

Gradient numerals need the clip chain

The numeral is gradient text (background-clip: text). Chrome won't clip to absolutely-positioned roll faces — the per-face background: inherit + clip chain in StatValue.module.css is load-bearing. Without it the digits are invisible.
RULE / 05

Tabular numerals

font-variant-numeric: tabular-nums keeps digit cells equal-width so rolls almost never trigger width easing — the band stays rock-still.
RULE / 06

Reduced motion = the value, immediately

Under prefers-reduced-motion no cells are ever built — the server-rendered numeral is simply there.
Recipe — a KPI band
<div className={styles.kpiBand}>
  <StatValue value="07" label="Client Projects" />
  <StatValue value="100%" label="In-House" />
</div>
/* Band CSS: a bordered grid of cells; each cell hover-raises
   (background-color transition) and shows the corner + mark. */
13 // Terminal surfaces

Terminal surfaces

Product pages speak terminal. These are the canonical surfaces — output frames that boot-print like the loading screen, command references, and capability rows — generalized from reports.qubetx.com and rendered in v3 tokens.

TerminalFrame — boot-print render with live timestamps (scroll it into view)
TR-300 // Sample outputNODE_ID: QBTX-01
[··:··:··]tr300 --title "MACHINE REPORT"
[··:··:··]MOUNTING /SYSTEM/PROBES :: CPU, MEM, DISK, NET
[··:··:··]COLLECTING 14 DIAGNOSTIC CHANNELS...
[··:··:··]OS | WINDOWS 11 24H2
[··:··:··]CPU | RYZEN 9 7950X · 32 THREADS @ 4.5 GHZ
[··:··:··]MEMORY | 47.8 GIB / 62.7 GIB (76%)
[··:··:··]REPORT COMPLETE — 0 WARNINGS
CommandTable — the command reference
CommandDescription
tr300Run the full machine report
tr300 --jsonStructured output for monitoring pipelines
tr300 --fastSkip the slow collectors
tr300 --updateSelf-update to the latest version

Run tr300 --help for the full reference.

CapabilityRows — numbered feature rows
  1. Server HTML is the report

    Every line is in the static HTML before any JavaScript runs — crawlers, no-JS readers, and reduced-motion users see the complete output instantly.

  2. The print is theater, not data

    The boot-print reveal is a client-side curtain over content that already exists. It can never change what the terminal says — only when each line becomes visible.

  3. Real clocks only

    Timestamps are stamped at reveal time from the actual system clock, the same honesty contract as the boot screen. Never hard-code fake times.

RULE / 01

Final state first

TerminalFrame server-renders every line. The boot-print hides and streams them client-side after first scroll-into-view; reduced motion shows everything immediately. Never feed it content that only exists client-side.
RULE / 02

Mono, uppercase, honest

Terminal content is IBM Plex Mono. Display text is stored sentence-case and uppercased by CSS. Timestamps come from new Date() at reveal — the terminal never lies about time.
RULE / 03

Accent lines are landmarks

accent: true paints a line in the arrival blue. Use it for completions and verdicts — one or two per frame, never decoration.
RULE / 04

Tables are tables

CommandTable renders real table semantics — header cells, scoped columns. Screen readers get a navigable grid, not styled divs.
RULE / 05

One owner per line

TerminalFrame owns its lines’ visibility (inline styles + timers). Never point anime, Framer Motion, decode, or the slot roll at nodes inside a printing frame.
RULE / 06

Print once

The boot-print fires on the FIRST in-view only (useInViewOnce). Re-printing on every scroll pass reads as a glitch — the report already happened.
Recipe — a product page's sample output
import { TerminalFrame } from '@/components/terminal'

<TerminalFrame
  title="TR-300 // Sample output"
  meta="NODE_ID: QBTX-01"
  timestamps
  lines={[
    { text: 'tr300 --json', prompt: true },
    { text: 'COLLECTING 14 DIAGNOSTIC CHANNELS...' },
    { text: 'REPORT COMPLETE — 0 WARNINGS', accent: true },
  ]}
/>
14 // Install & download

Install & download

Every QubeTX product ends the same way: an initialize section and a download. These are the canonical layouts — the copy button is the slot roll's home game.

InstallBlock — OS tabs, prompt, copy → Copied (the canonical slot-roll flash)
Initialize
curl -LsSf https://reports.qubetx.com/install.sh | sh
DownloadCard — one artifact, one action (this one is real)
v3.2.2 · zip · tokens + fonts + source + agent docs

Design-system kit

The same artifact as the sidebar button: everything a new QubeTX project copies on day one.

Download
RULE / 01

Copy is a slot roll

The copy button's label flashes Copy → Copied via useSlotRoll().flash() and auto-reverts. This is the standard copy confirmation on every QubeTX surface — never a toast, never an alert.
RULE / 02

Commands are real

Whatever the install block shows must work verbatim in a fresh shell today. Test the command before shipping the page; nothing erodes a tool site faster than a broken curl line.
RULE / 03

One action per card

A DownloadCard carries exactly one artifact and one button. Multiple downloads = multiple cards, stacked — never a dropdown of formats.
RULE / 04

Tabs are tabs

OS pills use real tablist/tab/tabpanel semantics with an FM layoutId fill gliding between them. Keyboard users switch targets without a pointer.
RULE / 05

Reserve the flash width

The copy label reserves its widest flash ("Copied") in CSS — the roll never resizes the button, so the revert (1.4s later, outside the CLS input window) shifts nothing.
RULE / 06

Failure is honest

If the clipboard is unavailable the label flashes Failed — never a silent no-op, never a fake success.
Recipe — a product page's initialize section
import { InstallBlock, DownloadCard } from '@/components/terminal'

<InstallBlock
  title="Initialize"
  targets={[
    { id: 'macos', label: 'macOS', command: 'curl -LsSf https://…/install.sh | sh' },
    { id: 'windows', label: 'Windows', command: 'iwr https://…/install.ps1 | iex' },
  ]}
/>

<DownloadCard
  name="TR-300 binary"
  meta="v2.4 · zip · windows + macos + linux"
  href="/downloads/tr300.zip"
/>
15 // Site chrome

Site chrome

The frame around every page: a fixed three-zone header with scroll-spy, a disclosure dropdown, a portaled mobile overlay, and a footer that ends with a heartbeat. The header and menu are live one click away — the home page is their specimen.

Header anatomy — live on the home page (open qubetx.com and scroll)
MechanismBehavior
useScrolled(24)Past 24px → data-scrolled: backdrop blur, hairline, height compresses to 60px
useActiveSection(ids)IO scroll-spy (40% focal band) feeding the FM layoutId="nav-active" underline
[ bracket ] hoversPseudo-element brackets fade/slide in 150ms — CSS only, with :focus-visible twins
NavDropdownDisclosure (not ARIA menu): Enter/ArrowDown open, Escape closes + refocuses, outside-click closes, FM clip-path + stagger
MobileMenu (<1024px)Full-screen overlay PORTALED to document.body (a transformed/filtered ancestor would shrink inset:0 — gotcha #14), focus trap, Lenis stop/start
data-load="header"Entrance belongs to LoadSequence (anime timeline) — never FM on those nodes
SysStatus — the footer heartbeat, live (cycles every 7s while visible)
RULE / 01

Fixed chrome is the only blur

The scrolled header’s backdrop-filter is the single allowed backdrop-filter on the site (the cursor never blurs, panels never blur). Budget: one.
RULE / 02

Overlays portal to body

Any fullscreen position:fixed overlay must createPortal to document.body — LoadSequence leaves identity transforms on [data-load] ancestors, and a transformed ancestor becomes the containing block (this silently collapsed the mobile menu once; never again).
RULE / 03

Anchor nav goes through Lenis

Every in-page link calls useAnchorNav() (lenis.scrollTo, −88px offset). CSS scroll-behavior: smooth is intentionally absent — one scroll driver.
RULE / 04

The footer ends with state

Nav columns (letter-roll labels) → stroked wordmark (per-char rise) → bottom bar: copyright · SYS_STATUS heartbeat · the Konami hint. Decorative status is aria-hidden and pauses offscreen/hidden-tab/reduced.
RULE / 05

Skip link first

The first focusable element on every page is the “Skip to content” link targeting #main-content.
Recipe — scroll-spy nav underline
const active = useActiveSection(SECTION_IDS)

{NAV_ITEMS.map((item) => (
  <a key={item.href} href={item.href} data-active={active === item.id || undefined}>
    {item.label}
    {active === item.id && (
      <motion.span layoutId="nav-active" className={styles.underline} />
    )}
  </a>
))}
16 // The doctrine

The motion doctrine

Five animation owners, one law: one owner per element property. Everything else in this group is built on that sentence plus a short list of bans that were each earned the hard way.

Ownership — who animates what
OwnerTerritory
anime.js v4Dot-field values (breathe/entrance), ScrollTrace timeline, text reveals/decode/typewriter/letter rolls, logo redraw — imported ONLY via src/lib/motion/anime.ts (the test-mock seam)
Framer MotionAnimatePresence exits, layoutId indicators, whileInView card entrances, whileTap squash, useScroll MotionValues
raw rAFCursor engine, useMagnetic, ProjectCard tilt — transform-only writes, settle-cancelled loops
slotText enginePer-character label changes — owns its cells’ inline transitions entirely (zero deps)
wave objectsDot-field ripples (makeRippleWave/applyRippleWaves) — pure math evaluated per frame by the blitter
CSSEvery simple hover: brackets, underlines, gradient sweeps, blinks, scanlines
Routing — reaching for the right tool
NeedReach for
Hover / press springFramer Motion (whileTap/whileHover + SPRING presets)
Element enters viewport onceuseInViewOnce (IO) + FM variants or anime — never scroll math
Scroll-scrubbed scenePaused anime timeline seek()ed from a Lenis callback (anime onScroll is BANNED)
Multi-step choreography / SVG drawing / staggersanime.js via the seam
Cursor-distance responseuseProximityGlow / cursorEngine — already built, don’t rebuild
Short text that changedNeither — the slot roll. Always. (§17)
Paragraph reveal / heading riseRevealText (§18)
Layout/size reaction to resizeresizeCoordinator subscription — ResizeObserver is banned codebase-wide
LAW / 01

One owner per property

An element with FM variants is never also an anime target — anime targets live one level down (split spans, SVG paths). Two writers on one property = fighting animations.
LAW / 02

No ResizeObserver. Anywhere.

It oscillates with Pretext shrinkwrap. All resize reactions subscribe to src/lib/pretext/resizeCoordinator (one rAF-coalesced window listener).
LAW / 03

Triggers are IO, scrubbing is Lenis

Scroll triggers = IntersectionObserver (useInViewOnce); scroll scrubbing = Lenis callbacks seeking paused timelines. anime's onScroll is banned.
LAW / 04

Server HTML shows final state

Visible text first, always. Hidden/initial states are applied client-side after mount, guarded against FOUC by the html[data-loading] inline script (3s failsafe).
LAW / 05

Reduced motion = skip to final

Every primitive consults useMotionPreference() / prefersReducedMotion() and renders the end state. Never a slower version, never an opt-out.
LAW / 06

anime through the seam

animejs is imported ONLY via src/lib/motion/anime.ts — the single mock point that makes the whole motion system testable. v4.4 removed string cubic-beziers; use EASE_ANIME.
LAW / 07

No per-event tween bursts

Creating hundreds of anime tweens inside a pointer handler costs 8–36ms a hit (measured). High-frequency interactions get wave objects / pure math evaluated per frame (§19).
LAW / 08

CSS beats inline; inline display beats media queries

Never animate a property an engine writes inline on the same node; wrappers set display via class so media queries can override.
17 // Slot roll

The slot roll

How a tiny label changes. Any short text that changes in place — a button caught mid-action, a counter, a status word, a period picker — rolls per character: the new glyph slides in as the old slides out, arriving in blue and settling to ink. It is the system standard, not optional.

Button label — flash & revert (spam them)

label.flash('Copied') rolls there and back on its own. Spam the buttons — flashes coalesce, nothing stutters. The left roll arrives blue; the right is the quiet ink-only variant.

Period picker — direction follows travel
April 2026

→ Forward rolls up, back rolls down — the glyphs move the way the data moves. skipUnchanged keeps "2026" perfectly still.

Applied to a live value — only changed digits move
$2,847,210

→ A live value rolls only the digits that changed — a 7-digit figure moving by hundreds rolls two or three cells, never the whole number.

Interrupt vs queue — a label never shows two values at once
Ready

→ Default interrupt: true fast-forwards a running roll; false lets it finish and replays only the LATEST request. Either way a label never shows two values at once.

The options (SlotTextOptions)
Option · defaultMeaning
direction: 'down'Default. 'up' for forward/advance travel (counters up, next period); 'down' for reverts
stagger: 40ms between adjacent character cells
duration: 240ms per glyph roll
exitOffset: 50ms head start for the outgoing glyph
easing: EASE_SLOT_CSScubic-bezier(0.34, 1.56, 0.64, 1) — the reserved overshoot
bounce: 0.3Per-glyph speed/stagger jitter + settle tilt (0 = mechanical odometer)
color: 'var(--color-arrival)'Arrival tint: string, (i, total) => string, or null for the quiet ink-only roll
colorFade: 280ms for arrival → ink settle
skipUnchanged: trueUnchanged characters hold still — a 7-digit value changing one digit rolls one cell
interrupt: trueA new roll fast-forwards a running one; false queues + coalesces the latest (flash uses this)
RULE / 01

Labels only

A word or two — buttons, counters, status words, pickers. Sentences and headings belong to RevealText; scrambles to decode.
RULE / 02

The destination rule

External links roll on hover to a teaser (hoverLabel); internal links roll on interaction (flashLabel). Mouse only — coarse pointers never hover-roll. (QubeTX original — supersedes the upstream "never on hover".)
RULE / 03

Blue on arrival, ink at rest

The arrival color never persists past ~280ms. Pass color: null for quiet rolls — mandatory on gradient faces where the blue vanishes.
RULE / 04

Direction follows travel

Counting up / advancing = up; reverting / going back = down. The hover teaser enters up and leaves down.
RULE / 05

Reduced motion snaps

The engine consults the house store at animate time and rebuilds the final state instantly. No opt-out, no slower version.
RULE / 06

Meaningful change only

Roll when the VALUE changes — state, count, period. Never on hover-as-decoration, never on page load for static text. (The footer status cycle is the one sanctioned showcase, and it pays its way by pausing offscreen.)
RULE / 07

The engine owns the cells

Never hand-author cell markup, never let React reconcile a rolled container’s children, never point decode/Pretext/anime/FM at the same node.
RULE / 08

Reserve the width

Every rolled label sits in an invisible sizer stack (grid-area 1/1 siblings) holding the widest value — rolls and reverts shift nothing, ever.
The API — engine + React layer
// Imperative (event-driven labels): tuple hook
const [labelRef, label] = useSlotRoll('Copy')
<span ref={labelRef}>Copy</span>
label.flash('Copied')                       // rolls there and back
label.set('May 2026', { direction: 'up' }) // permanent change

// Declarative (state-driven labels): rolls on prop change
<SlotRoll text={status} options={{ direction: 'up' }} />

// Raw engine (non-React surfaces / controllers):
const ctl = attachSlotText(el, '0%', { direction: 'up', color: null })
ctl.set('47%'); ctl.destroy()
18 // Text systems

Text systems

Text is the site's primary material, so its motion is strictly routed: reveals for arrival, decode for verification, the slot roll for change, the letter roll for hover. Nothing else touches glyphs.

RevealText — masked word rise (server HTML is the visible sentence)
Detail is the product.

<RevealText mode="words">: server HTML is the visible sentence; client masks it and rises word-by-word on first view (STAGGER_MS.words). The replay remounts — production reveals fire once.

decode() — scramble-resolve verification
Web Development & Digital Infrastructure

decode(el, 450): glyphs scramble and resolve left → right. Used on eyebrows, section pills, and stat labels — anywhere a label should feel freshly verified.

RollingLabel — the hover letter roll
→ Two stacked copies per character in an overflow mask; hover rolls the stack −100%, staggered 18ms L→R (anime out(4)). Footer links and dense link lists.
Routing — which text system when
SituationSystem
Heading / sentence entersRevealText — masked rise, words (40ms) or chars (18ms), once, IO-triggered
Label feels freshly verifieddecode(el, 450) — glyph scramble resolving L→R (eyebrows, pills, stat labels)
Short text CHANGED in placeThe slot roll — always (§17)
Link label under hoverRollingLabel — stacked-copy letter roll (link lists; use sparingly, one column never two registers)
Terminal output appearsTerminalFrame boot-print (§13) — line stream, never per-char typing of prose
Paragraph wraps/reflowsNot a motion job — PretextBlock reserves the space (§23); blocks animate as wholes
RULE / 01

Server HTML is the sentence

RevealText splits server-side with the text visible; hiding happens client-side after mount. aria-label carries the unsplit text; split spans are presentation.
RULE / 02

Whole blocks under Pretext

Pretext-wrapped paragraphs animate as single blocks (opacity/transform). Per-char/word splitting is reserved for headings the page owns directly.
RULE / 03

decode never shares a node

decode() rewrites textContent — pointing it at a node the slot roll or RevealText owns destroys their DOM. One engine per node, period.
RULE / 04

Reveals fire once

useInViewOnce semantics: a reveal that replays on every scroll pass is noise. (The replay buttons here remount — a documentation affordance, not a pattern.)
RULE / 05

Under the 400ms ceiling

decode 450ms is the documented upper bound for label-scale text effects; reveals scale with STAGGER_MS but individual word rises stay at MS.base.
Recipe — the three calls
// Arrival (once, on view):
<RevealText text="Detail is the product." as="h2" mode="words" />

// Verification (imperative, repeatable):
import { decode } from '@/lib/motion/decode'
if (!reduced) decode(labelEl, 450)

// Hover roll (inside any link):
<a href="/work"><RollingLabel text="Selected work" /></a>
19 // Dot field

The dot field

The hero's reactive dot matrix — anime.js breathing, pure-math ripples, canvas blitting. This one is live: move your pointer across it.

DotGrid — the production component, container-sized (pointer = local swell, button = field-wide pulse)

→ Move the pointer across the field: each move spawns a wave object (~0.2ms) the blitter evaluates per frame. The button broadcasts on the qubetx:pulse CustomEvent bus — radius ∞, the whole field swells.

Architecture — one owner per channel
LayerResponsibility
anime.jsOwns the breathe idle loop and the entrance (scale/alpha) — plain JS dot objects, distance-function delays
wave objectsOwn pulse/mix: one makeRippleWave per pointer event (~0.2ms O(dots) scan), applyRippleWaves evaluates per frame (pure, unit-tested, LUT-sampled envelopes)
canvas 2DThe blitter only — paints radius = baseR × breathe × pulse, color from the LUT ramp. Never computes
geometryTL→BR size/alpha/color ramp; feathered left edge + bottom band; invisible dots CULLED; ≤1400 dots via pitch widening
qubetx:pulse busCustomEvent {x, y, strength} → field-wide ripple (radius ∞). The load beat, easter eggs, and this page’s demo button all broadcast on it
lifecycleIO pauses offscreen; rebuilds via resizeCoordinator (waves cleared — index-aligned); reduced motion = static ramp
MatrixDisplay — the LED word board (5×7 bitmap font, column-staggered sweeps)
RULE / 01

Never per-event tween bursts

Ripples were once ~600 anime tweens per pointer event: 8ms moves, 36ms taps, dropped frames. The wave-object model cut that to 0.2ms with identical visuals. Measured, documented, never going back.
RULE / 02

Channels never fight

breathe (anime) multiplies with pulse (waves) at paint time — the channels compose in the blitter, so the idle loop and ripples coexist without ownership conflicts.
RULE / 03

Delays are distance functions

All choreography delays derive from distance to an origin — radially correct, and safe under culling (no reliance on a complete lattice).
RULE / 04

The bus, not the internals

External features trigger ripples via firePulse() / qubetx:pulse — never by importing DotGrid's internals.
RULE / 05

Budget: ≤2.3ms rippling

Frame budget at ship: ~1ms idle, ≤2.3ms while rippling, 0 offscreen; sweep p95 17.5ms at 2× ripple density. New work on the field must re-verify these numbers.
Recipe — mounting a field + firing the bus
// Container-sized field (position the wrapper; the grid fills it):
<div className={styles.stage}>
  <DotGrid className={styles.field} entrance={false} />
</div>

// Anything can ripple the field through the bus:
import { firePulse } from '@/components/effects/DotGrid'
firePulse({ x: innerWidth / 2, y: innerHeight / 2, strength: 1.6 })
20 // Scroll systems

Scroll systems

One scroll driver (Lenis), one trigger mechanism (IO), one scrubbing pattern (paused timelines seeked from scroll progress). The rail gliding down this page's right edge and the circuit trace descending qubetx.com's left gutter are the same pattern wearing different clothes — scroll storytelling as an optional, per-surface expressive layer.

The systems
SystemRole
Lenis (SmoothScroll)THE scroll driver — window-native smoothing (lerp 0.1, duration 1.5), so IO and FM useScroll just work. Overlays call stop()/start()
useAnchorNav(-88)Every in-page jump: lenis.scrollTo with the header offset; links keep real hrefs for no-JS/a11y; CSS scroll-behavior is intentionally absent
useInViewOnceScroll TRIGGERS — IO, fires once, initial state false (SSR-safe)
ScrollTraceThe home page’s left-gutter circuit: paused timeline of svg.createDrawable segments seek()ed from Lenis progress (draws down, reverses up); hidden <1024px. Ships in the kit as an OPTIONAL expressive decoration
ScrollProgress2px gradient bar — FM scaleX MotionValue, zero re-renders
SectionRail (this page)The right-edge rail here: same drawable+seek model, a cube tick per section, a slot-rolled SEC/% readout — the documentation page’s own expressive layer
Engine roles — who owns what when the page scrolls
EngineOwns
LenisThe single source of scroll truth. Its callback delivers smoothed progress every frame — both other engines read from it, neither listens to raw scroll
anime.js v4Drawable strokes + paused timelines (createTimeline → svg.createDrawable → seek). Owns everything that DRAWS with scroll: the trace’s segments, the rail, the junction cubes
Framer MotionuseScroll MotionValues piped straight into style — ScrollProgress’s scaleX, the footer ring’s pathLength. Owns simple continuous bindings, never drawables
IntersectionObserverEntry TRIGGERS only (useInViewOnce) — fire-once choreography starts. Never used for position math
The trace, live — a miniature ScrollTrace scrubbed by its own transit through your viewport (scroll up to watch it reverse)
RULE / 01

Triggers ≠ scrubbing

Entering the viewport once = IO trigger. Position-linked drawing = Lenis callback seeking a paused timeline. The two never mix, and anime’s onScroll is banned outright.
RULE / 02

Optional expressive decoration

The trace is never a mandatory layer. Default surfaces stay calm — ScrollTrace and this page’s rail are deliberate expressive choices on showcase surfaces. Working tools don’t scroll-animate.
RULE / 03

Bidirectional or broken

A scrubbed timeline must reverse cleanly when the user scrolls up — seek(progress × duration), never play()/pause() choreography.
RULE / 04

Native fallback always

Every Lenis-dependent behavior has a native path (scrollIntoView, window scroll listener) — the page works if Lenis never loads.
RULE / 05

One owner per stroke

An anime drawable is never also an FM target. FM gets the simple continuous bindings (scaleX, pathLength); anime gets anything that draws a path. Lenis feeds both; neither touches raw scroll events.
RULE / 06

Re-measure through the coordinator

The trace measures section offsets on mount, on resizeCoordinator resize, and after fonts settle — never with a ResizeObserver. Layout shifts without a resize won’t re-route it; design within that.
buildTrace() — the pure geometry (scrollTracePath.ts, unit-tested)
OptionMeaning
junctions: number[]Document-Y offsets where isometric cube wireframes (the logo motif) sit — ScrollTrace derives them from section tops via its sectionIds prop
startY / endYThe trace’s vertical span. The site starts at 75% of the first viewport and stops 160px above the document end
gutterXThe lane’s horizontal center. Wide viewports: the true outer margin ((vw − --container-max)/2 − 40). Tight viewports: a hairline lane at x≈11
amplitude45° jog distance each side of the lane — 14px in the wide gutter, 5px in the hairline lane
cubeRJunction cube circumradius (9px wide / 5px tight). cubePaths() returns the hexagon outline + three internal edges as plain d-strings
Recipe — the scrub pattern (ScrollTrace's core)
// 1 · A paused timeline; segments are svg.createDrawable strokes
const tl = createTimeline({ autoplay: false })
segments.forEach((seg, i) => tl.add(seg, { draw: '0 1', duration: 100 }, i * 80))

// 2 · Lenis scrubs it — no scroll math anywhere else
useLenis(({ progress }) => {
  tl.seek(progress * tl.duration)
})
Recipe — wiring the trace on a new surface
// Geometry is pure — design your own path, keep the motif
const geo = buildTrace({
  junctions: offsets,       // your section tops (or any beats you choose)
  startY: vh * 0.75,        // begin below the hero
  endY: docHeight - 160,    // stop short of the footer
  gutterX,                  // your lane — left gutter, right edge, anywhere
  amplitude: 14,            // jog personality
  cubeR: 9,                 // junction cube size
})

// Or take the component wholesale and just point it at your sections
<ScrollTrace sectionIds={['intro', 'specs', 'pricing']} />

// Strokes: brand gradient (#0066FF → #7c3aed), glow = a second wider
// stroke at 0.06 opacity — never an SVG filter
21 // Cursor & pointer

Cursor & pointer

On fine pointers the native cursor disappears and a three-layer instrument takes over: a dot that leads, a ring that trails with physics, a bloom that breathes. The sandbox below uses the page's own cursor — it's already running.

The modes — move through the zones (fine pointers; touch sees static panels)
Default

The dot leads, the ring trails with dt-normalized lerp; velocity squashes the ring along its travel axis.

Interactive

Ring scales up over anything marked interactive.

Magnetic dock

The ring DOCKS to the element's center — the cursor and the magnetic pull meet halfway.

RULE / 01

One rAF, transform-only

cursorEngine runs a single rAF writing transforms exclusively — no filters, no layout properties, will-change on exactly its three layers (the site-wide budget).
RULE / 02

dt-normalized lerp

Smoothing uses 1−(1−k)^(dt·60) so the feel is identical at 60, 120, and 144Hz. Raw lerp-per-frame reads slower on faster monitors — always normalize.
RULE / 03

Settle-cancelled loop

When every layer is within epsilon of its target the loop CANCELS — idle cost is zero. Any pointer movement restarts it.
RULE / 04

Modes are data attributes

data-interactive scales the ring; data-magnetic docks it to the element's center (pairing with the Magnetic wrapper's ≤6px pull). CSS owns each mode's look via data-mode.
RULE / 05

Native cursor on coarse pointers

The custom cursor exists only under @media (pointer: fine) without reduced motion. Touch users never see (or pay for) it.
Recipe — opting elements into cursor modes
// Any interactive element:
<button data-interactive="true">…</button>

// CTA that the ring should dock to (pair with the Magnetic wrapper):
<Magnetic strength={6}>
  <OutlineButton href="…" magnetic>Get Started</OutlineButton>
</Magnetic>

// The engine is pure + unit-tested: tick(dt) → assert transform strings.
22 // Boot & load

Boot & load

First visits boot. The SYSTEM_INITIALIZER overlay covers hydration and font-load jank behind a terminal sequence whose completion is real readiness, not a timer — then hands off to the entrance mid-fade. The frame below documents the lifecycle in its own language.

The lifecycle — boot-printed (this frame uses the boot screen's own render language)
BOOT // Lifecycle contractBOOT_SCREEN.md
PRE-PAINT html[data-boot] armed by the layout inline script
home route only · first session visit only · never under reduced motion
PAINT overlay is server HTML — cursor blink, dot pulse, first log line are pure CSS
HYDRATION log stream + slot-roll odometer begin; the 5s minimum clock starts HERE
READY max(5s, fonts.ready, window load) → double-rAF painted-frame confirmation
progress parks at 99% + AWAITING RENDERER SYNC if readiness outlasts the hold
HANDOFF sessionStorage flag set · data-boot lifted · qubetx:boot-complete fires
LoadSequence plays the hero entrance BEHIND the 700ms fade
FAILSAFES no-JS never arms · inline script force-lifts after 10s
LoadSequence — the entrance order (one anime timeline owns it all)
StepWhat plays
1 · headery/opacity rise, stagger 60ms (data-load="header")
2 · eyebrowPill rises + label scramble-decodes
3 · headlineMasked line rises, stagger 90ms
4 · gradientLine-3 background-position sweep
5 · description → CTAs → companyStaggered rises (data-load groups)
6 · the beatqubetx:pulse fired bottom-right — the dot field answers
RULE / 01

Completion is readiness

The boot ends at max(minimum hold, fonts loaded, window load) plus a double-rAF painted-frame check. Slow networks extend it automatically; the progress bar parks at 99% and says why.
RULE / 02

Armed pre-paint, or not at all

The inline script sets html[data-boot] BEFORE first paint (home route, first session visit, motion allowed). CSS hides the overlay without the attribute — there is no JS race, and no-JS visitors are never trapped.
RULE / 03

The overlay is server HTML

The jank it masks happens before hydration, so the overlay must paint before hydration: server-rendered, with its idle motion (blink, pulse, sweep) in pure CSS.
RULE / 04

Entrance plays behind the fade

qubetx:boot-complete fires BEFORE the 700ms fade so LoadSequence runs while the overlay dissolves — a handoff, not a hard cut.
RULE / 05

FOUC guard is separate

html[data-loading] hides [data-load] entrance targets on every route (3s failsafe); the boot arming is its own attribute with its own 10s failsafe. Two guards, two jobs.
Recipe — the pre-paint guards (layout inline script)
// app/layout.tsx — inline <script>, before anything paints:
// 1 · FOUC guard (every route): hide entrance targets, 3s failsafe
d.setAttribute('data-loading', '')
// 2 · Boot arming (HOME ONLY, first session visit, motion allowed):
if (location.pathname === '/' &&
    !sessionStorage.getItem('qubetx:booted') &&
    !matchMedia('(prefers-reduced-motion: reduce)').matches) {
  d.setAttribute('data-boot', '')
}
// failsafes: data-loading −3s · data-boot −10s
23 // Pretext

Text that knows its shape

Text that knows its shape before the DOM does: canvas measureText as ground truth, then pure arithmetic. Heights are reserved so async copy never shifts layout; left-aligned paragraphs shrinkwrap so no orphan word wraps alone.

Live — drag the width; measured, then shrinkwrapped

Pretext measures this paragraph with canvas measureText before the DOM lays it out — so the height is reserved ahead of time and the last line never wraps a single orphan word alone.

LAW / 01

No ResizeObserver. Ever.

It oscillates with shrinkwrap (measure → narrow → re-measure → loop). All reflows go through resizeCoordinator — sync clientWidth reads coalesced into one rAF per window resize.
LAW / 02

Shrinkwrap left-aligned only

Narrowing a centered block’s max-width pulls it off-center. Centered text gets min-height reservation only.
LAW / 03

Never measure letter-spaced text

Canvas measurement ignores letter-spacing — mono pills, eyebrows, and nav labels are banned from Pretext.
LAW / 04

Computed font names

next/font rewrites family names; PretextProvider resolves COMPUTED names for its readiness check (literal names silently never match — that bug once forced a permanent 3s degradation).
LAW / 05

Whole blocks animate

Pretext-wrapped text moves as a single block (opacity/transform). Per-char splitting belongs to RevealText on headings the page owns.
LAW / 06

Degrade to plain text

Fonts not ready (3s timeout), <1024px for RoutedText, or no JS → the plain paragraph renders. Pretext is enhancement, never a dependency.
Recipe — body text that never shifts
import { PretextBlock } from '@/lib/pretext'

<PretextBlock text={copy} lineHeight={1.65} shrinkwrap as="p" className={styles.body}>
  {copy}
</PretextBlock>
// shrinkwrap ONLY because this paragraph is left-aligned.
// The advanced API (RoutedText): prepareWithSegments + layoutNextLine
// flows the About lead around the logo cube — see the home page live.
24 // Agent playbook

The agent playbook

The page so far is the WHAT; this is the DO. A coding agent starting a new QubeTX project follows these six steps, routes every feature through the table, and ships nothing that fails the checklist. The kit's SKILL.md carries this same playbook offline.

Bootstrap — empty directory to running system
  1. Scaffold + dependencies

    create-next-app (TypeScript, App Router, no Tailwind unless wanted — the system is tokens + CSS Modules), then: npm install animejs framer-motion lenis clsx lucide-react @chenglou/pretext. Static export? Set output: "export".

  2. Copy the kit layers

    From the kit zip: src/lib/motion + src/lib/pretext + src/hooks + src/fonts + src/components/ui + src/components/terminal + src/test. Paste the tokens block from tokens/qubetx-tokens.css into app/globals.css.

  3. Wire the providers

    layout.tsx: font variables on <body> (makira + plexMono), then <PretextProvider><SmoothScroll><CustomCursor />{children}</SmoothScroll></PretextProvider>. Add suppressHydrationWarning to <html> if you use the FOUC guard.

  4. Wire the test harness

    Copy vitest.config.ts conventions + src/test/setup.ts (mocks for pretext, framer-motion, animejs, lenis; IO + matchMedia stubs). Components are tested as final-state DOM with all mocks active.

  5. Pick the register, build the page

    Persuading → landing register (§03). Operating → technical register (§13–14). Compose from the live components; copy from the cheatsheet (§25); route every motion decision through §16–18.

  6. Run the gate. Every commit.

    npm run lint; npm test; npm run build — all three, before every commit, no exceptions. Then verify motion in a real Chrome session: jsdom cannot exercise canvas, anime, or Lenis paths.

The whole setup, as commands
$ npx create-next-app@latest my-qubetx-app --typescript --app --no-tailwind
$ cd my-qubetx-app
$ npm install animejs framer-motion lenis clsx lucide-react @chenglou/pretext
$ npm install -D vitest @vitejs/plugin-react jsdom @testing-library/react @testing-library/jest-dom
# unzip the kit, then copy:
#   src/lib/motion src/lib/pretext src/hooks src/fonts
#   src/components/ui src/components/terminal src/test
#   tokens/qubetx-tokens.css → merge into app/globals.css
$ npm run lint; npm test; npm run build   # the gate — green before you build features
Task routing — building X? Go to §Y
Building…Section / system
A label that changes§17 — useSlotRoll / SlotRoll. Always.
A button or link§09 — OutlineButton / TextLink + the destination rule
A product/tool page§13–14 — TerminalFrame, CommandTable, InstallBlock, DownloadCard
A heading arriving§18 — RevealText (words). Pill labels decode
Body copy that wraps§23 — PretextBlock (min-height; shrinkwrap if left-aligned)
A card grid§11 — pick the species by job; glow lives on the grid
A canvas surface§19 — plain-object animation + pure math + dumb blitter
Scroll behavior§20 — IO for triggers, Lenis for scrubbing, useAnchorNav for jumps
A live status / KPI§12 — StatValue pattern; set() on real change only
First-load theater§22 — opt-in; copy the boot CONTRACT, not just the look
CHECK / 01

Final-state DOM test per surface

Every new component ships with a test asserting its server-rendered text/structure with all mocks active.
CHECK / 02

Reduced-motion path in the same commit

Any motion feature includes its skip-to-final-state path and (where applicable) a test for it.
CHECK / 03

Ownership named in review

PR descriptions for motion work name the owner of every animated property. Two writers = redesign.
CHECK / 04

Real-browser verification

Canvas/anime/Lenis features are verified in actual Chrome (probes, traces) before "done" — jsdom passing is necessary, never sufficient.
CHECK / 05

Lighthouse floors

100 a11y / 100 best-practices / 100 SEO and real-navigation CLS 0.000 are floors, not targets. A regression blocks the merge.
CHECK / 06

Docs move with the code

New tokens, components, or laws update DESIGN_SYSTEM.md (and this page) in the same PR. The system of record never lags the system.
25 // Cheatsheet

Cheatsheet

Every recurring pattern as a paste-ready snippet. Each one assumes the kit layers are in place (§24).

Slot roll — flash / set
const [ref, label] = useSlotRoll('Copy')
<span ref={ref}>Copy</span>
label.flash('Copied')
label.set('May 2026', { direction: 'up' })
Slot roll — declarative
<SlotRoll text={status}
  options={{ direction: 'up' }} />
// rolls whenever `status` changes
Decode a label
import { decode } from '@/lib/motion/decode'
if (!reduced) decode(el, 450)
Reveal a heading
<RevealText text="Detail is the product."
  as="h2" mode="words" />
Pulse the dot field
import { firePulse } from '@/components/effects/DotGrid'
firePulse({ x, y, strength: 1.6 })
Magnetic CTA
<Magnetic strength={6}>
  <OutlineButton href={href} magnetic>
    Get Started
  </OutlineButton>
</Magnetic>
Smooth anchor jump
const navigate = useAnchorNav(-88)
<a href="#services"
  onClick={(e) => { if (navigate('#services')) e.preventDefault() }}>
In-view trigger (once)
const [ref, inView] = useInViewOnce<HTMLDivElement>({ threshold: 0.4 })
useEffect(() => { if (inView && !reduced) play() }, [inView, reduced])
Terminal output
<TerminalFrame title="TR-300 // Output" timestamps
  lines={[{ text: 'tr300 --json', prompt: true },
          { text: 'REPORT COMPLETE', accent: true }]} />
Install + copy
<InstallBlock targets={[{ id: 'macos', label: 'macOS',
  command: 'curl -LsSf https://…/install.sh | sh' }]} />
Body copy (Pretext)
<PretextBlock text={copy} lineHeight={1.65}
  shrinkwrap as="p" className={styles.body}>
  {copy}
</PretextBlock>
The gate
$ npm run lint; npm test; npm run build
# all three green before EVERY commit