/* Brûlée Focus marketing site.

   Direction: editorial paper with one dark act, drawn from studio-size.com and
   humanmade.co.jp. Both of those sites reach "designed" the same way, and it is
   almost entirely subtractive:

     - one grotesque, three weights, nothing else
     - five or six colours for the whole page
     - no strokes and no box-shadows anywhere; grouping comes from alignment,
       hairline rules and whitespace instead of from enclosure
     - very tight negative tracking on display type, wide positive tracking on
       small uppercase labels
     - one slow, decisive easing curve used for everything

   The previous sheet did the opposite: a 3-4px plum stroke around every box, a
   hard offset "sticker" shadow under each one, two radial gradients on the body
   and ~20 colour tokens. Enclosing a thing requires no compositional decision,
   which is exactly why generated UI reaches for it. Every deletion below trades
   an enclosure for an alignment.

   What survived from the old sheet, and why:
     - --demo-top / --demo-bot / --on-demo. They depict a page being browsed,
       not site chrome, so they are theme-constant by design (see .browser-page).
     - The .demo-cup geometry block. Those --win-* / --liq-* values are written
       by script.js from tools/render_mug.py output; the site must not drift
       from the sprite the extension actually ships.

   Hue pass (this revision). The page was built on #a85a2e terracotta, which is
   close enough to Anthropic's clay to read as borrowed, and it fought the one
   thing on the page that has to stay warm: an orange cat. Two warm families
   competing is why the chrome never settled. The page now runs a violet
   family (phantom.com's) as the UI colour and leaves warmth entirely to the
   art -- the cat, the coffee, the crema. Complement, not competition.

   The load-bearing rule, and the reason the buttons looked wrong even in the
   old palette: a light accent is a FILL, not a text colour. #ab9ff2 carries
   white at 2.3:1 -- nowhere near legible -- so the primary button is dark ink
   on lavender, which is how Phantom actually sets it. Text-on-paper uses a
   separate, much darker stop (--accent-text). Getting these two confused is
   what produces the washed-out lavender button you see on most clones. */

/* ---------- Fonts ---------- */
/* Vendored under site/fonts/ (SIL OFL, see fonts/LICENSE.txt). Self-hosted
   rather than CDN-linked: the extension has no backend and no network
   dependency, and the site should not acquire one. url()s resolve against this
   stylesheet, so they work whether the repo root is served or index.html is
   opened directly. */

