/* bubbleup.gg — the app's own look, landing-tuned */
:root {
  --purple: #9147ff; --cyan: #36c6ff; --amber: #ffb02e; --green: #2fd66f;
  /* The site's red, named here for the first time (finding 148, Kevin at walk
     11: the demo's `live` mark reads red). The NUMBER is not new — it is
     `main.js`'s `YOUTUBE_HUE = 0xff4e45`, the one red this site already
     draws — so naming it is what stops a second red being invented the next
     time something has to say danger or live. `main.js` keeps its own copy
     because a PIXI tint is a number and not a custom property. */
  --red: #ff4e45;
  --ink: #08080b; --bg: #0e0e10;
  --line: #26262f; --line-hi: #3a3a47;
  --fg: #f0f0f6; --fg-2: #b9b9c6; --fg-3: #7b7b8b;
  --mono: ui-monospace, "SF Mono", "JetBrains Mono", "IBM Plex Mono", Menlo, "Cascadia Mono", Consolas, monospace;

  /* ---------- `--ui`: ONE scale for the site's chrome, on every page --------
   *
   * FINDING 94 (Kevin, walk 8): *"A little weird how button sizes change so
   * drastically per page like from demo and homepage to pricing/iphone the logo
   * and the top/bottom/floating/etc buttons … I don't think there's a cohesion
   * from fullscreen 5k 42" 240hz monitor or something crazy to just regular mac
   * browser width on a 1080p monitor."*
   *
   * He is right and the BEFORE table in this lane's report is the proof: at
   * 3840x2160 the same three-dot mark measured 34 / 30.6 / 17 / 17 px on
   * `/` `/demo` `/pricing` `/phone`, because four stylesheets each had their own
   * answer — `/demo` on `clamp(10px, 1.2vh, 18px)` (finding 49), `/` on
   * `clamp(10px, min(1.1vh, 0.78vw), 17px)` (finding 74), and `/pricing` and
   * `/phone` on nothing at all, so a 5K screen got a laptop's header. This is
   * the one number those four answers become. EVERY piece of chrome on the site
   * is a multiple of it, so two pages can no longer disagree about how big "a
   * chip" is, and every page's chrome moves together from a laptop to a wall.
   *
   * THE FLOOR IS 13px, AND IT IS TODAY'S `/demo` AT 1080p. `--chrome` on the
   * demo page is `1.2vh`, which is 12.96px in a 1920x1080 window — the LARGEST
   * header chrome this site has at that size, on any page. Putting the floor
   * there (13px: one twentieth of a pixel over it, and a round number whose
   * whole tenths are exactly the multiples below) is what makes the brief's
   * "nothing gets smaller on a 1080p window" true of every page at once: the
   * three pages that were smaller come UP to the demo's size, and the demo's own
   * chrome does not move there. Every multiple in this file, in `demo.css`, in
   * `contact.css`, in `pricing.css`, in `phone.css` and in `privacy.css` is a
   * whole tenth of the unit, exactly as findings 49 and 74 wrote them as whole
   * tenths of their own 10px floor — so the vocabulary is unchanged and only the
   * number it is spoken in moved.
   *
   * THE SLOPE IS `max(0.68vw, 1.2vh)`, AND THE `max()` IS LOAD-BEARING.
   *   - `0.68vw` is the growth, on WIDTH, because width is the axis Kevin named
   *     ("5k 42 inch" against "regular mac browser width on a 1080p monitor").
   *     It crosses the 13px floor at 1912px, so every window narrower than a
   *     full 1080p screen sits ON the floor: a 1440x900 laptop, a 1280x800 one
   *     and a 400px phone all get the same chrome, which is the whole of what
   *     "no cohesion at browser width" was asking for.
   *   - `1.2vh` is `demo.css`'s own slope, kept. Without it a pure-width unit
   *     would SHRINK the demo page's furniture on a tall window — 1920x1200 is a
   *     real monitor and today gives that page 14.4px against a width term's
   *     13.06 — and the brief forbids anything getting smaller. With the `max()`
   *     the unit is provably at or above every chrome value the site has today,
   *     at every viewport, because every one of those values is at most `1.2vh`
   *     and at most its own ceiling of 17-18px: below 13px of `1.2vh` the floor
   *     covers them, between 13 and 30 the `1.2vh` term itself does, and above
   *     30 the ceiling is still nearly twice their 18. That is a proof rather
   *     than a sample, which is why the guard is written this way and not as a
   *     second clamp.
   *
   * THE CEILING IS 28px, REACHED AT 4118px OF WIDTH (or 2333px of height) — so a
   * 5K screen is the first one that gets all of it, a 4K screen is still short
   * of it, and an 8K one gets no more. 28px is 2.15x the floor, and the reason
   * it is not the 17-18px the old ceilings stopped at is that those were reached
   * at ~1500px of HEIGHT and were never asked to answer for a 42-inch panel: at
   * 5120 across 36.6 inches a CSS pixel is ~140ppi against a 24-inch 1080p
   * screen's ~92, so 13px of chrome there is physically two thirds the size of
   * the same chrome on the laptop, before the extra viewing distance of a
   * desk-width monitor. 28px puts the 5K header back at about 1.4x the laptop's
   * physical size, which is what a screen that far away needs.
   *
   * IT IS 28 AND NOT 30 FOR A MEASURED REASON. `/pricing`'s reading column is
   * capped at 1100px (`pricing.css`), and the Buy chip sits in a grid track
   * 525.9px wide inside it. That chip's natural width is 17.52 x the unit — 4
   * of horizontal padding plus fifteen tracked-out mono characters — so it
   * crosses the track at a unit of 30.0 and its label folds onto a second line
   * at any viewport 4412px or wider. At 28 it is 490.6px against the same
   * 525.9px track: one line, with 35px to spare, everywhere. The chrome-content
   * imbalance BEHIND that number is a real finding and this lane does not close
   * it (the cards' own type stops at its clamp ceilings while the chrome keeps
   * going) — see the lane report's §6. The ceiling is only where the chrome
   * stops outrunning it.
   *
   * WHAT DOES NOT SCALE: the 1px hairlines, the 3px purple spine, the drop
   * shadows, and the 18/22px insets the fixed corners and the document wraps
   * stand at. Signature and page geometry, not proportion — the same call
   * findings 49 and 74 both made.
   *
   * WHAT IS DELIBERATELY OUT: the landing's reader card. It is the SCENE's
   * nameplate, it wanders with the reigning bubble, and it keeps `--room` /
   * `--card` below exactly as finding 74 left them. See that block's comment. */
  --ui: clamp(13px, max(0.68vw, 1.2vh), 28px);

  /* ---------- `--head-cap`: ONE width for the header's rule ----------------
   *
   * FINDING 98, ROUND 2 (Fable, on the frames). Round 1 put the purple door at
   * the end of the head's row on `/pricing`, `/phone` and `/privacy`, and from
   * 2560x1440 up the head wrapped to two lines on all three: those pages hold
   * their whole document inside a 1100px READING column (a card you can read at
   * a glance — finding 87), while `--ui` keeps growing with the screen, and a
   * brand cluster plus three chips stopped fitting. Measured at 2560: 396 + 691
   * in 1056 of rule. Without the door the same row was 668, and at 5120 it was
   * 1055 against 1056 — one pixel of slack. The door spent a budget that was
   * already gone.
   *
   * The ruling: the HEADER spans what `/demo`'s header spans, on every page
   * that has one, while the reading column below it stays 1100px. So the
   * expression that used to live in demo.css is here instead, byte for byte,
   * and demo.css now reads it from here — one number, four pages, and they
   * cannot come apart. Its three bounds and the `dvh` guard are explained where
   * they were written, at demo.css's `.wrap`; it is quoted rather than
   * re-derived because "the same width as /demo" is the ruling, and any second
   * expression that merely agreed today would be free to stop agreeing.
   *
   * It is a `max-width` everywhere it is used, never a `width`: below the floor
   * a max-width simply stops binding, which is what keeps a 400px phone at full
   * width instead of 1280px of overflow. */
  --head-cap: clamp(1280px, min(92vw, calc((100dvh - 290px) * 32 / 9 + 66px)), 2200px);
}
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { height: 100%; }
body {
  background: var(--bg); color: var(--fg); font-family: var(--mono);
  overflow: hidden;
}
/* operator-page ground: two radial glows + hairline grid */
body::before {
  content: ""; position: fixed; inset: 0; pointer-events: none; z-index: 0;
  background:
    radial-gradient(ellipse 70% 45% at 50% -8%, rgba(145, 71, 255, 0.13), transparent 60%),
    radial-gradient(ellipse 70% 45% at 50% 108%, rgba(54, 198, 255, 0.07), transparent 60%),
    repeating-linear-gradient(0deg, transparent, transparent 45px, rgba(58, 58, 71, 0.14) 45px, rgba(58, 58, 71, 0.14) 46px),
    repeating-linear-gradient(90deg, transparent, transparent 45px, rgba(58, 58, 71, 0.14) 45px, rgba(58, 58, 71, 0.14) 46px);
}
#stage { position: fixed; inset: 0; z-index: 1; opacity: 0.9; }

