# 100xLab · design system handover for agents

> Machine-readable brief. You are an agent about to design, write or build for 100xLab. Read this
> whole file before you touch anything. It is the operating manual for the brand: what 100xLab is,
> how it looks, how it moves, how it speaks, and how to ship changes without breaking it.
> System codename: **Instrument** · design system v2.2 · palette **Clay, saturated**.
> **To reproduce the visuals exactly, follow §15 (build order) and use the exact values in §16 to §27.**

## 0. Ground rules for agents

1. **The code is the source of truth.** When this brief, a doc or a mockup disagrees with the live
   code, the code wins: `site/src/app/globals.css` (tokens), `site/src/lib/palette.ts` (palette, kept
   equal to the CSS by `tests/palette.test.ts`), `site/src/lib/motion/tokens.ts` (motion).
2. **Reuse before you invent.** Every surface is built from existing tokens, classes and components
   (§9). A new colour, radius, shadow, font or easing is a design-system change, not a page change.
3. **When in doubt, choose clearer, quieter, more precise.**
4. **Never fake evidence.** Every number on a page is real and sourced. Unknown → `TBA`.
5. **Ship the way the repo ships** (§14): typecheck, lint, test, build, then push to `main`.

## 1. Brand core

- **What 100xLab is:** the marketing engineering lab for ambitious companies. We build marketing as
  systems that grow revenue: brand, product marketing, growth and analytics, search and content.
  One senior team, no handoffs. Never call it a launch studio, an agency or an "experiment lab".
- **Method:** marketing engineering, i.e. marketers who build. Every playbook we implement was tested
  in our own experiments first; we keep experimenting with new tech and trends (AI agents, AI search)
  before clients rely on it. **Experiments are identity and proof, never a headline:** they live in
  the method, the work log (`EXP-###`, hypothesis, results) and readouts, not in titles or headings.
- **The model:** a five-circle Venn. Verticals: Brand · Product marketing · Growth & analytics ·
  SEO & GEO · Content & social. 100xLab sits at the centre, applying AI agents and automation across
  all five.
- **Big idea:** built to grow 100x. **Hero:** "Built to grow 100x. Engineered as systems."
  **Philosophy:** compounding systems: many small, tested, well-built systems compound into 100x growth.
- **Personality:** the brilliant lab partner. Curious, precise, fast, a little bold, always backed
  by evidence. A marketer who thinks like an engineer.
- **Metaphor:** a digital laboratory. Every screen is an instrument panel, every case study an
  experiment log (`EXP-###`), every number a readout.
- **Feel to hit:** an engineered instrument, not a creative agency mood board. Clients should read
  it as precise, senior, trustworthy, fast.
- **Services in the code ("fronts")**, named exactly as `fronts.ts` names them: Brand · Product
  marketing · Growth & analytics · SEO & GEO · Content & social (ids `brand`, `product-marketing`,
  `growth-analytics`, `seo-geo`, `content-social`). Automation is not a front: it is the centre
  (`labCentre`). Tile colours: ube, tangerine, matcha, blueberry, dragonfruit; the tone classes keep their
  v1 names (`tone-branding` … `tone-video`), picked by tile colour.
  Engagements: Sprint (1 week) · Launch (3 weeks) · Partner (monthly). The code wins (rule 1).
- **Primary CTA everywhere:** `BOOK A CALL` → `/contact`. Booking: `PICK A TIME` → the Cal.com link in
  `site.bookingUrl`.
- **Positioning source:** `100xMessagingBrief.md` (v2.0). Read it before writing any copy.

## 2. Principles (test every change against these)

| Principle | Rule | Test |
|---|---|---|
| Clarity | One primary message per view, readable in five seconds | Cover all but the largest element: does it still make its point? |
| Deference | The work and the numbers lead; chrome recedes | Does lime mark anything that is not an action? Does a tile colour appear off its service? |
| Depth | Layers (paper → oat → white), scale and motion. No shadows, gradients or glass | Explain the hierarchy without mentioning a shadow |
| Precision | 8pt base, 12-column grid, hairlines exposed | Overlay the grid: every edge snaps |
| Honest materials | Show construction: grid lines, serials, specs, process | Is every number real? |
| Evidence over ego | Measurements replace adjectives | "launched in 5 days", never "lightning fast" |
| Purposeful delight | One crafted detail per view, never more | Remove it: is the view still complete? |

## 3. Colour (Clay palette)

Warm oat neutrals do ~90% of the work. One lime colour means "act". Each service owns one saturated,
mid-tone tile with a deep same-hue ink. Colour is information, never decoration.

| Role | CSS var (Tailwind) | Light | Dark |
|---|---|---|---|
| Page | `--paper` (`paper`) | `#FEFDFB` | `#1B1A18` |
| Plain cell ("oat") | `--aluminum` (`aluminum`) | `#F4F3F0` | `#232220` |
| Raised cell, inputs | `--white` (`white`) | `#FFFFFF` | `#2A2926` |
| Hairline ("line") | `--steel` (`steel`) | `#D1CDC7` | `#3A3835` |
| Control border | `--line-strong` | `#7B7974` | `#6E6C68` |
| Secondary text | `--graphite` | `#55534E` | `#A8A49C` |
| Primary text | `--ink` | `#1B1A18` | `#FEFDFB` |
| Inline link, focus ring | `--link`, `--focus` | `#0667D9` | `#429DFF` |
| Action fill (lime) | `--signal` | `#E2FA1C` | same |
| Action hover | `--signal-hover` | `#C4DC00` | same |
| Text on lime | `--on-signal` | `#1B1A18` | same (never flips) |

Service tiles (identical in light and dark; text on a tile is always its partner ink):

| Front | Tile token | Tile | Ink token | Ink | Cell tone class |
|---|---|---|---|---|---|
| Branding | `--ube` | `#A78BFF` | `--ube-ink` | `#160546` | `tone-branding` |
| Launch / GTM | `--tangerine` | `#FF9F66` | `--tangerine-ink` | `#3A1000` | `tone-launch` |
| SEO / GEO | `--blueberry` | `#6FB8FF` | `--blueberry-ink` | `#001433` | `tone-seo-geo` |
| Automation | `--matcha` | `#4FE08A` | `--matcha-ink` | `#032A14` | `tone-automation` |
| Video / Content | `--dragonfruit` | `#FF7ACB` | `--dragonfruit-ink` | `#46022F` | `tone-video` |

Functional (UI states only): success `#1F8A4C` / text `#176B3B`, warning `#B7791F` / `#8A5A12`,
danger `#C62828` / `#B71C1C`, info `#2F5BD3` / `#2B52C0`. Use the `*-text` value for text, the
fill value for graphics only.

Rules:
- **Lime is for fills only** (buttons, the CTA band, one `LIVE` tag) and always carries
  `--on-signal` content. Lime on paper is 1.1:1: never text, never thin graphics.
- **A service colour only ever means that service** (its home tile, its /services section, its
  work tags, its illustration).
- **Trap:** `--ink` flips to near-white in dark mode. On lime use `--on-signal`; on a service tile
  use the tile's ink. Tone classes already re-point `--ink` for you; use `text-ink` inside them.
- Hairlines (`--steel`, 1.6:1) are decoration. Anything that marks an interactive control uses
  `--line-strong` (≥ 3:1).
- Charts: ink, lime, line, in that order; max three series; lime only on the one series that
  matters, always with a label.
- Retired, never reintroduce: the v2.0 pastel tiles (`#C8BBFB` `#FCC9AB` `#BEDFFD` `#BCF4CA` `#F8B9E4`), the v2.0 lime
  (`#EEF773`, `#CBD810`), the v2.0 x accents (`#FF7614`, `#395AFA`), violet `#6248E8`, lavender `#BCACFF`, yellow highlight `#FFEB77`,
  Signal Orange, Lab Black `#0B0B0C`, Signal Lime `#C6FF3D`.

## 4. Typography

