/* ===========================================================================
   **What a control does, and what a link looks like — said once for all four
   pages.**

   The front page's stylesheet came from the design and the other three are
   written by hand, so these rules lived in whichever sheet needed them first.
   Then the three pages were given the front page's theme button — the same
   markup, sun and moon and all — and it arrived without its behaviour, because
   the rules were in the sheet those pages do not load. A control that looks
   identical and answers differently is worse than two that look different.
   =========================================================================== */
a {
  /* **The look of the two in the footer**, which is the one asked for: the muted
     text colour at rest, the accent under the pointer, and no rule under it
     either way. A link that carries its own colour in its `style` attribute —
     the bar's items, a card's headline — is a control rather than a word in a
     sentence and keeps it; this is the rule for the words. */
  color: var(--text-muted);
  text-decoration: none;
  border-radius: 3px;
  transition: color var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out);
}
a:hover { color: var(--accent); }
/* **The pairing, not just the colour.** Those two words read as links because
   the sentence around them is *faint* and they are *muted* — a two-tone the
   footer has and a muted paragraph does not, which is why a link inside one came
   out the exact colour of its own sentence and stopped looking like a link at
   all. So wherever a sentence holds a word-link, the sentence takes the fainter
   tone and the link keeps the muted one: the same two colours, in the same
   relation, everywhere. A sentence with no link in it is untouched.

   `!important` because the tone it is replacing is written in the element's
   `style` attribute, which no rule beats otherwise — the same reason the hovers
   above carry it. */
p:has(> a), span:has(> a), figcaption:has(> a), li:has(> a) { color: var(--text-faint) !important; }

/* ---- what the prototype wrote inline ----
   **`!important`, and it is not a shortcut.** The design keeps an element's
   resting look in its `style` attribute — the medium gives it nowhere else — and
   applied a hover by *writing into that same attribute*. Lifted out to a class,
   the rule then loses to the inline declaration it is meant to override, which
   is silent: the page looks right and nothing answers the pointer. Reported as
   "the buttons aren't animated at all". */
/* `.hv1` is gone. It existed to beat the **inline** styles on the front page's
   own nav links with `!important`, and that header was replaced on 2026-09-12
   by the shared `.top` markup every other page uses — so the ordinary rule
   applies and there is nothing left to override. A hover class that nothing
   wears is what `tests/site.mjs` calls a hover the design drew and nothing
   answers; it caught this the moment the markup changed. */
.hv2:hover { background: var(--control-hover) !important; }
.hv3:hover { background: var(--accent-hover) !important; }
/* **A card lifts; it does not draw a line round itself.** The design tinted the
   border towards the accent as well, and on a grid of twelve that is twelve
   outlines chasing the pointer. The lift and the shadow already say "this one",
   and they say it the way the app does. */
.hv4:hover { top: -2px !important; box-shadow: var(--shadow-md) !important; }
.hv5:hover { filter: brightness(0.96) !important; }
.hv6:hover { color: var(--accent) !important; }
/* Words that are links: the colour eases, as every link's does (`a`, above) —
   named on the classes so nothing depends on which tag wears them. */
.hv6, .dl-main { transition: color var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out); }

/* ---- and a control moves the way the app's controls move ----
   The design gave every control `--dur-instant` (0.09s) and a hover that changes
   a colour and nothing else. The app's `.ui-btn` takes `--dur-fast`, eases its
   transform on `--ease-spring`, lifts a pixel and casts `--shadow-sm`, and
   presses to `translateY(0) scale(0.97)`. At 0.09s with no lift the difference
   is not subtle — reported as "the buttons still don't animate like they do in
   the app". `.hv4` is a card and keeps its own two-pixel lift; `.hv6` is a word
   in a sentence and lifts nothing. */
/* `.ac*` is always worn beside an `.hv*`; named here so the press is covered
   by the list it uses, not by the neighbour it happens to have. */