@font-face {
  font-family: "Geist";
  src: url("fonts/Geist-Variable.woff2") format("woff2-variations");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Geist Mono";
  src: url("fonts/GeistMono-Variable.woff2") format("woff2-variations");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

/* ---------- Tokens ---------- */

:root {
  --font: "Geist", system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-mono: "Geist Mono", ui-monospace, SFMono-Regular, Menlo, monospace;

  /* The display unit. Both reference sites set `html { font-size: ~0.694vw }`
     so the whole composition scales as one object -- and in doing so they
     override the visitor's browser font-size preference (WCAG 1.4.4). This
     keeps the proportional feel where it does the work (the wordmark, the
     section numerals, the band rhythm) and leaves body copy in rem, where the
     preference actually matters.

     0.72vw lands at ~10.4px on a 1440 comp; the clamp floor is what keeps the
     wordmark from collapsing on a phone and the ceiling stops it running away
     on a 2560 display. */
  --u: clamp(4px, 0.72vw, 11px);

  /* 4pt grid, unchanged -- it was never the problem. */
  --s1: 4px;   --s2: 8px;   --s3: 12px;  --s4: 16px;
  --s5: 20px;  --s6: 24px;  --s7: 32px;  --s8: 48px;
  --s9: 64px;  --s10: 96px;

  /* Six values for the entire page. The neutrals are not grey -- every one of
     them carries a little of the accent's violet, which is what makes a
     restricted palette read as chosen rather than as default. Contrast against
     --paper, all AA at body size: ink 16.3:1, ink-2 7.9:1, ink-3 5.5:1,
     accent-text 6.1:1. */
  --paper: #f4f2fa;
  --panel: #fbfaff;
  --ink: #171327;
  --ink-2: #4e4664;
  --ink-3: #665d80;
  --rule: rgba(23, 19, 39, 0.12);
  --rule-strong: rgba(23, 19, 39, 0.24);

  /* Three stops of one hue, because one stop cannot do three jobs.

     #ab9ff2 is the brand colour, but it is a *light* colour, and this is a
     light page. Two separate limits bite:
       - as a fill it sits 2.1:1 against --paper, under the 3:1 that a control's
         own boundary needs to be identifiable (WCAG 1.4.11). A lavender pill on
         near-white paper is legible only because of the text inside it.
       - as text it is 2.3:1, which is not a text colour under any rule.
     So the light field runs the fill one step deeper (3.1:1 boundary, 5.3:1
     for the label) and text three steps deeper. The pale original is not lost:
     .act--dark hands both jobs back to it, where a dark ground makes it work.

     --accent-hover deepens rather than lightens. Lightening a pale fill on
     hover weakens the affordance exactly as the pointer commits to it, and it
     would drag the label under AA -- the deeper stop keeps it at 4.7:1. */
  --accent: #8b7ce9;
  --accent-hover: #8272e6;
  --accent-text: #5d43b8;
  --on-accent: #171327;

  /* Focus rings are their own token because they cannot use --accent: a 2px
     lavender ring sits at 2.3:1 against --paper and fails the 3:1 non-text
     minimum (WCAG 1.4.11). --accent-text clears it at 6.1:1, and because the
     dark act re-points --accent-text at the light lavender, one declaration
     stays correct on both fields. */
  --focus: var(--accent-text);

  /* The demo "web page" surface. Deliberately constant: it depicts a page being
     browsed, not site chrome.

     Previously both stops were CAT_RAMP entries (#531a26 / #470928) so the fur
     sat in its own shadow family. That tie is now half kept and half traded on
     purpose: the field is violet so it belongs to the palette, but it lands on
     the warm plum side of violet -- the cat's darkest edge is #470928 (EDGE in
     tools/render_mug.py), and running the panel too blue would leave the
     silhouette floating on an unrelated hue. Do not push these toward indigo
     without looking at the cat. */
  --demo-top: #3b1a4e;
  --demo-bot: #22102f;
  --on-demo: rgba(242, 237, 255, 0.34);
  --on-demo-faint: rgba(242, 237, 255, 0.07);

  /* Type. Display is driven off --u; everything from the lede down is fixed so
     it respects the browser's font-size setting. */
  /* The vw term only bites below ~380px, where --u has bottomed out on its
     clamp floor and "Brûlée Focus" at 18u would run 17px past the measure on a
     320px screen. Above that the min() always picks the --u term. */
  --t-display: min(calc(var(--u) * 18), 19vw);
  --t-h2: clamp(30px, 3.6vw, 44px);
  --t-h3: 21px;
  --t-lead: 20px;
  --t-body: 17px;
  --t-small: 15px;
  --t-label: 11px;

  /* Radii are 0 for structure. The single soft value is reserved for the two
     panels that depict a screen, where it reads as a device corner rather than
     as decoration. */
  --r-panel: 18px;

  /* studio-size's curve, at their 300ms. The extension keeps its own HIG timing
     (src/popup.css) -- product chrome and an editorial page have different
     jobs, and pretending otherwise is what makes marketing sites feel like
     settings screens. */
  --ease: cubic-bezier(0.51, 0.01, 0.2, 1);
  --dur: 300ms;

  --nav-h: 60px;

  color-scheme: light;
}

/* No `prefers-color-scheme` block. "One dark act" IS the structure: inverting
   the whole page would flatten the contrast between section (02) and the rest,
   which is the only rhythm the page has. Neither reference site ships a dark
   mode either; each commits to one look. */

/* ---------- Base ---------- */

* {
  box-sizing: border-box;
}

html {
  scroll-behavior: smooth;
  /* Anchor targets must clear the sticky header. */
  scroll-padding-top: calc(var(--nav-h) + var(--s6));
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink-2);
  font-family: var(--font);
  font-size: var(--t-body);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  /* Geist ships proportional figures by default; the page has enough numerals
     (times, sizes, step counts) that lining them up is worth one declaration. */
  font-variant-numeric: tabular-nums;
}