| Role | Live font (CSS var) | Use |
|---|---|---|
| Display and UI | Inter Tight 400/500 (`--font-display`, `font-display`) | Headlines, body, UI |
| Technical | JetBrains Mono 400/500 (`--font-mono`) | Labels, data, specs, serials |
| Signal display | Departure Mono (`.segment`) | The wordmark, big counters and codes, sparingly |

Type scale (desktop px, size/line, tracking). Use the classes, never ad-hoc sizes:
`type-mega` 160/144 −4% · `type-display` 96/88 −3% · `type-h1` 64/64 −2.5% · `type-h2` 40/44 −2% ·
`type-h3` 24/32 −1% · `type-lead` 20/32 · body 16/24 · `label` 12/16 +6% mono UPPERCASE ·
`type-caption` 13/16 · `type-data` 14/20 mono · `type-readout` 48/48 · `micro` 11/16 +4% mono.
Mobile scales display sizes by ~0.6 (display never below 32px); body stays 16px.

Rules:
- **Display headlines are sentence case**: a capital to start and after every full stop, proper
  nouns as written. Never all-lowercase, never Title Case. ("Five things we do. One team does them.")
- **Uppercase is only for mono labels:** eyebrows, buttons, tags, nav, the serial rail.
- Tabular figures for every number in tables and readouts (`.tabular`). Units smaller and lighter
  than values.
- One display size per view; hierarchy jumps ≥ 1.5×. Body measure 45–75 characters. No italics in UI.
- Only weights 400 and 500 are loaded. Don't use bold or 600.

## 5. Layout

- **8pt grid** (4 for tight internals). Columns 4 / 8 / 12 at <640 / 640–1023 / ≥1024; gutters
  16 / 24 / 24 / 32; margins 16 / 32 / 48 / 64, then `clamp(64px, 4vw, 128px)` from 1920.
  `--max` is 2400px. Wrap page content in `.container-lab`.
- **Bento** is the page layout: separate cards, gap `--bento-gap` (`clamp(8px, 1vw, 16px)`), 1px
  line, 6px radius (`--radius-cell`), no shadows. `Bento` + `BentoCell span={4|6|8|12}` for grid
  pages; `BentoFlow` / `BentoRow` / `BentoCell grow` for the home rows, where a hovered or focused
  card grows to 1.6× its share over 650ms.
- **Cell tones:** `paper` (default), `oat`, `white`, `ink`, `signal`, and one per service. A tone
  re-points the text tokens inside the cell, so nested `text-ink` is always right.
- Page architecture: header rail (logo · breadcrumb path · nav · CTA, 64px) → title cell
  (`PageTitle`: code `§0X`, eyebrow, sentence-case title, one-sentence lead) → content cells → optional
  lime CTA band (`SignalBand`, max one per page) → footer rail (serial, contact, version stamp).
- Composition: strong asymmetry, content left, visual right; ≥ 25% of any hero is empty; one
  vertical spine (column 1 aligns logo, headline, labels).

## 6. Shape and elevation

- Default radius **0**. Bento cards 6px. Buttons and inputs 2px (`--radius-control`). Device
  frames 32px. Circles only for status lights, data points and gauges.
- Primary buttons have a **chamfered** 8px corner, drawn on a `::before` fill layer. Never put
  `clip-path` on the focusable element (it clips the focus ring).
- **No shadows, gradients or glass.** Depth = surface step (paper → oat → white), hairline frame,
  motion, or an ink scrim at 60% for overlays.
- Lines: hairline 1px `--steel` (structure), rule 2px ink (emphasis), leader 1px ink with a 4px dot.

## 7. Logo

- **The brand colour lives in the x.** The wordmark is ink (paper on dark); the x takes one of
  three accents: tangerine `#FF6A00`, ube `#6D4CD6`, blueberry `#2F4FF5` (`X_ACCENTS` /
  `X_ACCENT_CYCLE` in `lib/palette.ts`). In motion it steps tangerine → ube → blueberry, one per
  animation. The x is never lime. **Backgrounds stay neutral** (paper, oat, white, ink): never put
  the logo on a lime or service-tile field, and never colour the letters other than the x.
- **Wordmark (text):** `100xLab` set in Departure Mono (`LogoLockup`, `components/layout/Logo.tsx`),
  tracking −0.02em, real text (`translate="no"`), header size 20px, all ink.
- **Pixel wordmark:** `100xlab` on a 5×7 dot-matrix (97 lit cells, `lib/motion/wordmark-grid.ts`),
  animated by `WordmarkMotion` (props `shape` tile | dot | px, `sequence`, `hold`, `xAccents`) on the
  home pixel screen and the footer. Samples: Resolve, Gravity, Ripple, Scan, Tiles.
- Downloads (generated from the same glyph table, never stale), all under `/brand/assets/`, all
  transparent: `100xlab-wordmark-{pixel|dot}-{ink|paper}.svg`; coloured x
  `100xlab-wordmark-{pixel-ink|pixel-paper|dot-ink}-x-{tangerine|ube|blueberry}.svg`; animated (CSS,
  loops, honours reduced motion) `100xlab-wordmark-x-cycle-{ink|paper}.svg`,
  `100xlab-wordmark-ripple-{ink|paper}.svg`, `100xlab-wordmark-scan-dot-ink.svg`; palette
  tokens `100xlab-palette.json`.
- Clear space: 2× the x-height on every side. Minimum width 80px.
- Never: recolour outside the palette, add effects, rotate, outline, stretch, or set on busy imagery or a coloured field.

## 8. Motion

Motion explains structure and confirms precision. Tokens (CSS vars, and `DUR` / `EASE` in
`lib/motion/tokens.ts`):

| Token | Value | Use |
|---|---|---|
| instant | 100ms | Hover, toggles |
| quick | 200ms | Buttons, tags |
| base | 320ms | Cells, panels |
| slow | 600ms | Page transitions, headline reveals |
| `ease.standard` | `cubic-bezier(0.2, 0, 0, 1)` | Most movement |
| `ease.mechanical` | `cubic-bezier(0.65, 0, 0.35, 1)` | Slides, masks (machined) |
| `ease.snap` | `cubic-bezier(0.3, 1.4, 0.5, 1)` | Small confirmations only |

Rules:
- Animate **opacity and transform only.** Never layout properties (the home bento grow is the one
  sanctioned exception).
- **No animation library.** Use the platform: Web Animations (`element.animate`) and
  IntersectionObserver (`onView` in `lib/motion/anim.ts`). GSAP was removed on purpose; do not add it back.
- **Nothing runs a per-frame loop at rest.** Canvases draw only in view and only while something
  changes; Lenis smooth scroll ticks only while scrolling. Measure idle main-thread work before and
  after any motion change.
- Entrances wait for `lab:ready` (`whenLabReady`) so nothing plays under the preloader.
- Every motion respects `prefers-reduced-motion` with a still, complete final state.
- Use the existing primitives from `@/components/motion`: `Reveal`, `SplitReveal` (sentence-case
  headlines, masked lines), `CountUp` (readouts tick up ≤ 900ms), `Magnetic` (primary hero buttons),
  `Marquee`, `BrandReel`, `WordmarkMotion`, `DitherArt`, `PageTransition` (ink shutter),
  `Preloader` (first visit only). Signature behaviours: count-up, the x sting, reel, marquee.

## 9. Components and classes

- UI (`@/components/ui`): `Button` / `CtaButton` (variants primary, secondary, link, icon; sizes sm, md, lg; `magnetic`),
  `Tag`, `TabTag`, `Chip`, `Label`, `Micro`, `SerialRail`, `Readout`, `ReadoutRow`, `Section`,
  `SectionHead`, `PageTitle`, `SpecTable`, `StatusLight`, `Accordion`, `Field` + `Input` /
  `Textarea` / `Select` / `ChoiceGroup`, `SignalBand`, `ThemeToggle`, `JsonLd`.
- Bento (`@/components/bento`): `Bento`, `BentoFlow`, `BentoRow`, `BentoCell`, `FrontCell`,
  `FrontVisual`, `PriceTag`.