.hv2, .hv3, .hv4, .hv5, .ac1, .ac2, .ac3, .ac4 {
  transition:
    background var(--dur-fast) var(--ease-out),
    border-color var(--dur-fast) var(--ease-out),
    box-shadow var(--dur-fast) var(--ease-out),
    color var(--dur-fast) var(--ease-out),
    filter var(--dur-fast) var(--ease-out),
    opacity var(--dur-fast) var(--ease-out),
    top var(--dur-fast) var(--ease-spring) !important;
}
/* **`top`, not a transform** — the app's correction, made here too: a
   transformed control is the frame its own light is fixed to, so the one being
   pointed at was the one that did not light. */
.hv2:hover, .hv3:hover, .hv5:hover {
  top: -1px !important;
  box-shadow: var(--control-lift) !important;
}
/* The press comes last, because `:active` is also `:hover` and the lift must
   give way to it. The scales are the design's own; the app presses to 0.97 and
   these sit either side of it. */
/* The press is the control sitting back down, as in the app — a scale is a
   transform, and would take the light off the button for as long as it is held. */
.ac1:active, .ac2:active, .ac3:active, .ac4:active { top: 0 !important; box-shadow: none !important; }

/* ---- the whole download card is the download ----
   Reported: it should be clickable all over. The headline link stretches an
   invisible box over its card, which is the pattern that keeps one anchor and
   one accessible name rather than wrapping the card in a second link. The
   format links inside — `.msi`, `.deb`, `.rpm` — are lifted above it so they
   still answer for themselves. */
.dl-card { position: relative; cursor: pointer; }
.dl-main::after { content: ""; position: absolute; inset: 0; border-radius: inherit; }
.dl-card a:not(.dl-main), .dl-card span, .dl-card em { position: relative; z-index: 1; }
.dl-card a:not(.dl-main) { pointer-events: auto; }
/* The card's own hover answers wherever the pointer is on it, because the whole
   of it is now the target. */
.dl-card:hover .dl-main { color: var(--accent) !important; }

/* ---- moving between pages ----
   **A move between two pages is a move, not a reload.** The page fades up as it
   arrives and fades down as it leaves, so the four of them read as one place
   rather than four documents that replace each other.

   The arrival is a CSS **animation**, not a transition, because it has to be in
   force at the first paint: a starting state written by script arrives after the
   page has already been drawn at full strength, which is a flash and then a
   fade. And its fill is `backwards` rather than `both` — a filled animation owns
   the property for ever, and the leaving transition below is on the same one.

   Only the leaving half needs script, and it is additive: with none, a link is
   an ordinary link. */
@keyframes page-in { from { opacity: 0; } to { opacity: 1; } }
body {
  animation: page-in var(--dur-base) var(--ease-out) backwards;
}
:root[data-leaving] body {
  opacity: 0;
  transition: opacity 0.16s var(--ease-in);
}

/**
 * **The documentation's search, wearing the command palette's clothes.**
 *
 * Asked for directly: *"I want this search to be styled, just like the search in
 * our app."* It already *behaves* like it — the matcher is the app's own,
 * vendored — so looking like something else was the odd part.
 *
 * The values are the palette's from `src/styles/app.css`, and the tokens are the
 * same tokens, because `site/tokens.css` is generated from the app's. What is
 * deliberately **not** copied is the shell's arrival animation and its scrim: a
 * palette is a thing that opens over a document, and this one is part of a page
 * and never goes away.
 */