h1, h2, h3, p, ol, ul, figure, figcaption, dl, dd {
  margin: 0;
}

/* Deliberately NO `img { max-width: 100% }` reset here. Every image on this
   page sets its own width, and .mug-liquid is sized *wider than its own clip
   window* on purpose -- --liq-w is 10000/FILL_WINDOW.width, ~181%, which is
   what rebuilds the sprite at full cup-box scale inside overflow:hidden. A
   blanket max-width clamps that to 100% and the coffee renders as a narrow
   column stuck to the left wall of the mug. */

/* One container rule for every band on the page. Sections are full-width so a
   dark one bleeds edge to edge with no negative-margin trick; only .wrap
   constrains the measure. */
.wrap {
  width: min(1120px, calc(100vw - var(--s8)));
  margin: 0 auto;
}

/* ---------- Type ---------- */

h1 {
  color: var(--ink);
  font-size: var(--t-display);
  font-weight: 500;
  line-height: 0.84;
  /* Display tracking has to come in hard as size goes up -- this is the single
     most visible difference between set type and default type. */
  letter-spacing: -0.045em;
}

h2 {
  color: var(--ink);
  font-size: var(--t-h2);
  font-weight: 500;
  line-height: 1.04;
  letter-spacing: -0.03em;
  text-wrap: balance;
}

h3 {
  color: var(--ink);
  font-size: var(--t-h3);
  font-weight: 500;
  line-height: 1.25;
  letter-spacing: -0.02em;
}

p {
  color: var(--ink-2);
}

/* The inverse of display tracking: at 11px, uppercase needs air to stay
   legible, and the wide track is what marks it as a label rather than as small
   body copy. */
.eyebrow,
.num,
.row-num {
  font-family: var(--font-mono);
  font-size: var(--t-label);
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-3);
}

.lede {
  font-size: var(--t-lead);
  line-height: 1.5;
  max-width: 42ch;
}

code {
  /* Tight horizontal padding: at 0.4em the trailing punctuation after an
     inline <code> visibly detaches from it. */
  padding: 0.15em 0.28em;
  border-radius: 4px;
  background: rgba(23, 19, 39, 0.06);
  color: var(--ink);
  font-family: var(--font-mono);
  font-size: 0.88em;
}

/* ---------- Nav ---------- */

.nav {
  position: sticky;
  top: 0;
  z-index: 10;
  border-bottom: 1px solid var(--rule);
  background: color-mix(in srgb, var(--paper) 82%, transparent);
  backdrop-filter: saturate(1.4) blur(14px);
  -webkit-backdrop-filter: saturate(1.4) blur(14px);
}

.nav-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s6);
  height: var(--nav-h);
}

.nav-mark {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  color: var(--ink);
  font-size: var(--t-small);
  font-weight: 500;
  letter-spacing: -0.01em;
  text-decoration: none;
}

.nav-mark img {
  width: 20px;
  height: 20px;
}

.nav-links {
  display: flex;
  gap: var(--s6);
}

.nav-links a {
  color: var(--ink-3);
  font-size: var(--t-small);
  text-decoration: none;
  transition: color var(--dur) var(--ease);
}

.nav-links a:hover {
  color: var(--ink);
}

.nav-toggle {
  display: none;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  padding: 0 var(--s3);
  border: 1px solid var(--rule-strong);
  border-radius: 999px;
  background: transparent;
  color: var(--ink);
  font: 500 var(--t-small) / 1 var(--font);
}

.nav-toggle[aria-expanded="true"] {
  background: var(--ink);
  color: var(--paper);
}