- CSS classes: `container-lab`, `type-*`, `label`, `micro`, `segment`, `tabular`, `btn`,
  `btn-primary`, `btn-secondary`, `btn-link`, `tag`, `tag-active`, `tag-signal`, `chip`, `input`,
  `status-light`, `hairline`, `prose-lab`, `stretched` (whole-card link), `tone-*`, `signal-surface`.
- Buttons: primary = lime fill, on-signal mono uppercase label, chamfered corner, **one per view**;
  secondary = 1px ink outline; link = text + `→`. Visual heights 32/40/48, hit area always ≥ 44px.
  Labels are verbs, 1–3 words (`BOOK A CALL`, `VIEW EXPERIMENT`).
- Tags: mono 11px uppercase, hairline frame, square. Interactive tags use `line-strong`, `aria-pressed`.
- Inputs: 48px, `line-strong` border, visible label above, placeholder is an example
  (`name@company.com`), errors in `type-caption` + `danger-text` with an `ERROR` prefix.
- Readouts: value is the largest element, label above, context below; only one lime readout per row.
- Arrows: `↗` external/explore, `→` next. Icons 24px grid, 1.5px stroke, outline only, always labelled.

## 10. Illustration

- The only illustration language is **dithered scenes**: live-rendered objects printed as an 8×8
  ordered (Bayer) dither, every dot either the card's ink or transparent, so the tile colour shows
  through (`DitherArt`, WebGL2 shaders in `lib/motion/dither/`: `gl.ts`, `glsl.ts`, `scenes.ts`,
  `mascot.ts`). Dots are 1 CSS px snapped to whole device pixels.
- One scene per service, depicting what the client actually gets: Branding = the puffy six-lobed
  mascot with a glow inside; Launch / GTM = a rocket on its pad; SEO / GEO = a search bar over the
  Google, ChatGPT and Claude tiles; Automation = a robot at a desk filing paperwork; Video / Content
  = a retro TV playing the launch video.
- Behaviour: **still at rest, plays on card hover or focus**, eases to a stop on leave; on touch
  screens plays once on entering view. The mascot is the exception: it follows the cursor across
  the page and changes expression (calm, curious, happy on hover, dizzy on fast moves, excited on
  press, wondering then sleepy when idle).
- New illustrations follow the same recipe: one subject that shows the deliverable, rendered in
  the card's ink only, dithered, motion only on interaction.
- Avoid: stock photos of people at laptops, 3D blobs, neon gradients, crypto coins, generic AI
  brains, line art, icons as illustration, emoji.
- Client work is shown in a `MediaSlot` (oat cell) with real screenshots, never mockup fiction.

## 11. Voice

- Sharp, calm builder. Confident because of the work, not the adjectives. Direct, technical but
  plain, never hypey, founder to founder. Readers often skim on a phone in their second language:
  short sentences, one idea each, plain words.
- Headlines in sentence case, calm: "Which system is holding your growth back?" Mono labels
  describe, never sell: `FUNDS RAISED`, not `AMAZING RESULTS`.
- Talk in systems: what the system does and what it measured. "Tested playbooks, not guesswork."
- Numbers replace adjectives. "100x" is the brand, never a promised outcome.
- "Experiment" belongs to the method and the proof, never to a headline or the category name.
- Terms: marketing engineering lab (not launch studio, agency, growth agency); systems (not
  solutions); RevOps only in body copy, defined on first use; GEO defined on first use.
- Banned: exclamation marks, "revolutionary", "seamless", "unlock", "leverage", "synergy",
  "solutions", "growth hacking", "passionate", price predictions, investment advice, any revenue
  result a 30-day check has not measured.
- British English (optimise, colour). "We" for the lab, "Deb" for founder notes. Errors say what
  happened and what to do next.
- Copy lives in `site/src/content/*.ts`, not in components.

## 12. Accessibility (WCAG 2.2 AA, non-negotiable)

- Text 4.5:1, large text and UI boundaries 3:1. Measure any new colour pair before shipping it.
- Focus always visible: 2px `--focus` outline, 2px offset; on lime and tiles the ring is that
  surface's ink.
- Touch targets ≥ 44×44 (extend with padding or a transparent pseudo-element).
- Never encode meaning in colour alone. Mono labels ≥ 11px, body ≥ 16px.
- Decorative canvases are `aria-hidden` inside a `role="img"` wrapper with a real label.
- Reduced motion: final state, no loops, no preloader, no split, 100ms fade on route change.

## 13. Do / don't

| Do | Don't |
|---|---|
| Expose the grid, align everything to it | Float rounded cards with soft shadows |
| One lime element per view | Paint buttons, icons and headlines lime |
| Real numbers as readouts | Invent metrics or vague claims |
| Sentence-case display, uppercase mono labels | All-lowercase or Title Case Headlines |
| Square corners, chamfered primary action | Pill buttons, 16px rounded cards |
| Service colour only for its service | Use tile colours as decoration |
| Dithered scenes that show the deliverable | Stock art, 3D blobs, line icons as art |
| Animate on interaction, then rest | Loop forever or animate for decoration |
| Leave large areas empty | Fill every cell |

## 14. Ship it (repo workflow)

- Repo: the Next.js app lives in `site/` (Next 16 App Router, Cache Components on, Tailwind 4,
  React 19, pnpm). This Next version differs from older training data: read
  `site/node_modules/next/dist/docs/` before using a Next API.
- Pages in `site/src/app/**`, copy in `site/src/content/**`, components in `site/src/components/**`,
  motion engine in `site/src/lib/motion/**`, tokens in `site/src/app/globals.css`.
- Before every commit, from `site/`: `npx tsc --noEmit -p .`, `pnpm lint`, `pnpm test`, `pnpm build`.
  All must pass.
- Verify in a browser, light and dark (`data-theme`), desktop and 390px wide, with and without
  reduced motion. Check the console for errors.
- Deploy: commit and push to `main`. Railway builds `site/` and deploys production automatically.
  Never commit secrets or `.env` files.
- If the link-preview design changes, bump `VERSION` in `site/src/app/opengraph-image.tsx`.
- Changing the palette: edit `globals.css` **and** `lib/palette.ts` together (the test fails otherwise).

## 15. Reproduce the visuals: the kit and the build order

Goal: anyone (or any agent) following this brief should rebuild the site's look pixel for pixel.
This section is the recipe; §16 to §26 are the exact values it points to.

**What you can fetch** (all static, all under `/brand/assets/`, generated from the live code so they never drift):

| File | What it is | Use it for |
|---|---|---|
| `100xlab-tokens.css` | The entire `globals.css`, verbatim | Drop in as the stylesheet. It is the source of truth for tokens, type ramp, buttons, tags, inputs, bento, tones |
| `100xlab-system.json` | Palette, layout, type ramp, motion, wordmark glyph table + geometry + samples, Bayer matrix | Anything that is not CSS: canvases, SVG, decks, OG images, other apps |
| `100xlab-palette.json` | Light, dark, action and service colours | Quick palette import |
| `100xlab-wordmark-*.svg` | Every logo file (transparent) | Placing the logo |

**Fonts** (three, no others): Inter Tight 400/500 and JetBrains Mono 400/500 from Google Fonts
(`display: swap`, Latin subset); Departure Mono 400 from departuremono.com (SIL OFL, `.woff2`), the
site self-hosts it at `site/src/app/fonts/DepartureMono-Regular.woff2`. CSS variables are
`--font-inter-tight`, `--font-jetbrains`, `--font-departure`, bridged to `--font-display`,
`--font-mono`, `--font-segment`.

**Build order** (do not skip ahead; each step is checkable):

1. Load the three fonts and set the variables on `<html>` (§17).
2. Install `100xlab-tokens.css` (or recreate §16 verbatim). Light is `:root`; dark is
   `[data-theme="dark"]` on `<html>`.