.doc-palette {
  border-radius: 14px;
  overflow: hidden;
  /* The settings pane's surface — glass at *reading* density, which is what a
     panel full of rows is, rather than the lighter chrome density. */
  background: var(--glass-sheen), var(--glass-grain), var(--glass-body-reading);
  backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
  border: 1px solid var(--glass-rim);
  box-shadow: var(--glass-shadow), inset 0 1px 0 var(--glass-lip), inset 0 -1px 0 var(--glass-foot);
}
.doc-palette-head {
  display: flex;
  align-items: center;
  gap: 10px;
  height: 58px;
  padding: 0 16px;
  border-bottom: 1px solid var(--glass-edge);
}
.doc-palette-icon { display: flex; flex: 0 0 auto; color: var(--text-faint); }
.doc-palette-field {
  flex: 1 1 auto;
  min-width: 0;
  padding: 0;
  border: 0;
  outline: none;
  background: transparent;
  color: var(--text);
  font-family: inherit;
  font-size: 15.5px;
}
/* The head carries the border; an empty list must not draw a second one. */
.doc-palette-list { list-style: none; margin: 0; padding: 0; }
.doc-palette-list:not(:empty) { padding: 8px; }

.doc-palette-group {
  padding: 3px 10px 5px;
  margin: 0;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--text-faint);
}
.doc-palette-group + .doc-palette-row { margin-top: 0; }
li + .doc-palette-group { margin-top: 10px; }

/**
 * **One weight, always.** A row that goes 500 → 700 under the pointer re-lays
 * its own text out and appears to jump; the tint carries it instead. Only
 * `background` and `color` transition, and quickly — a row under a moving
 * pointer that eases slowly is a row that lags.
 */
.doc-palette-row {
  display: flex;
  align-items: center;
  gap: 9px;
  width: 100%;
  min-height: 36px;
  padding: 0 10px;
  box-sizing: border-box;
  border-radius: var(--radius-control);
  color: var(--text);
  font-size: 13.5px;
  font-weight: 500;
  text-align: start;
  text-decoration: none;
  transition: background 0.09s var(--ease-out), color 0.09s var(--ease-out);
}
a.doc-palette-row:hover, a.doc-palette-row:focus-visible { background: var(--control-hover); }
.doc-palette-where { color: var(--text-faint); font-size: 12.5px; }
.doc-palette-keys { margin-inline-start: auto; }
.doc-palette-none { padding: 10px 12px; color: var(--text-faint); font-size: 13.5px; }

/* ── The search in the top bar ──────────────────────────────────────────────

   A nav item until it is pressed, then a field, then the app's own palette
   below it. The three states are one element so the change reads as *this
   thing becoming that thing* rather than one control being swapped for
   another — which is the whole of what was asked for.

   **The expansion is a spring.** `--ease-spring` overshoots a little and
   settles; an ease would arrive and stop, which reads as a panel appearing
   rather than a control opening. Only `width` and `opacity` are animated, and
   deliberately nothing else: a colour, a shadow or a filter in here takes the
   whole animation off the compositor, which this project has measured as the
   difference between 9ms and 30ms between painted frames. */
.topsearch {
  position: relative;
  display: flex;
  align-items: center;
  /* **The edge is a shadow, and only its colour changes.** Opening used to
     *add* a border — a pixel's shift and a box appearing between two frames.
     An inset shadow eases the same way and takes no room at all. */
  border-radius: var(--radius-control);
  box-shadow: inset 0 0 0 1px transparent;
  transition: background var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out),
              padding var(--dur-fast) var(--ease-out);
}
.topsearch-tab {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  cursor: pointer;
  font: inherit;
  font-size: 14px;
  color: var(--text-muted);
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--radius-control);
  padding: 6px 10px;
  transition: background var(--dur-instant) var(--ease-out), color var(--dur-instant) var(--ease-out);
}
.topsearch-tab:hover { color: var(--text); background: color-mix(in srgb, var(--text) 6%, transparent); }
.topsearch-icon { display: flex; }

/* The word goes when the field arrives — the icon stays, so there is something
   that did not move for the eye to follow across the change. */
.topsearch-word {
  max-width: 6em;
  overflow: hidden;
  white-space: nowrap;
  transition: max-width var(--dur-base) var(--ease-spring), opacity var(--dur-fast) var(--ease-out);
}
.topsearch.open .topsearch-word { max-width: 0; opacity: 0; }