/* ---------- Buttons ---------- */
/* Scoped to .btn. Styling the bare `a` element -- as the old sheet did -- turned
   every link on the page into a 44px filled button, footer and prose included. */

.btn {
  display: inline-flex;
  align-items: center;
  min-height: 46px;
  padding: 0 var(--s6);
  border: 1px solid transparent;
  border-radius: 999px;
  background: var(--accent);
  color: var(--on-accent);
  font-size: var(--t-small);
  font-weight: 500;
  letter-spacing: -0.005em;
  text-decoration: none;
  transition:
    background-color var(--dur) var(--ease),
    border-color var(--dur) var(--ease),
    color var(--dur) var(--ease);
}

.btn:hover {
  background: var(--accent-hover);
}

/* A hairline and a colour shift, not a raised slab. */
.btn.secondary {
  background: transparent;
  border-color: var(--rule-strong);
  color: var(--ink);
}

.btn.secondary:hover {
  background: rgba(23, 19, 39, 0.04);
  border-color: var(--ink);
}

a:focus-visible,
button:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 3px;
  border-radius: 2px;
}

/* ---------- Section spine ---------- */
/* Every section is a numbered act: a mono label in a narrow left column, the
   content in a wide right one. This is what replaced "each section is a card in
   a card grid" -- the page now has a structure instead of a stack. */

.act {
  padding-block: calc(var(--u) * 11);
  border-top: 1px solid var(--rule);
}

.act-grid {
  display: grid;
  grid-template-columns: minmax(0, 200px) minmax(0, 1fr);
  gap: var(--s8);
  align-items: start;
}

/* The label rides along beside its content, which is what gives a long section
   a sense of place. align-self:start is load-bearing -- a stretched grid item
   cannot stick. */
.act-label {
  position: sticky;
  top: calc(var(--nav-h) + var(--s7));
  display: grid;
  gap: var(--s2);
  align-self: start;
}

.act-body {
  min-width: 0;
  display: grid;
  gap: var(--s8);
}

.section-head {
  display: grid;
  gap: var(--s4);
  max-width: 52ch;
}

/* ---------- Hero ---------- */

.hero {
  border-top: 0;
  padding-block: calc(var(--u) * 9) calc(var(--u) * 11);
}

.hero-grid {
  display: grid;
  gap: var(--s5);
}

/* align-items:start, not end. The demo panel is much taller than the copy, and
   bottom-aligning them opened ~200px of dead air between the wordmark and the
   tagline -- a hole in the middle of the reading path. Trailing whitespace at
   the foot of a short column is normal; a gap mid-sentence is not. */
.hero-lower {
  display: grid;
  grid-template-columns: minmax(0, 0.85fr) minmax(300px, 1.15fr);
  gap: var(--s9);
  align-items: start;
  margin-top: var(--s7);
}

/* The children carry their own `max-width` in ch, so they do not need
   `justify-items: start` to keep a sane measure -- and leaving it off lets
   .hero-actions fill the column, which is what the full-width button rule
   under 640px hangs off. */
.hero-copy {
  display: grid;
  gap: var(--s5);
  min-width: 0;
  padding-bottom: var(--s4);
}

.tagline {
  color: var(--ink);
  font-size: var(--t-lead);
  font-weight: 400;
  line-height: 1.35;
  letter-spacing: -0.015em;
  max-width: 24ch;
}

.hero-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s3);
  margin-top: var(--s2);
}

.cat-note {
  display: grid;
  gap: var(--s1);
  max-width: 30ch;
  margin: 0;
  padding-top: var(--s4);
  border-top: 1px solid var(--rule);
  color: var(--ink-3);
  font-size: var(--t-small);
  line-height: 1.45;
}