3. Set base styles (§17): `html`/`body` paper and ink, 16/24, antialiased, `font-feature-settings: "ss01", "cv11"`.
4. Use the type classes from §18 for every piece of text; never ad-hoc sizes.
5. Build chrome primitives from §19: `.btn` family, `.tag`, `.chip`, `.input`, `.check`, `.status-light`.
6. Lay out with `.container-lab` and the bento system (§20): `.bento` (grid pages) or
   `.bento-flow` / `.bento-row` (home, footer), cells carry a `tone-*` class.
7. Add header and footer rails and the page title (§21), then compose pages from the recipes in §21.
8. Add the logo and the pixel wordmark (§22), then dithered art (§23), then motion (§24).
9. Add theme switching (§25). Then run the acceptance checklist (§27).

**Copy verbatim vs re-implement.** If you have repository access
(private repo `deb-pradhan/100xlab-site`, app in `site/`), copy these files unchanged rather than
re-deriving them: `src/app/globals.css`, `src/lib/palette.ts`, `src/lib/motion/**` (including
`dither/*.ts`, the GLSL scenes and the mascot), `src/components/motion/**`, `src/components/bento/**`,
`src/components/ui/**`, `src/components/layout/**`. Without repository access, use the kit above plus
§22 and §23, which specify the wordmark and dither art completely enough to rebuild them with any
renderer. The one thing you cannot rebuild exactly from a description is the five GLSL scenes; follow
§23 and match them by eye against the live cards on `/` and `/brand`.

## 16. Tokens, verbatim

Hex values are lowercase in CSS. `--aluminum` is "oat", `--steel` is "line" (names kept from v1).
Light is the default; dark overrides only the neutrals, link, focus and functional text.

```css
:root {
  --paper: #fefdfb;  --aluminum: #f4f3f0;  --white: #ffffff;
  --steel: #d1cdc7;  --graphite: #55534e;  --ink: #1b1a18;

  /* action: lime. Text on it is always light-mode ink. */
  --signal: #e2fa1c;  --signal-hover: #c4dc00;
  --on-signal: #1b1a18;  --on-signal-hover: #1b1a18;
  --link: #0667d9;  --line-strong: #7b7974;  --focus: #0667d9;

  /* service tiles: identical in both modes; text on a tile is its -ink partner */
  --ube: #a78bff;         --ube-ink: #160546;
  --tangerine: #ff9f66;   --tangerine-ink: #3a1000;
  --blueberry: #6fb8ff;   --blueberry-ink: #001433;
  --matcha: #4fe08a;      --matcha-ink: #032a14;
  --dragonfruit: #ff7acb; --dragonfruit-ink: #46022f;

  /* functional (fill = graphics only, *-text = text) */
  --success: #1f8a4c;  --warning: #b7791f;  --danger: #c62828;  --info: #2f5bd3;
  --success-text: #176b3b;  --warning-text: #8a5a12;  --danger-text: #b71c1c;  --info-text: #2b52c0;

  /* shape */
  --chamfer: 8px;  --radius-cell: 6px;  --bento-gap: clamp(8px, 1vw, 16px);
  --hit-min: 44px;  --header-h: 64px;   /* 56px below 640 */

  /* motion */
  --motion-instant: 100ms;  --motion-quick: 200ms;  --motion-base: 320ms;  --motion-slow: 600ms;
  --ease-standard: cubic-bezier(0.2, 0, 0, 1);
  --ease-mechanical: cubic-bezier(0.65, 0, 0.35, 1);
  --ease-snap: cubic-bezier(0.3, 1.4, 0.5, 1);

  /* layout */
  --margin: 16px;  --gutter: 16px;  --cols: 4;  --max: 2400px;
  color-scheme: light;
}
@media (min-width: 640px)  { :root { --margin: 32px; --gutter: 24px; --cols: 8; } }
@media (min-width: 1024px) { :root { --margin: 48px; --gutter: 24px; --cols: 12; } }
@media (min-width: 1440px) { :root { --margin: 64px; --gutter: 32px; } }
@media (min-width: 1920px) { :root { --margin: clamp(64px, 4vw, 128px); } }

[data-theme="dark"] {
  --paper: #1b1a18;  --aluminum: #232220;  --white: #2a2926;  --steel: #3a3835;
  --graphite: #a8a49c;  --ink: #fefdfb;  --link: #429dff;  --line-strong: #6e6c68;  --focus: #429dff;
  --success-text: #3fbf78;  --warning-text: #e0a93b;  --danger-text: #ff6b6b;  --info-text: #7c9cff;
  color-scheme: dark;
}
@media (prefers-reduced-motion: reduce) {
  :root { --motion-instant: 0ms; --motion-quick: 0ms; --motion-base: 0ms; --motion-slow: 0ms; }
}
```