.topsearch-field {
  width: 0;
  opacity: 0;
  padding: 0;
  font: inherit;
  font-size: 14px;
  color: var(--text);
  background: transparent;
  border: 0;
  outline: none;
  transition: width var(--dur-base) var(--ease-spring), opacity var(--dur-fast) var(--ease-out),
    padding var(--dur-base) var(--ease-spring);
}
.topsearch.open .topsearch-field {
  width: 15rem;
  opacity: 1;
  padding: 6px 4px;
}
/* The whole control takes the field's rim once it is one, rather than the field
   growing a box of its own inside the tab. */
.topsearch.open {
  background: var(--surface);
  box-shadow: inset 0 0 0 1px var(--border);
  padding-inline-end: 8px;
}
.topsearch.open .topsearch-tab { border-color: transparent; }
.topsearch.open .topsearch-tab:hover { background: transparent; }

/* The results, in the app's own palette. It hangs from the control rather than
   pushing the bar around — the bar is sticky and a nav that changes height as
   you type is a page that jumps under the pointer. */
.topsearch-panel {
  position: absolute;
  inset-inline-end: 0;
  top: calc(100% + 8px);
  width: min(30rem, 86vw);
  max-height: min(28rem, 70vh);
  overflow-y: auto;
  z-index: 50;
}
.topsearch-panel[hidden] { display: none; }
.topsearch.showing .topsearch-panel {
  animation: topsearch-drop var(--dur-base) var(--ease-spring) both;
}
@keyframes topsearch-drop {
  from { opacity: 0; transform: translateY(-6px); }
  to   { opacity: 1; transform: translateY(0); }
}

@media (prefers-reduced-motion: reduce) {
  .topsearch-word, .topsearch-field { transition: none; }
  .topsearch.showing .topsearch-panel { animation: none; }
}

/* Narrow: the word goes first, and the field takes what is left rather than
   pushing the language and theme buttons off the bar. */
@media (max-width: 640px) {
  .topsearch-word { display: none; }
  .topsearch.open .topsearch-field { width: 9rem; }
}

/* ── The bar every page wears ───────────────────────────────────────────────

   Here rather than in `pages.css` because the front page does not load that
   file, and its header is the same markup as everybody else's. */

.wrap { max-width: var(--wrap); margin: 0 auto; padding: 0 28px; }

/* **A backdrop here is one still thing, not six that follow the scroll.** The
   front page is a display and earns its six motifs; these are documents, so each
   has a single wash and a single glyph, the same construction on all three and
   only the glyph differing. The content is lifted above it. */
main, header, footer { position: relative; z-index: 1; }
/* **And it scrolls away with the page.** Pinned to the viewport, the light from
   above followed the reader down the document and stopped being *from above* at
   all — it was simply a tint on the screen. Absolute, drawn across the first
   screenful of the *page*, it is where it says it is and the reader leaves it
   behind. `body` is the box it is measured against. */
body { position: relative; }

/* ---- The bar ----
   Glass, like the app's: the page runs *under* it, because a bar with only flat
   colour behind it has nothing to frost. */
.top {
  position: sticky;
  top: 0;
  z-index: 40;
  /* **The app's bar, in the app's material** (2026-09-30): the same glass
     body, blur, saturation and lip, from the same tokens, rather than numbers
     of the site's own that had drifted from them. */
  background: var(--glass-body);
  backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
  border-bottom: 1px solid color-mix(in srgb, var(--text) 9%, transparent);
  box-shadow: var(--shadow-sm), inset 0 1px 0 var(--glass-lip);
}
.top .wrap { display: flex; align-items: center; gap: 20px; padding-block: 12px; }
.brand {
  display: inline-flex; align-items: center; gap: 10px;
  font-weight: 700; letter-spacing: -0.01em; text-decoration: none; color: var(--text);
}
.mark {
  display: grid; place-items: center;
  width: 26px; height: 26px; border-radius: 7px;
  background: linear-gradient(140deg, var(--math-accent), var(--accent));
  color: #fff; font-size: 12px; font-weight: 700;
}
.top nav { margin-inline-start: auto; display: flex; align-items: center; gap: 6px; }
.top nav a, .chip {
  color: var(--text-muted); text-decoration: none; font-size: 14px;
  padding: 6px 10px; border-radius: var(--radius-control);
  /* The app's `.ui-btn` motion, said once for both: `--dur-fast`, and the
     transform on `--ease-spring`. Without the transform in this list the press
     below snapped, which is the whole of what "it does not animate like the app"
     means. */
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out),
              box-shadow var(--dur-fast) var(--ease-out), top var(--dur-fast) var(--ease-spring);
}
.top nav a:hover, .chip:hover {
  background: var(--control-hover); color: var(--text);
  top: -1px; box-shadow: var(--control-lift);
}
.top nav a:active, .chip:active { top: 0; box-shadow: none; }