.cat-note span {
  color: var(--accent-text);
  font-family: var(--font-mono);
  font-size: var(--t-label);
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

/* ---------- Hero browser demo ---------- */

.browser-demo {
  overflow: hidden;
  border-radius: var(--r-panel);
  background: var(--panel);
}

.browser-bar {
  display: flex;
  align-items: center;
  gap: var(--s2);
  min-height: 40px;
  padding: 0 var(--s4);
  border-bottom: 1px solid var(--rule);
}

/* Hairline outlines rather than the old 2px plum discs -- at this size a heavy
   stroke reads as three loud dots instead of as window furniture. */
.browser-bar span {
  width: 9px;
  height: 9px;
  border: 1px solid var(--rule-strong);
  border-radius: 50%;
}

.browser-bar strong {
  margin-left: var(--s2);
  color: var(--ink-3);
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 400;
}

.browser-page {
  position: relative;
  min-height: 420px;
  overflow: hidden;
  background:
    linear-gradient(90deg, var(--on-demo-faint) 1px, transparent 1px),
    linear-gradient(var(--demo-top), var(--demo-bot));
  background-size: 80px 80px, auto;
}

/* Top-left, clear of the cup and the cat's paw. Placed bottom-right, the
   skeleton bars ran underneath both and read as a rendering glitch. */
.feed-lines {
  position: absolute;
  left: var(--s7);
  top: var(--s7);
  display: grid;
  gap: var(--s3);
  width: min(240px, 46%);
}

.feed-lines span {
  height: 12px;
  border-radius: 999px;
  background: var(--on-demo);
}

.feed-lines span:nth-child(2) { width: 82%; }
.feed-lines span:nth-child(3) { width: 60%; }

.demo-cat {
  position: absolute;
  left: 11%;
  bottom: 8%;
  /* Sized so the auto height lands at ~324px -- the buddy art is 0.78:1, and at
     300px wide the box grows to 385px and clips against .browser-page's
     overflow:hidden. */
  width: min(252px, 44%);
}

/* `>` is load-bearing. As `.demo-cat img` this also matched the three mug
   sprite layers, so mug-back, mug-fill and mug-front each cast their own hard
   18px copy of themselves and the three stacked into a dark rectangle sitting
   under the cup. The cat is the only child image element; the cup is a <span>.

   The shadow itself is now soft rather than the old `0 18px 0` hard offset --
   that was the sticker idiom this redesign removed everywhere else, and on the
   dark demo panel it read as a smudge rather than as depth. */
/* This one reads brulee-buddy-large.png, not the sprite the extension ships.
   The hero paints the cat at 252px, which wants 504 device px on a 2x display;
   the extension's sprite is cut to the ~181px the float can actually paint. Two
   files rather than one because sharing would either blur the hero or make every
   page in the browser carry the hero's resolution. See tools/render_buddy.py.

   No `image-rendering: pixelated` anywhere on this page any more, on the cat or
   on the mug layers. The art is continuous-tone, so nearest-neighbour was not
   preserving a pixel grid -- there is none to preserve -- it was just dropping
   whichever rows and columns missed the sampling grid. */
.demo-cat > img {
  width: 100%;
  display: block;
  filter: drop-shadow(0 14px 18px rgba(14, 6, 26, 0.5));
}

/* The demo cup is the extension's own sprite, not a lookalike drawn in CSS.
   The --win-* / --liq-* values are written by script.js from the generated
   BRULEE_MUG geometry, so this page cannot drift away from the product the
   way a hand-tuned box would. Do not retune these by eye. */
.demo-cup {
  position: absolute;
  right: 4%;
  bottom: 6%;
  width: 31%;
  /* The sprite canvas is square and every layer shares its origin. */
  aspect-ratio: 1;
}

.demo-cup .mug-layer {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
}

.demo-cup .mug-window {
  position: absolute;
  left: var(--win-x);
  top: var(--win-y);
  width: var(--win-w);
  height: var(--win-h);
  overflow: hidden;
  border-radius: 0 0 50% 50% / 0 0 var(--win-r) var(--win-r);
}

.demo-cup .mug-liquid {
  position: absolute;
  display: block;
  width: var(--liq-w);
  height: var(--liq-h);
  left: var(--liq-x);
  top: var(--liq-y);
}

/* ---------- Brew index ---------- */
/* Transcribed from BREW_MODES in src/popup.js:42-75. If a mode changes there,
   change it here -- the site must not advertise a brew the popup cannot run.

   Was a bordered card stack; now a leader-rule index. A price-list/table-of-
   contents form suits four fixed options far better than four boxes, and the
   rule does the grouping the border used to. */

.index {
  display: grid;
}

.index-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: baseline;
  gap: var(--s2) var(--s5);
  padding-block: var(--s6);
  border-top: 1px solid var(--rule);
}