Contrast you can rely on (measured, WCAG): ink on lime 14.8:1 (hover 11.3:1); every tile ink on its
tile 6.7:1 or better; secondary text on a tile (the tile's ink at 78%) 4.5:1 or better; ink on paper
17.1:1 in both themes; graphite on paper 7.6:1 (oat 6.9:1; dark 7.0:1); link on paper 5.2:1; `--line-strong` on paper 4.3:1. The logo x accents are graphics exempt from text contrast, but keep
them off small text. `tests/palette.test.ts` enforces these; run it after any colour change.

**Tones** re-point the text tokens inside a cell, so nested `text-ink`, `text-graphite`, links and
focus rings are always right. Reproduce exactly:

```css
.tone-oat   { background: var(--aluminum); }
.tone-white { background: var(--white); }
.tone-ink   { /* a dark cell on any theme */
  --paper: #1b1a18; --aluminum: #232220; --white: #2a2926; --steel: #3a3835; --line-strong: #6e6c68;
  --ink: #fefdfb; --graphite: #a8a49c; --focus: #429dff; --link: #429dff;
  background: #1b1a18; color: var(--ink); }
[data-theme="dark"] .tone-ink { --paper: #2a2926; background: #2a2926; } /* lifts one step so it shows on the dark page */
.tone-signal { --ink: #1b1a18; --graphite: #3d3c37; --focus: #1b1a18; --link: #1b1a18;
  background: var(--signal); color: var(--ink); }
.tone-branding   { --ink: var(--ube-ink);         background: var(--ube); }
.tone-launch     { --ink: var(--tangerine-ink);   background: var(--tangerine); }
.tone-seo-geo    { --ink: var(--blueberry-ink);   background: var(--blueberry); }
.tone-automation { --ink: var(--matcha-ink);      background: var(--matcha); }
.tone-video      { --ink: var(--dragonfruit-ink); background: var(--dragonfruit); }
/* lime and tiles: line tinted from their own ink; tiles also re-point graphite, focus, link */
:is(.tone-signal, .tone-branding, .tone-launch, .tone-seo-geo, .tone-automation, .tone-video)
  { --cell-line: color-mix(in srgb, var(--ink) 14%, transparent); }
:is(.tone-branding, .tone-launch, .tone-seo-geo, .tone-automation, .tone-video)
  { --graphite: color-mix(in srgb, var(--ink) 78%, transparent); --focus: var(--ink); --link: var(--ink); }
```

`.surface-invert` (an inverted full-width section) sets the same dark neutrals as `.tone-ink`.
Texture helpers (use sparingly, never on content): `.dot-grid` (radial dot, 16px, `--steel` 70%) and
`.line-grid` (16px, `--steel` 45%).

## 17. Fonts, base styles, theme plumbing

```ts
// next/font, in app/layout.tsx
Inter_Tight({ variable: "--font-inter-tight", subsets: ["latin"], weight: ["400", "500"], display: "swap" })
JetBrains_Mono({ variable: "--font-jetbrains", subsets: ["latin"], weight: ["400", "500"], display: "swap" })
localFont({ variable: "--font-departure", src: [{ path: "./fonts/DepartureMono-Regular.woff2", weight: "400", style: "normal" }], display: "swap" })
```

Tailwind 4 bridge (`@theme inline`): `--font-display` = Inter Tight, then Neue Haas Unica, system-ui;
`--font-mono` = JetBrains Mono, Berkeley Mono, ui-monospace; `--font-segment` = Departure Mono,
ui-monospace. Colour tokens are exposed as utilities (`bg-paper`, `text-ink`, `border-steel`,
`text-graphite`, `bg-signal`, `text-on-signal`, `tone-*`…). Radii: `--radius-control` 2px,
`--radius-cell` 6px, `--radius-device` 32px.

Base layer: `html` and `body` take `--paper` / `--ink`, `font-family: var(--font-display)`, 16/24,
`-webkit-font-smoothing: antialiased`, `font-feature-settings: "ss01", "cv11"`,
`text-rendering: optimizeLegibility` on `html`. `::selection` is `--signal` on `--on-signal`.
`:focus-visible` is `2px solid var(--focus)` with `2px` offset. Links inherit colour, no underline.
`img, svg, video { display: block }`. `body { overflow-x: clip }`, `html { scroll-padding-top: calc(var(--header-h) + 16px) }`.

Theme: `data-theme="light" | "dark"` on `<html>`; a tiny `beforeInteractive` script reads
`localStorage["theme"]` and sets it before first paint (no flash); `ThemeToggle` writes it. The
`<meta name="theme-color">` is `#FEFDFB` (light) and `#1B1A18` (dark).

## 18. Type ramp, exact

Use the classes. Sizes are `px`, written size/line-height. Mobile is below 640, tablet 640 to 1023,
desktop 1024 and up. Display and headings are weight 500, `text-wrap: balance`.

| Class | Mobile | Tablet | Desktop | Tracking | Font |
|---|---|---|---|---|---|
| `type-mega` | 96/88 | 128/116 | 160/144 | −0.04em | Inter Tight 500 |
| `type-display` | 56/56 | 72/68 | 96/88 | −0.03em | Inter Tight 500 |
| `type-h1` | 40/44 | 52/54 | 64/64 | −0.025em | Inter Tight 500 |
| `type-h2` | 32/36 | 36/40 | 40/44 | −0.02em | Inter Tight 500 |
| `type-h3` | 24/32 | 24/32 | 24/32 | −0.01em | Inter Tight 500 |
| `type-lead` | 18/28 | 20/32 | 20/32 | −0.005em | Inter Tight 400 |
| body (default) | 16/24 | 16/24 | 16/24 | 0 | Inter Tight 400 |
| `type-caption` | 13/16 | 13/16 | 13/16 | 0 | Inter Tight 400 |
| `label` | 12/16 | 12/16 | 12/16 | +0.06em | JetBrains Mono 500, UPPERCASE |
| `micro` | 11/16 | 11/16 | 11/16 | +0.04em | JetBrains Mono 400, UPPERCASE |
| `type-data` | 14/20 | 14/20 | 14/20 | 0 | JetBrains Mono 400, tabular |
| `type-readout` | 40/40 | 48/48 | 48/48 | −0.02em | Departure Mono, tabular |
| `segment` | n/a | n/a | n/a | n/a | Departure Mono 400 (wordmark, codes like `§02`, big counters) |

Section code: `<span class="segment text-[15px] leading-4 text-graphite tabular">§02</span>` followed by a
`label text-ink` eyebrow. Headlines are authored in sentence case in `src/content/*.ts`.

## 19. Component recipes (exact)

**Primary button** (one per view): 40px tall (`btn-sm` 32, `btn-lg` 48), padding 0 24px (sm 16, lg 32),
mono 500 12px/1 uppercase, tracking 0.06em, gap 8px, radius 0 on the element. The fill is a `::before`
layer so the focus ring is never clipped: `inset: 0; z-index: -1; background: var(--signal);
clip-path: polygon(0 0, calc(100% - var(--chamfer)) 0, 100% var(--chamfer), 100% 100%, 0 100%);`.
Text `--on-signal`. Hover: fill `--signal-hover`, 200ms `ease-standard`. Active: `translateY(1px)`.
Hit area ≥ 44px via an `::after` that extends it. Disabled: text graphite, fill `--steel`. Inside a
lime cell (`.signal-surface`) the primary inverts to an ink plate (`--on-signal` fill, `#fefdfb` text) so
it never vanishes into the field.

**Secondary:** transparent, `1px solid var(--ink)`, text ink; hover fills ink, text paper.
**Link:** no box, `padding: 4px 0`, gap 6px; the label has a 1px `--signal-hover` underline that grows from
0% to 100% width on hover/focus (200ms); the arrow nudges 3px right (`↗` nudges up-right 2px).
**Icon button:** 40×40, `1px solid var(--line-strong)`, hover border ink and oat fill.

**Tag:** 24px tall, padding 0 8px, mono 400 11/16 uppercase, tracking 0.04em, `1px solid var(--steel)`,
square. `tag-active`: ink fill, paper text. `tag-signal`: lime fill and border, `--on-signal` text (the `LIVE` tag).
**Chip (filter):** 32px tall, padding 0 12px, same type, `1px solid var(--line-strong)`; hover oat;
`aria-pressed="true"` is ink fill, paper text. Hit area extended ±6px vertically.

**Input:** 48px tall, padding 0 16px, 16/24 Inter Tight, `--white` fill, `1px solid var(--line-strong)`,
radius 2px; hover border ink; focus: ink border + 2px focus outline offset 2px; invalid: `--danger` border;
disabled: oat fill, `--steel` border. Placeholder is mono 14px graphite. Textarea min-height 128px.
Select: custom chevron from two 6px gradients. Label sits above, visible always.
**Check/radio row:** min-height 44px, `1px solid var(--line-strong)`, mono 11px uppercase label, 16px box with an
8px ink dot that scales in (100ms `ease-snap`); checked row border turns ink.

**Status light:** 8px circle; `data-state="live"` is lime with a 1.6s pulse ring (scale 1 to 2.6, opacity .6 to 0).
It is the only motion allowed to loop. **Readout:** value (`type-readout` or `segment`) largest, mono `label`
above, context below, tabular figures. **Hairlines:** 1px `--steel`; emphasis rule 2px ink.
**Registration marks** (`.reg`): small crosshairs at hairline intersections, `color: var(--ink)`, positioned at the corners.
**Accordion:** native `<details class="acc">`, the `+` sign rotates 45° over 200ms `ease-mechanical`.

## 20. Layout and bento, exact

- `.container-lab`: `width: 100%; max-width: var(--max); margin-inline: auto; padding-inline: var(--margin)`.
- Page rhythm: content starts below the 64px rail; `PageTitle` first; sections separated by `mt-16` rails
  (`§` code + `label`), cells `pb-16` at the end of the page (`lg:pb-24` on home).
- **`.bento`** (grid pages): 1 column mobile, 2 columns from 640, 12 columns from 1024, `grid-auto-flow: row dense`,
  `gap: var(--bento-gap)`. Spans: `4` (a third), `6` (half), `8` (two thirds), `12` (full); on tablet spans
  6/8/12 go full width and an odd lone span-4 widens to full.
- **`.bento-flow` / `.bento-row`** (home and footer): a column of rows; each row splits by each cell's `--grow`.
  Below 640 rows stack; 640 to 1023 cells wrap two-up (`flex: 1 1 calc(50% - gap)`); from 1024 a row is
  `nowrap` and a cell is `flex: var(--grow) 1 0%` with `transition: flex-grow 650ms var(--ease-standard)`.
  On hover-capable, motion-allowed screens a hovered or focused cell grows to `calc(var(--grow) * 1.6)` and its
  row-mates give way. Rows do not share column lines on purpose, so edges never line up across rows.
- **Cell:** `position: relative`, `1px solid var(--cell-line, var(--steel))`, `border-radius: 6px`, no shadow.
  Padding 16 / 24 / 32 (mobile / tablet / desktop), `flex flex-col`. Minimum height 280 (mobile and tablet) /
  320 (desktop); `tall` is 360 / 480. `flush` removes padding for edge-to-edge media (clip to the radius).
- Cards that are links use `.stretched` (an `::after` covering the card) so the whole card is one tab stop;
  nested secondary links sit above it with `.above-stretch`. The focus ring goes on the `::after`.

## 21. Page anatomy

**Header rail:** `<header class="sticky top-0 z-50 border-b border-steel bg-paper">`; inner `container-lab`,
height 56px (mobile) / 64px (≥640), `flex items-center gap-4 lg:gap-6`. Left to right: logo lockup (Departure
Mono 20px, `100xLab`, ink, tracking −0.02em, hit area 44px) · 1px×20px `--steel` divider · breadcrumb path
(`100XLAB / HOME`, mono micro) · scroll readout (xl only) · nav (mono labels, lg only) · theme toggle ·
primary button `BOOK A CALL` (≥640) · mobile menu below 1024.

**Footer rail:** `bg-paper`, `container-lab pb-6 lg:pb-8`, a `bento-flow` of three rows. (1) A `tone-white` info
cell (name label, descriptor, reply promise in `type-caption`) beside a `tone-white` link grid (2 / 3 / 5 columns,
`micro` group headings, mono label links). (2) A `tone-oat` cell, `height: clamp(180px, 22vw, 380px)`,
overflow hidden, holding the pixel wordmark in tile shape (`fill`, the x cycling the three accents). (3) The legal row: the disclaimer in
`type-caption` graphite beside a `micro` line of the legal line, legal links, the brand-assets link and the cookie-settings button.

**PageTitle:** a 12-column grid. Row 1: section code (`§0X`, segment 15px graphite) and `label` eyebrow. Row 2:
`h1.type-display` in a `col-span-9`, revealed by line (SplitReveal). Row 3: `type-lead` in graphite,
`max-w-[60ch]`, spanning 6 columns.

**Home** (`/`): `h1` is `sr-only`. One `BentoFlow` of five rows between the header and footer:

| Row | Cells (tone · grow) |
|---|---|
| 1 | Logo screen (`oat`, grow 2, tall, flush; `WordmarkMotion shape="tile" fill`, x cycles tangerine → ube → blueberry) · Value proposition (`white`, grow 1, tall: label, `type-h1` title, `type-lead` body, three ruled points, link) |
| 2 | Reel (`ink`, grow 1.5, flush; video or `BrandReel`) · Ask the lab (`signal`, grow 1) |
| 3 | Three `FrontCell`s: Branding, Launch, SEO/GEO (grows 1, 1.2, 1) |
| 4 | Two `FrontCell`s: Automation, Video (grows 1.15, 1) · Pricing (`white`, grow 0.9: label, `type-h3` title, ruled price list) |
| 5 | Work (`paper`, full: label, `type-h1` title in `Reveal`, `Marquee` of clients) |
| 6 | Closing CTA (`signal`, full: label, `type-h1`, `type-lead`, `CtaButton size="lg" magnetic`, link) |

**FrontCell** (`tone-{front}`, `gap-6`): header row (`label` front name, `label` "more →" stretched link), a
min-height 120px dithered illustration, a `type-h3` promise pushed to the bottom (`mt-auto`), then `micro` graphite
deliverables in a wrapping row.

| Front | id / tone | Label | Promise | Illustration |
|---|---|---|---|---|
| Branding | `branding` / ube | BRANDING | look like the company you are about to be. | puffy mascot |
| Launch | `launch` / tangerine | LAUNCH / GTM | live in 3 weeks, with a deck investors finish. | rocket on its pad |
| SEO / GEO | `seo-geo` / blueberry | SEO / GEO | show up when buyers ask Google or ChatGPT. | search bar over AI logos |
| Automation | `automation` / matcha | AUTOMATION | let agents do the busywork, not new hires. | robot at a desk |
| Video | `video` / dragonfruit | VIDEO / CONTENT | a launch video, then content that keeps moving. | retro TV |

**Brand page** (`/brand`): `PageTitle` (`§10`), then six rails each followed by a `Bento`: `01 LOGO` (pixel wordmark
on oat span 8 tall and on ink span 4 tall; text wordmark, dot-matrix, rules cells), `01.1 LOGO IN MOTION`
(live `WordmarkMotion` samples), `02 COLOUR` (six neutral cards with light/dark split swatch, a full-width lime cell
"lime means act.", five tile cells), `03 TYPE` (three specimen cells, the type-scale `SpecTable`), `04 ILLUSTRATION`
(five tiles with live `FrontVisual`), `05 MOTION` (token table), `06 SOCIAL KIT` (profile pictures, banners, covers, each with copy and download buttons) and `07 DOWNLOADS` (cards grouped mark / colour / animated / tokens, each image with copy and download buttons).
Rails: `mt-16 mb-4`, a segment code then a `label`.

## 22. The pixel wordmark, exact

Letters `100xlab` set on a 5-column × 7-row dot-matrix with one empty column between letters (advance 6),
97 lit cells, 41×7 cells overall. Glyph table (`#` lit, `.` off), also in `100xlab-system.json`:

```text
"1": ..#.. .##.. ..#.. ..#.. ..#.. ..#.. .###.
"0": .###. #...# #..## #.#.# ##..# #...# .###.
"x": ..... ..... #...# .#.#. ..#.. .#.#. #...#
"l": .##.. ..#.. ..#.. ..#.. ..#.. ..#.. .###.
"a": ..... ..... .###. ....# .#### #...# .####
"b": #.... #.... ####. #...# #...# #...# ####.
```
(rows top to bottom; the word is `1 0 0 x l a b` = "100xlab", rendered in the UI as the brand `100xLab`).

- **Base grid** 47×13 cells (the wordmark padded 3 cells each side). `makeGrid(w, h)` centres the 41×7 mark on any
  larger grid. The x's centre is the origin of the Ripple sample (distance measured with rows × 1.2).
- **Shapes:** `tile` (home, footer), `dot`, `px`. Tile: every cell is a rounded square of **0.84 of the pitch**,
  radius **0.16 of the pitch**; unlit tiles are `--steel` at **45% opacity**; lit tiles are `--ink`; the **x tiles take
  the accent** (tangerine `#ff6a00`, ube `#6d4cd6`, blueberry `#2f4ff5`, one per animation, in that order).
  Dot: rest dot radius 0.14 of the pitch, lit dot radius 0.42. Pixel (square): rest 0.2, lit half-size 0.43.
- **`fill` mode** (home): the grid fills the container; the wordmark spans 90% of the width (94% under 640px);
  pitch = min(width × span ÷ 41, height × 0.7 ÷ 7); columns are rounded so the side margins are equal (dead-centre).
  Canvas DPR is capped at 2.
- **Rotation:** play, hold (1600ms default; the brand page uses 1400), exit, 250ms of empty grid, next sample.
  Starts after `lab:ready`; runs only while on screen and the tab is visible; at rest and under reduced motion it
  shows the finished wordmark. Every sample ends on the clean wordmark and has an exit that takes it apart.

| Sample | Play | Exit | Behaviour |
|---|---|---|---|
| Resolve | 2600ms | 1300ms | Noise flickers like an instrument warming up, then cells lock onto the letters; exit returns the noise |
| Gravity | 2600ms | 1500ms | Cells drop in by column and bounce into place; exit: columns hop and fall out the bottom |
| Ripple | 2400ms | 1300ms | A pulse leaves the x and paints the letters outward; exit: a second pulse wipes them |
| Scan | 2000ms | 1100ms | A readout line sweeps left to right, the mark stays behind it |
| Tiles | 2600ms | 1300ms | Every cell is a card; the logo cards flip in (scaleY by cosine) |

**SVG export** (`100xlab-wordmark-*.svg`): cell 8 units, one empty column between letters, 8-unit padding, shown at
2×; pixel = `rect`, dot = `circle` r 0.42 cell; transparent. Animated files use CSS only: `.c` scales from 0.2 and
fades over **4.2s `cubic-bezier(0.65,0,0.35,1)` infinite** (delay by distance from the x for Ripple, by column × 0.03s
for Scan); the x cycle steps its fill every **1.6s** with `step-end` (no blends); `prefers-reduced-motion` stops both.

**Favicon** (`app/icon.svg`): 64×64, `rx 13`, `#1B1A18` ground, a tile grid in the same language: lit cells
`#FEFDFB`, the x `#FF6A00`, unlit cells paper at 16% opacity, tiles 7.36 units wide with `rx 1.29` on a 9.2-unit pitch (about 0.8 of the pitch).

**Link preview** (`app/opengraph-image.tsx`, 1200×630, versioned by `VERSION`): ink `#1B1A18` ground, a 49×27 tile
screen (pitch 1200 ÷ 49, tile 0.84, radius 0.16, unlit `#2B2A27`), the wordmark in `#FEFDFB` tiles lifted two rows with
the x in `#FF6A00`, five service tiles (two tiles per colour, the five tile colours in order, one column apart) on a row three rows above the bottom edge, and
a lime `#E2FA1C` plate (radius 12px, Inter Tight 600 38px, tracking −1px, ink text) spanning the wordmark's width with the
one-line descriptor. No pricing. Bump `VERSION` whenever the design changes.

### Social kit: profile pictures, banners, covers

Built in the same tile language, as plain SVG (no fonts, gradients or effects), so a file looks identical
pasted into Figma, Slack, LinkedIn or a deck, and rasterises exactly. Generated by `lib/brand/social.ts` (tile
versions), `lib/brand/art.ts` (dithered versions) and `lib/brand/dither.ts` (the Bayer engine); listed with sizes
in `100xlab-system.json` under `socialKit`; served at `/brand/assets/<file>.svg`.

**Banners and covers are pure graphics: no logo, no tagline, no text, no lime** (lime means "act", and a cover has no
action). Only profile pictures use the monogram. Do not add text, a wordmark or a call to action to these files.

| Group | Files | Size | What it is |
|---|---|---|---|
| Profile | `100xlab-profile-monogram-{ink-tangerine, paper-ube, oat-blueberry, dot-paper-ube}.svg`, `100xlab-profile-x-{ink-tangerine, paper-blueberry}.svg` | 800×800 | A 21×21 tile screen with the stacked "10 / 0x" monogram (11×15 cells, the x in the accent), or the x alone at double scale (18×18). Kept inside a circular crop |
| Banner | `100xlab-banner-linkedin-{ink,paper}.svg`, `100xlab-banner-x-{ink,paper}.svg` | 1584×396, 1500×500 | Tile graphics at pitch 12: ink = the **pixel x**, paper = the **skyline** |
| Cover | `100xlab-cover-16x9-{ink,paper}.svg`, `100xlab-cover-16x9-services-{ink,paper}.svg`, `100xlab-card-1200x630-{ink,paper}.svg` | 1920×1080, 1200×630 | Tile graphics at pitch 24 (cover) / 16 (card): pixel x, skyline, and **five service bars** stepping up in length |
| Graphic | `100xlab-graphic-{horizon,growth,ripple,bloom,bigx,fronts}-{linkedin,cover}.svg` | 1584×396, 1920×1080 | Dithered imagery, not tied to the grid (below) |
| Violet | `100xlab-violet-{linkedin,cover-16x9}-{deep,light}.svg` and `100xlab-graphic-{horizon,growth,ripple,bloom,bigx}-violet-{linkedin,cover}.svg` | 1584×396, 1920×1080 | The Branding violet as a full theme: **deep** = ground `#160546`, tiles and art `#A78BFF`; **light** = ground `#A78BFF`, tiles `#160546`. No new colours: unlit tiles are the same two violets at 14 to 16% opacity |

**Tile graphics** (`social.ts`): the **pixel x** is a giant x of tiles bleeding off the top and bottom (arm half-thickness 17% of the
half-length) in the ground's accent (tangerine on ink, ube on paper, violet or paper on the violet grounds), with a sparse scatter of lit
tiles fading out from its arms; the **skyline** is columns of tiles rising exponentially (1x to 100x), in groups of three, coloured by the
five service colours in order (violet versions alternate accent and ground contrast); the **service bars** are the five service tiles as solid
rounded blocks of length 52%, 64%, 76%, 88% and 100% of 70% of the width.

**Graphic banners and covers** use the dithered-illustration recipe as vector: a density field is thresholded by the 8×8 Bayer matrix (cell
`(i, j)` lit when density > `(bayer[j%8][i%8] + 0.5) / 64`) and emitted as one merged `<path>` per colour layer (square dots 4px on the
banner, 6px on the cover), so the art stays copyable vector with no raster and no gradients. Each concept is a function of a theme (ground,
text colour, art colour), which is how the violet versions exist. Six concepts, each at both sizes:

| Concept | Ground | Subject | Colours |
|---|---|---|---|
| Horizon | ink (or violet deep) | A banded sun rising over a lined ground, stars, halo | tangerine (or violet) dots, paper stars and lines |
| Growth | paper (or violet light) | An exponential curve with a dithered area under it, log ticks and an end marker | ink dots, ube (or paper) end marker |
| Ripple | ink (or violet deep) | Rings radiating from a tile-built x (the logo's Ripple motion) | paper rings, tangerine (or violet) first ring and x |
| Bloom | ube tile (or violet deep) | The Branding mascot as a lit height field (six lobes plus a core, top-left light) with its face, on a dithered glow | ube-ink (or violet) dots |
| Big x | ink (or violet deep) | The multiply sign, bleeding off the edge, in a gradient dither with a faint halo | tangerine (or violet), paper halo |
| Five fronts | paper | Five full-height service cells, each a pictogram (mascot, rocket, magnifier, gear, screen with play) | each tile's own ink on its tile |

On LinkedIn and X the avatar hides the left 30% and bottom 65% (22% and 55% on X) and the top edge is cropped on some layouts, so the focal subject
(sun, rings, mascot, big x, pixel x) is centred at about 68 to 70% of the width and only ambient texture sits behind the avatar zone.
`/brand` previews have an avatar-zone toggle for checking this. One subject, one ink per layer; never add a second subject, a raster, a gradient or text.

Every image on `/brand` has **COPY SVG** (the markup on the clipboard, ready to paste into Figma or code),
**COPY PNG** (a PNG on the clipboard: native size for social art, 2048px wide for logos, the finished still frame
for animated files), **SVG ↓** and **PNG ↓**. CSS and JSON files have **COPY** (their contents). Clipboard images
need a secure context (https or localhost) and a user click; if the browser blocks it the button says so and the
downloads still work. The 5×7 pixel face (`lib/brand/pixel-font.ts`) remains available for text artwork; the covers do not use it.

## 23. Dithered illustration, exact

- **Algorithm:** an 8×8 ordered (Bayer) dither. Threshold for a dot at `(x, y)` is `(bayer[y % 8][x % 8] + 0.5) / 64`
  (matrix in `100xlab-system.json`, built by the standard recursive 2×2 expansion). Each dot's brightness (from the
  scene) either clears its threshold (draw the **ink colour**) or not (**fully transparent**). There are no mid-tones, no
  anti-aliasing and no second colour, so the tile colour shows through the gaps.
- **Dot size:** 1 CSS px (`GRAIN = 1`), snapped to a whole number of device pixels (`dev = max(1, round(dpr))`);
  canvas size is `floor(cssSize × dpr ÷ dev)` with a floor of 80×54 dots, shown at `(w × dev) ÷ dpr` CSS px, with
  `image-rendering: pixelated`. The result is crisp, evenly sized dots at any zoom.
- **Ink:** read from the computed `color` of the box, which inside a tone cell is that tile's deep ink. On a service tile
  the art is therefore always the tile's own partner ink, never black.
- **Scenes** (WebGL2 fragment shader, one fragment per dot, no buffers; `antialias: false`, `powerPreference: low-power`):
  four 3D scenes raymarched from a signed-distance `map(p)` with the ground at y = −1 and a shared camera and lighting in
  `RENDER_3D` (`lib/motion/dither/glsl.ts`, scenes in `scenes.ts`): **Launch** a rocket on its pad (hover lights the
  engine and it lifts off, on a loop); **SEO/GEO** a search bar over Google, ChatGPT and Claude tiles (hover types a
  query and the tiles hop); **Automation** a robot at a desk filing an inbox sheet by sheet; **Video** a retro TV with
  the launch video paused (plays on hover). **Branding** is a 2D signed-distance mascot: a puffy six-lobed body with a glow
  inside (`mascot.ts`). Uniforms: `uInk` (colour), `uT` (scene time, advances only while hovered), `uE` (hover energy 0..1).
- **Behaviour:** scenes are **still at rest**; they play while the card is hovered or focused and ease to a stop on leave
  (`uE` eases 0 to 1 and back). On touch screens a scene plays once when it scrolls into view. The mascot never rests: it
  animates while in view, follows the cursor, and changes expression: calm, curious, happy on hover, dizzy on fast
  moves, excited on press, wondering after 4s without movement, asleep after 14s.
- **Performance rules:** create the GL context in an idle slot after load or when the card nears the viewport; draw only
  in view and only while something changes; dispose on unmount. No WebGL2: the box stays empty (the card still reads
  because its label and promise are text). Reduced motion: a still frame only.
- **Reproducing without the repo:** use any renderer. Draw one subject per tile from the table in §21, shaded as a
  continuous brightness field, then print it through the Bayer threshold above in the tile ink on transparent. Match
  the framing and subject to the live cards; do not add a second colour, outlines or gradients.

## 24. Motion primitives, exact parameters

| Primitive | Behaviour |
|---|---|
| `Reveal` | Fade + translateY 16px up to none, 600ms `ease-standard`, once, when the top reaches 12% above the viewport bottom; waits for `lab:ready`; `stagger` = 60ms between children |
| `SplitReveal` | Headline lines slide up 100% out of their own masks, 600ms `ease-mechanical`, 60ms between lines (30ms words, 15ms chars); masks grow 0.14em at the bottom so descenders do not clip; re-splits on width change; hero headlines play on mount |
| `CountUp` | Readouts tick from zeros to the value with the same digit count, ≤900ms, tabular figures, stable width; final text equals the value |
| `Magnetic` | Primary hero buttons follow the pointer by 25% up to 8px, return in 320ms `ease-standard`; pointer: fine only |
| `Marquee` | ≥1024px with motion allowed only: 40px/s horizontal drift, pauses on hover, press and focus, keyboard `PAUSE` toggle named "Pause client list"; elsewhere a static wrapped list |
| `PageTransition` | Ink shutter of `--cols` vertical strips (12 max, fewer on small screens) covers the new page, then retracts upward left to right (strips 360ms each, mechanical, about 500ms total) while content fades in (320ms, 120ms delay); skipped on first load; reduced motion: 100ms fade |
| `Preloader` | First visit per session only (`sessionStorage`), 2.2s: a 10×10 dot plot counts 000 to 100 over 1.3s starting at 0.1s, the diagonals flash lime at 1.45s, the panel lifts from 1.7s over 0.5s, `lab:ready` fires at 1.7s; click or Esc skips; reduced motion skips |
| `BrandReel` | Code-built loop: the mark fades, each service word snaps in (450ms `ease-snap`), holds 1.2s, slides out (350ms `ease-mechanical`), then the mark returns |
| Smooth scroll | Lenis, `lerp 0.1`, ticks only while input or scroll is happening; off under reduced motion |
| Bento grow | `flex-grow` ×1.6 over 650ms `ease-standard` (the one sanctioned layout animation) |
| Status pulse | 1.6s, the only infinite loop |
| Buttons and links | colour/fill 200ms `ease-standard`; press `translateY(1px)` 100ms |

All of these animate opacity and transform with Web Animations and IntersectionObserver. Do not add an animation
library. After any motion change, confirm the main thread is idle at rest.

## 25. Theming and surfaces

- Light is the default. Dark is "lab at night": paper `#1b1a18`, oat `#232220`, cards `#2a2926`, text `#fefdfb`.
- Lime, service tiles and their inks are identical in both themes. Never re-derive them for dark; never put
  `--ink` on them (use `--on-signal` or the tile ink, which the tone classes already do).
- A `tone-ink` cell is a dark card in both themes (lifted to `#2a2926` on the dark page so it does not vanish).
- Charts and data use ink, lime and line only (max three series). A service colour appears only for its own service.
- Colour split on a typical light page: neutrals ≈ 90%, lime and tiles together ≈ 5% outside the home bento.

## 26. Content model an agent must keep

- All copy is in `site/src/content/*.ts` (`home.ts`, `fronts.ts`, `services.ts`, `pricing.ts`, `work.ts`, `faq.ts`,
  `method.ts`, `about.ts`, `contact.ts`, `brand.ts`, `brand-agents.ts`, `site.ts`) and is imported through
  `@/content`. Components never hold copy.
- Fronts are typed (`id`, `label`, `promise`, `deliverables`, `visual`, `tileVar`, `detail`); changing a front's colour
  means changing its tile and ink pair in `globals.css` **and** `lib/palette.ts`.
- Prices are quoted per project until real numbers exist: show `QUOTED PER PROJECT` (`priceOnQuote`), never invent a number (`PRICES_PENDING`).
- Case studies are experiments with serials (`EXP-004` and so on); numbers are real and sourced or marked `TBA`.

## 27. Acceptance checklist (run before you say it matches)

1. `npx tsc --noEmit -p .`, `pnpm lint`, `pnpm test`, `pnpm build` all pass from `site/`.
2. Open `/`, `/services`, `/brand`, `/work` in **light and dark**, at **390, 768, 1280 and 1920** px wide. Compare
   against the live site: same tokens, same type ramp, same cell tones, same radii, same gaps.
3. **Tokens:** `getComputedStyle(document.documentElement)` returns the §16 values for `--signal`, `--ube` … and the
   dark overrides under `data-theme="dark"`. `tests/palette.test.ts` passes.
4. **Rules:** exactly one primary (lime) button per view; lime appears only on actions and the `LIVE` tag; each service
   colour appears only for its service; the logo sits on paper, oat, white or ink; no shadows, gradients or glass; every
   display headline is sentence case; no exclamation marks.
5. **Wordmark:** the resting frame equals the finished logo; tiles are 0.84 of the pitch with 0.16 radius; unlit tiles
   are `--steel` at 45%; the x is an accent, not ink. Compare against `100xlab-wordmark-pixel-ink-x-tangerine.svg`.
6. **Dither:** zoom to 400%; every dot is the same size, one colour (the tile's ink), no grey, no blur.
7. **Motion:** entrances wait for `lab:ready`; nothing loops except the `LIVE` light; with `prefers-reduced-motion`
   every view is complete and still; the idle main thread shows no per-frame work.
8. **Accessibility:** text 4.5:1, UI boundaries 3:1; visible 2px focus on every control; hit areas ≥ 44px; decorative
   canvases are `aria-hidden` inside a labelled `role="img"` wrapper; the console is clean.
9. **Link preview and favicon** match §22 (bump `VERSION` if you changed them).
10. If you changed a colour, update `globals.css`, `lib/palette.ts`, `100xDesignSystem.md` and this brief together.

## 28. Where things live

- Human-facing brand page: `/brand`. This brief: `/brand/agents.md` (also embedded in `/brand` as
  `<script type="text/markdown" id="agent-brief">`). Site summary for LLMs: `/llms.txt`.
- Kit downloads (generated from the live code): `/brand/assets/100xlab-tokens.css` (the full stylesheet),
  `/brand/assets/100xlab-system.json` (palette, layout, type, motion, wordmark glyphs, Bayer matrix),
  `/brand/assets/100xlab-palette.json`, and every logo SVG.
- Full design rationale (may lag the code): `100xDesignSystem.md` at the repo root.