/* ── On a phone ─────────────────────────────────────────────────────────────

   The site had **no media queries at all** until 2026-09-12, and measuring it
   said so plainly: every page overflowed its viewport by 260–335px at every
   phone width tried, and the cause was the same on all of them — six nav links
   and three controls in a row that could not wrap, 496px wide in a 390px
   window.

   Two things had to happen before this block could be written. The front page
   carried its **own** header in inline styles, structurally different from the
   other five, so no stylesheet could reach it; it uses the shared `.top` now.
   And a horizontal overflow is not a cosmetic fault — it is the whole page
   sliding under the thumb, every paragraph cut off at the right, on a device
   where there is no scrollbar to explain why. */

/* Nothing may push the page sideways. A stray wide element is a bug to fix, not
   a thing to hide — but the page must not be unusable while one exists. */
/* **`clip`, not `hidden`.** `hidden` makes the body a scroll container, and a
   sticky bar then sticks to the body rather than the window — so the bar, which
   is written `position: sticky`, scrolled away with the page on every page
   (found 2026-09-30 comparing the site's bar with the app's, which never
   leaves). `clip` stops the sideways slide and creates no scroll container. */
html, body { overflow-x: clip; }

/* Long things that have no spaces: an address, a command, a Hebrew compound. */
p, li, dd, dt, h1, h2, h3, td, th { overflow-wrap: break-word; }
pre, code { overflow-wrap: normal; }
pre { overflow-x: auto; }

@media (max-width: 760px) {
  .wrap { padding: 0 16px; }

  /**
   * **The bar becomes two rows: the name, then everything you can press.**
   *
   * The links and the controls share one strip that scrolls sideways rather
   * than wrapping into three lines — a sticky header that takes a third of a
   * phone screen is worse than one you have to nudge. The controls come first
   * (`order`) so search, language and theme are the half already on screen,
   * and the links are what you scroll to.
   */
  .top .wrap {
    flex-wrap: wrap;
    gap: 4px 12px;
    padding-block: 8px;
  }
  .top nav {
    width: 100%;
    margin-inline-start: 0;
    gap: 4px;
    overflow-x: auto;
    /* No scrollbar track on a strip this short; the overflow is discoverable
       because the last item is deliberately half-cut. */
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
  }
  .top nav::-webkit-scrollbar { display: none; }
  .top nav > * { flex: 0 0 auto; }
  .topsearch, .top nav [data-act] { order: -1; }

  /* **Forty pixels, because a finger is not a pointer.** The links were 26px
     tall and the search tab 29px; both are comfortably missable. */
  .top nav a, .top nav button { min-height: 40px; display: inline-flex; align-items: center; }

  /* Expanded, the field takes the strip rather than a fixed width — at 15rem it
     pushed the language and theme buttons out of reach. */
  .topsearch.open .topsearch-field { width: min(52vw, 13rem); }
  /* `top` is written by `search.js`, which measures where the bar actually
     ends — it is two rows at some widths and one at others. */
  .topsearch-panel {
    position: fixed;
    inset-inline: 12px;
    width: auto;
    max-height: 60vh;
  }

  /* A heading set for a 1180px column is a heading that breaks mid-word here. */
  h1 { font-size: clamp(26px, 7.5vw, 34px); }
  h2 { font-size: clamp(20px, 5.5vw, 26px); }

  /* The reference is a two-column grid of 247 commands; one column on a phone. */
  .dl { grid-template-columns: 1fr; gap: 2px 0; }
  .dl dd { margin-bottom: 10px; }

  /* Amount chips wrap rather than run off the edge. */
  .sup-amounts { gap: 6px; }
  .sup-own { width: 100%; }
}