.index-row:last-child {
  border-bottom: 1px solid var(--rule);
}

/* The leader lives inside the <dt> because a <dl> row admits only dt/dd -- a
   decorative <span> between them would be invalid markup. */
.index-row dt {
  display: flex;
  align-items: center;
  gap: var(--s4);
  color: var(--ink);
  font-size: var(--t-h3);
  font-weight: 500;
  letter-spacing: -0.02em;
}

.index-row dt::after {
  content: "";
  flex: 1 1 auto;
  height: 1px;
  background: var(--rule);
}

.index-row .dose {
  font-family: var(--font-mono);
  font-size: var(--t-small);
  font-weight: 500;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--accent-text);
  white-space: nowrap;
}

.index-row .note {
  grid-column: 1 / -1;
  color: var(--ink-3);
  font-size: var(--t-small);
  max-width: 52ch;
}

/* ---------- The dark act ---------- */

.act--dark {
  /* Scoped token override rather than a media query: this section is dark
     because of what it depicts, not because of a system preference. */
  --paper: #14111f;
  --panel: #1e1a2e;
  --ink: #f3f0fb;
  --ink-2: #a9a2c4;
  --ink-3: #8b83a8;
  --rule: rgba(243, 240, 251, 0.14);
  --rule-strong: rgba(243, 240, 251, 0.28);
  /* On the dark field the brand lavender is finally usable at full strength --
     7.9:1 as text, and any fill made of it is unmistakable against #14111f --
     so the three light-field stops collapse back into one colour. This is the
     only place on the page the brand hue appears undiluted, which is most of
     why the dark act reads as an event rather than as a colour inversion. */
  --accent: #ab9ff2;
  --accent-hover: #bcb0f5;
  --accent-text: #ab9ff2;

  background: var(--paper);
  color: var(--ink-2);
  border-top-color: transparent;
}

.band {
  grid-template-columns: minmax(0, 1fr) minmax(240px, 300px);
  gap: var(--s9);
  align-items: center;
}

@media (min-width: 901px) {
  .band {
    display: grid;
  }
}

.band-copy {
  display: grid;
  gap: var(--s4);
  max-width: 52ch;
}

.intermission-preview {
  display: grid;
  gap: var(--s4);
}

.intermission-page {
  position: relative;
  min-height: 210px;
  overflow: hidden;
  border-radius: var(--r-panel);
  background: linear-gradient(var(--demo-top), var(--demo-bot));
}

.intermission-page span {
  position: relative;
  z-index: 1;
  display: block;
  width: 70%;
  height: 10px;
  margin: var(--s5) auto 0;
  border-radius: 999px;
  background: var(--on-demo);
}

.intermission-page img {
  position: absolute;
  left: 50%;
  bottom: var(--s5);
  z-index: 3;
  width: 92px;
  transform: translateX(-50%);
}

/* The last warm colours on the page, and deliberately so: this is coffee, not
   chrome. The violet field is what lets them stay warm without the page
   reading as two competing temperatures -- against the old terracotta UI these
   browns were just more of the same. Do not violet-shift the coffee. */
.intermission-coffee {
  position: absolute;
  inset: 0;
  z-index: 2;
  background:
    linear-gradient(rgba(123, 62, 25, 0.2), rgba(55, 24, 12, 0.72)),
    rgba(79, 36, 16, 0.7);
  transform: scaleY(0);
  transform-origin: center bottom;
  animation: siteCoffeeRise 4s ease-in-out infinite;
}