/* bubbles — text anchors mirror the app: label bottom-anchored at the circle's
   center (grows upward), sub-line + count top-anchored at center (grow downward),
   zero gaps between them */
.bub {
  position: absolute; left: 0; top: 0; border-radius: 50%;
  will-change: transform; user-select: none; pointer-events: none;
  overflow: visible;
}
.bub .lbl, .bub img { position: absolute; left: 50%; bottom: 50%; transform: translateX(-50%); }
.bub .low {
  position: absolute; left: 50%; top: 50%; transform: translateX(-50%);
  display: flex; flex-direction: column; align-items: center; gap: 0;
}
.bub .sub { color: #fff; opacity: 0.6; line-height: 1.15; white-space: nowrap; }
/* The rim, and why it is an <svg> and not a border.
   A merged session cuts a bucket's rim into one arc per chat that fed it, sized
   by that chat's share and starting at 12 o'clock (`web/src/rim.ts` rimArcs,
   drawn by `web/src/viz.ts` redraw). A CSS border is one colour the whole way
   round, so `main.js` paints the arcs as paths instead — over a fixed 100x100
   viewBox on a circle of r=50, which puts the stroke's CENTRELINE on the
   bubble's own radius exactly where the app strokes it. `overflow: visible` on
   `.bub` is what lets the outer half of that stroke show.
   Colour, width and opacity all come from JS: the hue is the chat's PLATFORM's
   (TWITCH_HUE / YOUTUBE_HUE / the reserved KICK_HUE — finding 61, one hue per
   platform, so two Twitch chats share purple) and the width and alpha are the
   app's TREND_STROKE, which reads momentum on the rim a bubble already has. */
.bub .rim { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
.bub .rim > * { fill: none; stroke-linecap: butt; }
/* The BODY is one colour — the dominant chat's, at the app's 0.28 — and `main.js`
   sets it per bubble. This is only the ground under it: a bucket is no longer
   coloured by whether it is text or a number ("n.kind plays no part any more: a
   numeric bucket is colored by its SOURCE", `viz.ts` redraw), so `.txt`/`.num`
   no longer choose a hue. */
.bub { background: rgba(145, 71, 255, 0.28); }
/* Label, receipt and count: the app's in-bubble glyph treatment.
   No weight of their own — Pixi's TextStyle default is normal, and the landing
   was the only place these ran bold — plus the halo `viz.ts`'s `labelHalo`
   bakes into every raster: black, 0.7 alpha, 0.12em wide, round-joined, and
   BEHIND the ink (Pixi strokes first and fills second, which is what
   `paint-order` reproduces here). It is the overlay's light-background
   hardening; on this page it is what stops a white label dissolving into a
   bubble it happens to be sitting over. */
.bub .lbl, .bub .cnt, .bub .sub {
  paint-order: stroke fill;
  -webkit-text-stroke: 0.12em rgba(0, 0, 0, 0.7);
}
.bub .lbl { color: #ffffff; line-height: 1; white-space: nowrap; }
.bub .cnt { color: #bbbbbb; line-height: 1.1; }
.bub img { display: block; object-fit: contain; }

/* the three-dot mark. The BASE geometry is 17px with the dots hand-placed in px
   inside it, and it stays that: this rule is also `embed.html`'s mark — a
   fragment inside somebody else's layout, which has no business growing to their
   viewport — and the landing reader card's, which is dressed by `--card` below.
   The two places the mark is CHROME restate it as multiples: `.brand .mark`
   here and `.cu-pill .mark` in contact.css. */
.mark { position: relative; display: inline-block; width: 17px; height: 17px; }
.mark i { position: absolute; border-radius: 50%; display: block; }
.mark i:nth-child(1) { width: 9px; height: 9px; background: var(--purple); left: 5px; top: 7px; }
.mark i:nth-child(2) { width: 5px; height: 5px; background: var(--cyan); left: 0; top: 1px; }
.mark i:nth-child(3) { width: 4px; height: 4px; background: var(--amber); left: 12px; top: 0; }

/* ---------- the header, on all four document pages (finding 94) ----------
 *
 * `.brand` is the mark + wordmark link at the top left of `/demo`, `/pricing`,
 * `/phone` and `/privacy`. It did not exist in this file before: demo.css sized
 * it off its own `--chrome` and the other three each carried a flat 10px gap, a
 * 17px mark and a `clamp(19px, 1.1vw, 26px)` wordmark, which is three
 * stylesheets' worth of the same object at three different sizes. One rule, on
 * `--ui`, and the four pages cannot come apart again.
 *
 * The mark's three dots are hand-placed in px inside a 17px box, so the box
 * cannot be grown on its own — the dots would stay where they were. Restated as
 * multiples: 1.7 of the unit for the box, 0.9/0.5/0.4 for the dots, 0.5/0/1.2
 * across and 0.7/0.1/0 down. `transform: scale()` was the shorter way and the
 * wrong one — it leaves the old layout box behind, so the gap to the wordmark
 * would shrink as the mark grew. */
.brand { gap: var(--ui); }
.brand .wm { font-size: calc(var(--ui) * 1.9); }
.brand .mark {
  width: calc(var(--ui) * 1.7);
  height: calc(var(--ui) * 1.7);
}
.brand .mark i:nth-child(1) {
  width: calc(var(--ui) * 0.9);
  height: calc(var(--ui) * 0.9);
  left: calc(var(--ui) * 0.5);
  top: calc(var(--ui) * 0.7);
}
.brand .mark i:nth-child(2) {
  width: calc(var(--ui) * 0.5);
  height: calc(var(--ui) * 0.5);
  left: 0;
  top: calc(var(--ui) * 0.1);
}
.brand .mark i:nth-child(3) {
  width: calc(var(--ui) * 0.4);
  height: calc(var(--ui) * 0.4);
  left: calc(var(--ui) * 1.2);
  top: 0;
}
/* FINDING 98 ROUND 2: the band the header stands in, on the three pages whose
   document is a 1100px reading column (`/pricing`, `/phone`, `/privacy`). The
   `<header>` is a sibling of `.wrap` on those pages rather than its first
   child, so the column can stay narrow while the rule above it runs the width
   `/demo`'s header runs — which is the ruling, and is also the least that could
   be moved: no negative margins, no second copy of the cap, no `width` that a
   phone would have to fight.

   The top padding is the one `.wrap` used to spend above the head — the same
   `clamp(28px, 2.4vw, 52px)` on all three pages — and those three now start at
   `padding-top: 0`, so the page's vertical rhythm is unchanged to the pixel.
   The 22px sides are `.wrap`'s too; the two pages that tighten to 16px on a
   phone tighten this with them, in the same media query.

   `/demo` does NOT carry this box: its `.wrap` IS the wide one, so its header
   already spans the cap and moving it would change nothing but the diff. */
.head-wrap {
  /* `--head-cap` is `/demo`'s wrap cap, and from 1391px up the two are the same
     number to the pixel — 1766.4 at 1920, 2200 at 2560, 3840 and 5120, all four
     measured. The second term is the 4% of air the cap's own track already
     carries (`min(92vw, …)`), restated here because `--head-cap`'s 1280px FLOOR
     is a PANE argument: demo.css keeps it so "every width from the 1200px
     breakpoint up to a laptop is byte-for-byte the layout that was walked", and
     a floor wider than the viewport makes a box full-bleed. On `/demo` that is
     correct — its document is full-bleed there too. Here it would push the
     brand and the hairline 16px outside the column they stand over on a phone,
     which is a visible change at 400px and the one thing this round was told
     not to make. So: `/demo`'s cap, never wider than the page's own air. The
     two differ only between ~1196 and ~1391px, where the band tracks the
     column instead of going full-bleed. */
  max-width: min(var(--head-cap), 92vw);
  margin: 0 auto;
  padding: clamp(28px, 2.4vw, 52px) 22px 0;
}

/* FINDING 98: the identity end of the head — the brand link, then the `Beta`
   badge. One unit of gap, the same `--ui` that sets the gap INSIDE `.brand`
   between the mark and the wordmark, so the three objects read as one group at
   every viewport instead of the badge drifting as the wordmark grows. It is a
   box rather than two loose children because `.head` is
   `justify-content: space-between`: three children would push the nav into the
   middle of the rule, and at 3840px that middle is a long way from anything.
   `flex-wrap` is the floor under a wordmark and a badge that cannot fit one
   line; nothing in the set reaches it (a 400px phone is the narrowest frame
   walked and it is one line there). */
.head-start {
  display: flex; align-items: center; flex-wrap: wrap;
  gap: var(--ui);
}

/* The cluster at the other end of the head: the nav doors, and since finding 98
   the purple door to the demo at the end of them. 1.2 of the unit is the flat
   12px the four pages each carried at the old 10px floor, and now it grows with
   the objects it separates rather than leaving a laptop's 12px hole between two
   that doubled.

   It WRAPS, and right-aligned, which is new with finding 98: a row that ends in
   `.chip.go` is about 300px wide at the unit's floor, and under ~560px there is
   no line that holds it beside the brand. `.head`'s own `flex-wrap` takes this
   whole cluster to a second line first; `.head-end nav` below wraps the door to
   a third if even that is not enough. Right-aligned because the cluster has
   always sat at the right end of the rule, and a row that jumped to the left
   edge on a phone would read as a different page. */
.head-end {
  gap: calc(var(--ui) * 1.2);
  flex-wrap: wrap;
  justify-content: flex-end;
}

/* the title card — a reader card (flag shape + purple spine), pinned by main.js */
.card {
  position: fixed; left: 0; top: 0; z-index: 2; pointer-events: none;
  display: flex; flex-direction: column; align-items: flex-start; gap: 12px;
  padding: 22px 30px;
  border: 1px solid var(--line);
  border-left: 3px solid var(--purple);
  border-radius: 3px 9px 9px 3px;
  background: linear-gradient(180deg, rgba(16, 16, 20, 0.82), rgba(10, 10, 13, 0.82));
  backdrop-filter: blur(10px) saturate(120%);
  -webkit-backdrop-filter: blur(10px) saturate(120%);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04), 0 14px 44px rgba(8, 8, 11, 0.55);
  will-change: transform;
}
.card.flip {
  border-left: 1px solid var(--line);
  border-right: 3px solid var(--purple);
  border-radius: 9px 3px 3px 9px;
}
.row { display: flex; align-items: center; gap: 10px; }
.wm { font-size: 26px; font-weight: 700; letter-spacing: 0.22em; }
.tag { color: var(--fg-2); font-size: 13px; letter-spacing: 0.05em; }
.domain { color: var(--fg-3); font-size: 10px; letter-spacing: 0.14em; text-transform: uppercase; }
/* The site's chip — a label, a state, or (with `.to` / `.go` / `.act`) a door.
   Its proportions are finding 49's and finding 74's, unchanged: 1 of the unit
   for the type, 0.5/1.2 of padding, a 0.7 gap and a 0.6 dot. At the old 10px
   floor those read 10 / 5,12 / 7 / 6, which is the chip that shipped. */
.chip {
  display: inline-flex; align-items: center; gap: calc(var(--ui) * 0.7);
  border: 1px solid var(--line-hi); border-radius: 999px;
  padding: calc(var(--ui) * 0.5) calc(var(--ui) * 1.2); color: var(--fg-2);
  font-size: var(--ui); font-weight: 700; letter-spacing: 0.09em; text-transform: uppercase;
  background: linear-gradient(180deg, rgba(35, 35, 44, 0.8), rgba(22, 22, 28, 0.8));
}
.chip .dot { width: calc(var(--ui) * 0.6); height: calc(var(--ui) * 0.6); border-radius: 50%; background: var(--amber); animation: pulse 2.2s ease-in-out infinite; }
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; } }
/* The way into the demo. Not on the reader card — the card wanders with the
   reigning bubble, and a door should not. Fixed in the top-right corner: the
   bubbles gravitate to center so a corner is always clear, and top-right is
   where demo.html keeps its own live-demo pill, so the two pages agree about
   where "demo" lives. A chip in the purple that means "pressable" everywhere
   else on this product. */
.chip.go {
  position: fixed; z-index: 3;
  top: max(18px, env(safe-area-inset-top));
  right: max(18px, env(safe-area-inset-right));
  pointer-events: auto; text-decoration: none;
  color: var(--fg); border-color: var(--purple);
  /* The page's one call to action, sized like one: a step up from chip scale,
     still a chip — not a banner. The shadow lifts it off the field the way the
     card is lifted, plus a breath of purple so the eye finds it. 1.1/2 of
     padding, a 0.9 gap and 1.3 of type — finding 74's multiples, now on the
     site's own unit, and at the old 10px floor they read 11/20, 9 and 13. */
  padding: calc(var(--ui) * 1.1) calc(var(--ui) * 2); gap: calc(var(--ui) * 0.9);
  font-size: calc(var(--ui) * 1.3); letter-spacing: 0.1em;
  box-shadow: 0 10px 30px rgba(8, 8, 11, 0.5), 0 0 18px rgba(145, 71, 255, 0.18);
}
.chip.go::after { content: " \2192"; color: var(--purple); }
.chip.go:hover { color: #fff; border-color: var(--purple); background: linear-gradient(180deg, rgba(145, 71, 255, 0.28), rgba(145, 71, 255, 0.14)); }
.chip.go:focus-visible { outline: 2px solid var(--purple); outline-offset: 2px; }

/* ---------- `.chip.to`: a door that is not THE door (finding 75) ----------
 *
 * Before this, the site's chip vocabulary had three words and no fourth: `.go`
 * is the landing's one call to action, `.act` (pricing.css) is a button in the
 * flow, and a bare `.chip` is a LABEL — `Beta`, a tier's state (finding 98
 * retired the fourth, `/demo`'s `live demo`).
 * There was no way to say "this is pressable, and it is not the main thing",
 * which is exactly what a nav link is, so the five pages could not reach each
 * other and finding 75 was raised against that.
 *
 * So: the site's chip, unchanged in size, weight and colour, plus the two
 * things that make a chip a door — a hover that reaches for the purple, and a
 * focus ring. Deliberately NOT given `.go`'s arrow: the arrow on this site
 * means "the thing this page is for", and there is one of those per page.
 *
 * It lives in style.css rather than in each page's stylesheet because it is the
 * one piece of furniture all five pages carry, and a nav that drifted from page
 * to page would be worse than no nav. This file is on every page already. */
a.chip.to {
  pointer-events: auto;
  text-decoration: none;
  white-space: nowrap;
}
/* Two doors, so the head's nav is a row with a gap in it (finding 89). The gap
   is `.head-end`'s own — 1.2 of the unit, the flat 12px every page carried at
   the old floor — so the four chrome items on a document page are evenly spaced
   without any page knowing how many doors there are. The landing is not
   affected: its nav is `.chip-row`, which sets its own. */
.head-end nav {
  display: flex; align-items: center; flex-wrap: wrap; gap: calc(var(--ui) * 1.2);
  /* Finding 98 put a third, much wider chip in here. When it wraps, the line it
     leaves behind stays against the right edge — the corner the row has always
     been read in. */
  justify-content: flex-end;
}
/* THE DOOR, IN A HEAD (finding 98). `.chip.go` is `position: fixed` because on
   the landing it is furniture standing on a field — there is no header to put
   it in. On the four document pages there is one, and the row it ends is in
   normal flow above the hairline, so the fixed corner would put it ON TOP of a
   page that scrolls under it. Unfixed here, exactly the way `.chip-row .chip.go`
   is unfixed on the landing, and for the same reason: every size, colour and
   shadow of that one rule is kept, and only `position` is answered. */
.head-end .chip.go { position: static; }
a.chip.to:hover { color: var(--fg); border-color: var(--purple); }
a.chip.to:focus-visible { outline: 2px solid var(--purple); outline-offset: 2px; }
/* The page you are on, marked with the fill a press would have produced — the
   same gradient as `.chip.go:hover`, value for value. It is still a link (it
   goes to the top of the page it names), and `aria-current` is what actually
   carries the meaning; this is only the picture of it. */
a.chip.to[aria-current="page"] {
  color: var(--fg);
  border-color: var(--purple);
  background: linear-gradient(180deg, rgba(145, 71, 255, 0.28), rgba(145, 71, 255, 0.14));
}

/* ---------- the landing's top-right chip row ----------
 *
 * `.chip.go`'s comment above says the top-right corner is where "demo" lives
 * and why it is a corner and not the card: the card wanders with the reigning
 * bubble, and a door should not. `Pricing` is a door on the same argument, so
 * it stands in the same corner, and the corner becomes a ROW rather than a
 * second fixed element placed by arithmetic against the first one's width.
 *
 * WHY THE ROW OWNS THE CORNER AND `.chip.go` IS SET STATIC INSIDE IT. The
 * alternative was to lift `position`/`top`/`right` out of `.chip.go` and onto
 * the row. That is the tidier diff and the wrong one here: lane
 * `site-bigscreen` is editing `.chip.go` in parallel to scale the landing's
 * furniture with `--chrome`, and those are among the declarations it touches.
 * One extra line that overrides `position` inside the row leaves `.chip.go`'s
 * own rule untouched, so the two lanes cannot collide over it — and the chip
 * keeps every size, colour and shadow that rule gives it, whatever they become.
 *
 * `Pricing` is FIRST, so the row reads secondary → primary and the corner
 * itself — the strongest position on the page — still belongs to the demo.
 * FINDING 89 put `iPhone` between them, on the same rule: the two documents in
 * reading order, then the door the corner is for.
 * The row wraps, and with three chips in it that is no longer a contingency —
 * at 400px the row is two lines, right-aligned, which is better than any chip
 * shrinking or the corner sliding off. `max-width` is what holds it inside the
 * viewport; `site-nav.spec.ts` asserts the page does not scroll sideways at
 * that width and that every chip is on screen. */
.chip-row {
  position: fixed; z-index: 3;
  top: max(18px, env(safe-area-inset-top));
  right: max(18px, env(safe-area-inset-right));
  display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end;
  /* The gap between the corner's chips, on the same unit as the chips — a bare
     chip is 1 of type in 0.5/1.2 of padding, and 1 of gap beside it is what
     keeps the corner ONE piece of furniture rather than a 10px hole between two
     objects that have doubled (finding 74, restated on `--ui`). */
  gap: var(--ui);
  max-width: calc(100vw - 36px);
  /* The row is a container, not a target: the gaps between its chips must not
     take presses away from the field underneath. Each chip turns them back on
     (`.chip.go` already does, `.chip.to` does above). */
  pointer-events: none;
}
.chip-row .chip.go { position: static; }

/* small screens: the card still follows the reigning bubble — a nameplate that
   sat in a corner while the field moved without it read as a different page —
   but portrait has no width beside a bubble, so main.js seats it BELOW the
   leader (`.small`, plus `.under` / `.over` for which side of the bubble it
   ended up on) and shrinks it to leave the field its room. */
.card.small {
  align-items: center;
  padding: 14px 22px; gap: 8px;
  max-width: calc(100vw - 20px);
}
.card.small .wm { font-size: 19px; }
.card.small .tag { font-size: 11px; }
.card.small .domain { font-size: 9px; }
.card.small .chip { padding: 4px 10px; font-size: 9px; }
/* The spine turns to the edge that faces the bubble — the same move `.flip`
   makes horizontally — so the card always points at what it is naming, and the
   3px/9px radius keeps reading as a flag flown off that edge. */
.card.under {
  border-left: 1px solid var(--line);
  border-top: 3px solid var(--purple);
  border-radius: 3px 3px 9px 9px;
}
.card.over {
  border-left: 1px solid var(--line);
  border-bottom: 3px solid var(--purple);
  border-radius: 9px 9px 3px 3px;
}
/* ---------- big screens: the furniture, sized by the room it has ----------
 *
 * FINDING 74 (Kevin, walk 8): "the true homepage 'live demo' and 'like what you
 * see' on big screen 4k look small when the bubbles scale good just bc lots of
 * whitespace there like we have in the /demo big screen fixes".
 *
 * He is describing an asymmetry, not a taste. The field already scales with the
 * room: `main.js` sizes every bubble off `sqrt(AREA_BUDGET_FRAC * w * h)`, so a
 * 3840x2160 panel gets bubbles ~2.8x a laptop's. The three pieces of furniture
 * standing on that field did not move a pixel — a 262px card, a 234px chip and
 * a 208px pill, the same objects a 1280x800 window gets, marooned in the
 * corners of a wall. This is finding 49 on the demo page, one page over, and
 * the fix is deliberately the same one so the two pages cannot disagree about
 * how big "a chip" is: ONE number per family, every piece of furniture a
 * multiple of it, and clamps that sit on their FLOOR at laptop sizes so nothing
 * already walked moves.
 *
 * THE FLOOR IS THE SHIPPED PAGE, EXACTLY. 10px is `.chip`'s font size, and
 * every other value above is a whole tenth of it: 26/13/10 of type, 22/30 of
 * card padding, a 12px stack gap and a 10px row gap, a 17px mark with 9/5/4
 * dots at 5/0/12 across and 7/1/0 down, the beta chip's 5/12 padding and 7px
 * gap and 6px dot, `.chip.go`'s 11/20 and 9 and 13, the pill's 9/16 and 9 and
 * 12 and its 13px mark with 7/4/3 dots. At the floor every multiple below
 * reproduces those byte for byte — the measured boxes at 1280x800 are
 * 262x161 / 233.56x39 / 207.66x34 before and after.
 *
 * `--room` IS THE CLAMP'S MIDDLE TERM, AND IT HAS A WIDTH GUARD.
 *   - 1.1vh is the growth. Height, as on the demo page, and for the same reason
 *     restated for this one: a wide-but-short window is not a big screen, it is
 *     a laptop someone widened, and chrome that grew on `vw` alone would be
 *     biggest exactly where the field has least room to grow with it. 1.1vh
 *     crosses the 10px floor at 909px of viewport height, so every window
 *     900px tall or shorter — 1280x800 and 400x800 among them — is the shipped
 *     page to the pixel.
 *   - `min(…, 0.78vw)` is a guard, not a second opinion: 0.78vw is under 10px
 *     at any width up to 1282px, so ALL of the narrow band — the 1200px
 *     breakpoint the rest of the site is written around, and the phone widths
 *     below it — sits on the floor no matter how tall the window is. Without
 *     it a 900x1600 window would get a 2x card in a column with no room for
 *     one. Above about 1300px of width the height term is what binds, which is
 *     every screen this finding is about.
 *
 * TWO CEILINGS, ONE SLOPE. The card is the page's nameplate and carries all of
 * its type; the chip and the pill are controls parked in two corners. They grow
 * together and stop at different places: 20px for the card (reached at 1819px
 * of height) makes it 519x320 at 4K, almost exactly 2x today's 262x161, and
 * 17px for the corners (reached at 1545px) makes them ~1.7x — a 396x65 chip and
 * a 353x58 pill. A corner control at the card's 2x would be a banner, which is
 * the thing `.chip.go`'s own comment above already refuses at laptop size.
 *
 * WHAT DOES NOT SCALE. The 1px hairlines and the 3px spine stay where they are
 * — they are the signature, not a proportion, and the demo page's head rule
 * made the same call — and so do the drop shadows. `transform: scale()` was
 * rejected for the same reason finding 49 rejected it: it leaves the old layout
 * box behind, and `main.js` pins this card by reading `offsetWidth`/
 * `offsetHeight` every frame, so a scaled card would be parked by its stale
 * size and would drift under the leader bubble it is naming.
 *
 * WHY A `.home` SCOPE AND NOT `body`. style.css is loaded by five pages. The
 * `.card` is also embed.html's — a fragment inside somebody else's layout,
 * which has no business growing to their viewport.
 *
 * ---------- FINDING 94 took half of this block away, and kept this half ------
 *
 * `--chrome` is GONE. It sized the landing's two corner controls — the chip row
 * and the contact pill — and those are CHROME: the same objects, in the same
 * corners, as the other four pages carry. They are on `--ui` now, with the same
 * multiples this block gave them, so `/` and `/demo` and `/pricing` and `/phone`
 * cannot disagree about the size of a chip again.
 *
 * `--room` and `--card` STAY, untouched, and they are the landing's SCENE. The
 * reader card is not a header: it is the field's nameplate, it wanders with the
 * reigning bubble (`main.js` pins it by reading `offsetWidth`/`offsetHeight`
 * every frame), and its wordmark is a DISPLAY size — 2.6 of its unit, where a
 * header wordmark is 1.9 of the shared one. Folding it into `--ui` was tried on
 * paper and costs one of the brief's own rulings whichever way it is done: at
 * 1.9 the 4K wordmark falls from finding 74's 52px to 49.6, and raising the
 * shared multiple until it clears 52 at 4K (2.37) puts a 31px wordmark in
 * `/pricing`'s header on a 1440x900 laptop, against the 19px it ships today.
 * So the card keeps its own scale and this lane leaves finding 74's numbers
 * exactly where they are. The lane report says so with the arithmetic. */
body.home {
  --room: min(1.1vh, 0.78vw);
  --card: clamp(10px, var(--room), 20px);
}

/* `:not(.small)` is how this block stays out of the portrait layout's way.
   `main.js` puts `.small` on the card below 700px of width or 520px of height,
   and dresses it in its own fixed sizes below; at either of those the guard and
   the floor already hold `--card` at 10px, so there is nothing here for the
   small card to lose — only a specificity fight to avoid. */
.home .card:not(.small) {
  gap: calc(var(--card) * 1.2);
  padding: calc(var(--card) * 2.2) calc(var(--card) * 3);
}
.home .card:not(.small) .row {
  gap: var(--card);
}
.home .card:not(.small) .wm {
  font-size: calc(var(--card) * 2.6);
}
.home .card:not(.small) .tag {
  font-size: calc(var(--card) * 1.3);
}
.home .card:not(.small) .domain {
  font-size: var(--card);
}
/* The mark's three dots are hand-placed in px inside a 17px box, so the box
   cannot be grown on its own — the dots would stay where they were. Restated as
   multiples, which at the floor is the geometry above to the pixel. */
.home .card:not(.small) .mark {
  width: calc(var(--card) * 1.7);
  height: calc(var(--card) * 1.7);
}
.home .card:not(.small) .mark i:nth-child(1) {
  width: calc(var(--card) * 0.9);
  height: calc(var(--card) * 0.9);
  left: calc(var(--card) * 0.5);
  top: calc(var(--card) * 0.7);
}
.home .card:not(.small) .mark i:nth-child(2) {
  width: calc(var(--card) * 0.5);
  height: calc(var(--card) * 0.5);
  left: 0;
  top: calc(var(--card) * 0.1);
}
.home .card:not(.small) .mark i:nth-child(3) {
  width: calc(var(--card) * 0.4);
  height: calc(var(--card) * 0.4);
  left: calc(var(--card) * 1.2);
  top: 0;
}
/* The beta chip rides ON the card, so it is on the card's scale and not the
   corners' — it is a piece of the nameplate, not a third control. */
.home .card:not(.small) .chip {
  gap: calc(var(--card) * 0.7);
  padding: calc(var(--card) * 0.5) calc(var(--card) * 1.2);
  font-size: var(--card);
}
.home .card:not(.small) .chip .dot {
  width: calc(var(--card) * 0.6);
  height: calc(var(--card) * 0.6);
}

/* FINDING 94 removed four blocks that stood here: `.home .chip.go`,
   `.home .chip-row`, `.home .chip-row .chip.to` and `body.home .cu-pill` (with
   its mark). Every one of them was finding 74 telling ONE page how big a chip
   is, in multiples of a `--chrome` only that page had. The multiples are
   unchanged — they are on `.chip`, `.chip.go` and `.chip-row` above and in
   `contact.css`, over `--ui` — so the landing's corners are the same objects
   they were, and now they are the same objects `/demo`, `/pricing` and `/phone`
   have. The pill in particular no longer needs a `body.home` of extra
   specificity to reach past `contact.css`: that file sizes it for every page
   itself. */

@media (prefers-reduced-motion: reduce) {
  .chip .dot { animation: none; }
}