/* Narrower still — the 320px phones that are still in use. */
@media (max-width: 380px) {
  .wrap { padding: 0 12px; }
  .top .brand { font-size: 14px; }
}

/**
 * **A finger is not a pointer, whatever the width.**
 *
 * The tap-target floor was written into the phone block and a tablet is not a
 * phone: at 768px the nav links measured 35px, below the 40 a thumb needs,
 * because 768 is outside `max-width: 760`. Width was the wrong question — what
 * decides is whether the thing pointing is a finger.
 *
 * Kept off the desktop deliberately: 35px is comfortable with a mouse, and
 * growing every control on a pointer device would loosen a bar that is
 * deliberately quiet.
 */
@media (hover: none) and (pointer: coarse) {
  .top nav a,
  .top nav button,
  .top .brand {
    min-height: 40px;
    display: inline-flex;
    align-items: center;
  }
  /* Links in running text too — a footnote reference or a reference to another
     page is as hard to hit as a button is. */
  main a { padding-block: 2px; }
}

/**
 * **The recreation of the app, on a phone.**
 *
 * It is a picture of the program — a toolbar, an outline, a page, a status bar,
 * animated. Its narrowest honest layout is about 520px, and at 390px it was not
 * shrinking but **clipping**: the outline took half the width and the document
 * was a sliver reading "Intr". Nothing scrolled sideways, because the page
 * forbids that, so it simply looked broken.
 *
 * **Scaled rather than rearranged**, and that is the whole argument. A real
 * mobile layout for it would be a second design to keep in step with the first,
 * and it would stop being a picture of *this* program. Looking at it from
 * further away is what a reader does with a photograph of a screen.
 *
 * `zoom` rather than `transform: scale()` because zoom **takes part in layout**:
 * a transformed box still occupies its full height, so the caption below would
 * sit 300px down a column of nothing. (The editor may not use `zoom` for its own
 * sheet — that is measured and recorded — but this is one static block sized
 * once, not a document relaid out on every keystroke.)
 */
/**
 * **Stepped, because `vw` is not resolved inside `zoom`.**
 *
 * The obvious spelling is `zoom: min(1, calc((100vw - 34px) / 520))`, which
 * would follow the width exactly. Measured in WebKit: `zoom: 0.6` shrinks a
 * 520px box to 312, `zoom: calc(356 / 520)` shrinks it to 356 — and the version
 * with `100vw` in it leaves the box at **520**, silently. A plain number works
 * and so does arithmetic on plain numbers; a viewport unit does not reach it.
 *
 * So the steps are written out. Each is the ratio for the *narrowest* width in
 * its range, so the picture is never wider than the column — a little small at
 * the top of a range is invisible, and a pixel too wide is the clipping this
 * exists to end. The design width is 520 and the column is the viewport less
 * 32px of padding.
 */
.app-sim { min-width: 520px; }
@media (max-width: 552px) { .app-sim { zoom: 0.86; } }
@media (max-width: 480px) { .app-sim { zoom: 0.76; } }
@media (max-width: 430px) { .app-sim { zoom: 0.68; } }
@media (max-width: 390px) { .app-sim { zoom: 0.63; } }
@media (max-width: 360px) { .app-sim { zoom: 0.55; } }
/* One more, because the picture's widest row runs a little past its own box —
   measured, not assumed: at 320px the frame fitted and a toolbar inside it stood
   9px proud. */
@media (max-width: 340px) { .app-sim { zoom: 0.48; } }