.intermission-coffee::before {
  content: "";
  position: absolute;
  left: -10%;
  right: -10%;
  top: -14px;
  height: 30px;
  border-radius: 50%;
  background: rgba(255, 190, 116, 0.28);
}

.intermission-preview figcaption {
  display: grid;
  gap: var(--s1);
}

.intermission-preview strong {
  color: var(--ink);
  font-size: var(--t-small);
  font-weight: 500;
  letter-spacing: -0.01em;
}

.intermission-preview small {
  color: var(--ink-3);
  font-size: 13px;
  line-height: 1.45;
}

@keyframes siteCoffeeRise {
  0%, 20% { transform: scaleY(0.08); }
  70%, 100% { transform: scaleY(1); }
}

/* ---------- Numbered rows ---------- */
/* Replaces .card-grid. Same content, no boxes: a numeral, a heading and a
   measure, separated by hairlines. Three bordered cards in a row all compete
   for the same attention; three rows read in order, which is how the content
   is actually meant to be taken. */

.rows {
  display: grid;
}

.row {
  display: grid;
  grid-template-columns: 3rem minmax(0, 0.8fr) minmax(0, 1.4fr);
  gap: var(--s6);
  align-items: start;
  padding-block: var(--s7);
  border-top: 1px solid var(--rule);
}

.row:last-child {
  border-bottom: 1px solid var(--rule);
}

.row p {
  font-size: var(--t-small);
  color: var(--ink-3);
  max-width: 56ch;
}

/* Rows carried no prose links until the privacy page, so these were falling
   through to the UA's blue. Ink with a muted underline instead: on this palette
   a saturated blue is the loudest thing in the column, and the surrounding text
   is already --ink-3. */
.row p a {
  color: var(--ink-2);
  text-decoration: underline;
  text-decoration-color: var(--rule);
  text-underline-offset: 0.2em;
  transition: text-decoration-color var(--dur) var(--ease);
}

.row p a:hover {
  text-decoration-color: currentColor;
}

/* A row is a three-column grid, so a row with two paragraphs puts the second
   one in column 1 -- a 3rem gutter, which wraps it to roughly one word per
   line. Wrapping the prose keeps it a single grid item however many paragraphs
   it holds. The privacy page needs this; index.html's rows are all one
   paragraph and are unaffected. */
.row-prose {
  display: grid;
  gap: var(--s4);
}

.row-prose p {
  margin: 0;
}

/* ---------- Install steps ---------- */

.steps {
  display: grid;
  padding: 0;
  list-style: none;
  counter-reset: step;
}

.steps li {
  display: grid;
  grid-template-columns: 3rem minmax(0, 1fr);
  gap: var(--s6);
  align-items: baseline;
  padding-block: var(--s5);
  border-top: 1px solid var(--rule);
  color: var(--ink);
  font-size: var(--t-body);
  counter-increment: step;
}

.steps li:last-child {
  border-bottom: 1px solid var(--rule);
}

/* A mono numeral in the same column as .row-num, so steps and rows share one
   spine down the page. The old circled counter was a third border idiom. */
.steps li::before {
  content: "0" counter(step);
  font-family: var(--font-mono);
  font-size: var(--t-label);
  font-weight: 500;
  letter-spacing: 0.12em;
  color: var(--ink-3);
}

/* ---------- Notes + footer ---------- */

.notes {
  gap: var(--s4);
  max-width: 68ch;
}

.notes p {
  font-size: var(--t-small);
  color: var(--ink-3);
}

.install-close {
  display: flex;
  align-items: center;
  gap: var(--s4);
  margin-top: var(--s8);
  padding-top: var(--s6);
  border-top: 1px solid var(--rule);
}

/* Height drives, width follows. This was a 56x56 square, which is fine for the
   nav and footer marks (those use icon-48.png, which really is square) but not
   for the buddy art: it is 200x257, so forcing it into a square squashed the cat
   to 78% of her height and she read as short and wide. Setting one dimension is
   safer than object-fit here, because it leaves no phantom box for the flex gap
   to measure against. */
.install-close img {
  height: 56px;
  width: auto;
}