/* ===========================================================================
   **The light that follows the pointer — the app's Reveal Highlight, here.**

   Until 2026-09-30 the site's controls answered a hover with a tint and a lift,
   and the app's with light: a **rim** round every control in the piece of the
   page the pointer is in, so a group shows its structure as the hand
   approaches, and a **wash** on the one under it. `src/styles/app.css` is the
   original, with the reasoning for each rule; this is the same thing drawn on
   the site's controls, from the same tokens (`tokens.css` is generated from the
   app's). `reveal.js` says where the pointer is and arms one host with
   `data-lit` — not `data-reveal`, which the site already uses for its scroll-in
   entrance.

   Three things are rules, not details, exactly as in the app: `background-
   attachment: fixed` puts one pair of client pixels right for every element;
   `background-position` is never transitioned, so the light never lags the
   hand; and nothing lit is transformed, or it becomes the frame its own light is
   fixed to — which is why every lift above is `top`.
   =========================================================================== */
:root {
  /* Where the light is at full strength, and how long leaving a host takes —
     the app's defaults, declared here because the site's tokens are generated
     from the app's `:root` and these live on its surfaces instead. */
  --rv-k: 1;
  --rv-fade: 0.5s;
}
[data-lit] {
  --rv-attach: fixed;
  --rv-a: var(--rv-k, 1);
  --rv-fade: var(--dur-base);
}
/* The bar is wide: the app's toolbar reach. */
header.top { --reveal-edge-span: 320px; }

/* Controls (lift, rim, wash), cards (their own lift, rim, wash) and rows (rim
   and wash). Positioned for the pseudo-elements, and resting at `top: 0` so the
   lift has a length to move from — from `auto` it would jump. */
.hv2, .hv3, .hv5, .top nav a, .chip, .topsearch-tab, .btn, .sup-chip,
.hv4, .doc-palette-row {
  position: relative;
  top: 0;
}

/* **The rim**, outside the box by its own width, where Fluent draws it. */
.hv2::before, .hv3::before, .hv5::before, .top nav a::before, .chip::before,
.topsearch-tab::before, .btn::before, .sup-chip::before, .hv4::before, .doc-palette-row::before {
  content: "";
  position: absolute;
  inset: calc(-1 * var(--reveal-ring));
  border-radius: var(--rv-ring-r);
  padding: var(--reveal-ring);
  pointer-events: none;
  background-image: var(--rv-ring-img);
  background-repeat: no-repeat;
  background-attachment: var(--rv-attach);
  background-size: var(--reveal-edge-span) var(--reveal-edge-span);
  background-position:
    calc(var(--rv-x) - var(--reveal-edge-span) / 2)
    calc(var(--rv-y) - var(--reveal-edge-span) / 2);
  -webkit-mask-image: linear-gradient(#000, #000), linear-gradient(#000, #000);
  -webkit-mask-clip: content-box, border-box;
  -webkit-mask-composite: xor;
  mask-image: linear-gradient(#000, #000), linear-gradient(#000, #000);
  mask-clip: content-box, border-box;
  mask-composite: exclude;
  opacity: var(--rv-a, 0);
  /* Leaving a host, the light goes over half a second rather than at once. */
  transition: opacity var(--rv-fade, 0.5s) var(--ease-out);
}
/* A card's corners are its own, not a control's. */
.hv4::before { border-radius: calc(var(--radius-md) + var(--reveal-ring)); }

/* **The wash**, on the one under the pointer, fading in rather than appearing. */
.hv2::after, .hv3::after, .hv5::after, .top nav a::after, .chip::after,
.topsearch-tab::after, .btn::after, .sup-chip::after, .hv4::after, .doc-palette-row::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  background-image: var(--rv-glow-img);
  background-repeat: no-repeat;
  background-attachment: var(--rv-attach);
  background-size: var(--reveal-glow-span) var(--reveal-glow-span);
  background-position:
    calc(var(--rv-x) - var(--reveal-glow-span) / 2)
    calc(var(--rv-y) - var(--reveal-glow-span) / 2);
  opacity: 0;
  transition: opacity var(--dur-instant) var(--ease-out);
}
.hv2:hover::after, .hv3:hover::after, .hv5:hover::after, .top nav a:hover::after, .chip:hover::after,
.topsearch-tab:hover::after, .btn:hover:not(:disabled)::after, .sup-chip:hover::after, .hv4:hover::after,
.doc-palette-row:hover::after {
  opacity: 1;
}
/* On an accent-filled control the light is white — the image, not the colour,
   because a custom property substitutes where it is declared. */
.hv3, .btn.primary, .sup-chip.on { --rv-glow-img: var(--rv-glow-white); }

/* **Where motion is not wanted, the light is not either** — the plain tint
   stays, so a control still plainly answers the pointer. */
@media (prefers-reduced-motion: reduce) {
  [data-lit] { --rv-a: 0; }
  body { --reveal-glow: transparent; }
}

/* The small controls that answered a hover with nothing but a colour now ease
   it, and lift like the rest. */
.topsearch-tab, .sup-chip, .doc-palette-row {
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out),
              top var(--dur-fast) var(--ease-spring);
}
.topsearch-tab:hover, .sup-chip:hover { top: -1px; box-shadow: var(--control-lift); }
.topsearch-tab:active, .sup-chip:active { top: 0; box-shadow: none; }