.install-close p {
  display: grid;
  gap: var(--s1);
  margin: 0;
}

.install-close strong {
  color: var(--ink);
  font-size: var(--t-small);
  font-weight: 500;
}

.install-close span {
  color: var(--ink-3);
  font-size: var(--t-small);
}

.site-footer {
  padding-block: var(--s8) var(--s9);
  border-top: 1px solid var(--rule);
}

.site-footer .wrap {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s4);
}

.site-footer p {
  font-size: var(--t-small);
  color: var(--ink-3);
}

.site-footer small {
  flex-basis: 100%;
  margin-top: var(--s5);
  color: var(--ink-3);
  font-size: 12px;
  opacity: 0.75;
}

.footer-mark {
  display: flex;
  align-items: center;
  gap: var(--s3);
  color: var(--ink);
  font-size: var(--t-small);
  font-weight: 500;
  letter-spacing: -0.01em;
}

.footer-mark img {
  width: 22px;
  height: 22px;
}

/* ---------- Scroll reveal ---------- */
/* The hidden state is applied by script.js, never here -- as a static rule it
   would leave the whole page blank for anyone without JS. See script.js. */

[data-reveal].is-hidden {
  opacity: 0;
  transform: translateY(12px);
}

[data-reveal].is-revealed {
  opacity: 1;
  transform: none;
  transition:
    opacity 600ms var(--ease),
    transform 600ms var(--ease);
}

/* ---------- Responsive ---------- */

@media (max-width: 900px) {
  .wrap {
    width: min(1120px, calc(100vw - var(--s6)));
  }

  .act-grid,
  .hero-lower,
  .band {
    grid-template-columns: 1fr;
  }

  .act-grid {
    gap: var(--s6);
  }

  /* Stacked, the label sits directly above its content -- sticking it would
     park it over the heading it introduces. */
  .act-label {
    position: static;
    grid-auto-flow: column;
    justify-content: start;
    gap: var(--s3);
  }

  .hero-lower {
    gap: var(--s8);
    align-items: stretch;
  }

  .browser-page {
    min-height: 340px;
  }

  .row {
    grid-template-columns: 3rem minmax(0, 1fr);
  }

  .row p {
    grid-column: 2;
  }
}

@media (max-width: 640px) {
  /* Five links will not fit beside the mark, and a hamburger for a six-section
     page is more machinery than the page is worth. The spine numerals carry the
     structure once you are scrolling. */
  .nav-inner {
    position: relative;
  }

  .nav-toggle {
    position: absolute;
    top: 8px;
    right: 0;
    z-index: 1;
    display: inline-flex;
  }

  .nav-links {
    position: absolute;
    top: calc(100% + 1px);
    right: 0;
    left: 0;
    display: none;
    flex-direction: column;
    gap: 0;
    padding: var(--s2) var(--s6) var(--s4);
    border-bottom: 1px solid var(--rule);
    background: var(--paper);
  }

  .nav-links.is-open {
    display: flex;
  }

  .nav-links a {
    display: flex;
    align-items: center;
    min-height: 44px;
  }

  .hero-actions .btn {
    flex: 1 1 100%;
    justify-content: center;
  }

  .index-row {
    grid-template-columns: 1fr;
  }

  /* With one column the leader has nothing to lead to. */
  .index-row dt::after {
    display: none;
  }

  .row,
  .steps li {
    grid-template-columns: 1fr;
    gap: var(--s2);
  }

  .row p {
    grid-column: 1;
  }

  .install-close {
    align-items: flex-start;
  }
}

/* Mirrors the guard the extension already ships (src/popup.css:472-483). The
   coffee rise is a decorative 4s loop; it must stop, not just shorten. */
@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }

  .intermission-coffee {
    animation: none;
    transform: scaleY(0.72);
  }

  /* Belt and braces: script.js already skips the reveal entirely under reduced
     motion, but if it ever failed to run, content must not be left invisible. */
  [data-reveal].is-hidden {
    opacity: 1;
    transform: none;
  }
}