/* **The theme button's two icons, stacked**, so changing theme turns one away
   as the other turns in (`site.js`, `applyTheme`) instead of one vanishing. */
[data-act="toggleTheme"] { display: grid !important; place-items: center; }
[data-act="toggleTheme"] [data-icon] {
  grid-area: 1 / 1;
  transition: opacity var(--dur-slow) var(--ease-out), transform var(--dur-slow) var(--ease-spring);
}
[data-act="toggleTheme"] [data-icon][data-off] { opacity: 0; transform: rotate(-120deg) scale(0.5); }

/* **The view transition the language switch runs** — a plain cross-fade, at
   the pace a pane changes in the app. */
::view-transition-old(root), ::view-transition-new(root) {
  animation-duration: var(--dur-slow);
  animation-timing-function: var(--ease-out);
}

/* **Keyboard focus: the app's one ring.** It was `outline: 2px solid
   var(--focus-ring)` — and `--focus-ring` is a *shadow* (`0 0 0 3px …`), not a
   colour, so the declaration was invalid and focus drew nothing at all on six
   pages; the front page, which does not load that sheet, had no rule to begin
   with. The app draws the ring as a shadow and eases it in; so does this.
   `!important` because much of the site sets its resting shadow inline. */
:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring) !important;
}

/* **Scrollbars: the app's, thin and quiet** — the site had the tokens and no
   rule, so it showed the platform's own, the loudest thing on the page. The
   same values as `src/styles/app.css`. */
* {
  scrollbar-width: thin;
  scrollbar-color: var(--scrollbar) transparent;
}
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background: var(--scrollbar);
  border: 3px solid transparent;
  background-clip: padding-box;
  border-radius: 8px;
}
::-webkit-scrollbar-thumb:hover { background: var(--scrollbar-hover); background-clip: padding-box; }
::-webkit-scrollbar-corner { background: transparent; }

/* **`.field` — anything you type into, drawn as the app draws one** (its
   `.ui-field`): a soft fill with a faint inset and no rim, and the one focus
   ring. The reset and verify forms and the amount on the support page each
   had a bordered box of their own. */
.field {
  font: inherit;
  color: var(--text);
  background: var(--field-bg);
  border: 1px solid transparent;
  box-shadow: var(--field-inset);
  border-radius: var(--radius-control);
  padding: 9px 11px;
  outline: none;
  transition: background var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out);
}
.field:focus, .field:focus-visible { box-shadow: var(--field-inset), var(--focus-ring) !important; }
.btn:disabled { opacity: .55; cursor: default; }
