/*
 * ═══════════════════════════════════════════════════════════════════════
 *  THE NOON THEME STYLESHEET (08-03-PLAN.md Task 2; D-86/D-88/D-93/D-97)
 * ═══════════════════════════════════════════════════════════════════════
 *
 * The INVARIANT half of D-86's split. The token VALUES are dynamic, tiny and
 * uncacheable, so they ship as an inline `:root` block in the server-rendered
 * document. The RULES that consume them — this file — are large, identical for
 * every deployment and perfectly cacheable, so they ship as one static asset
 * fetched once per visitor. One database read either way.
 *
 * ═══════════════════════════════════════════════════════════════════════
 *  ⚠ THERE ARE TWO DIFFERENT KINDS OF ABSENCE. CONFLATING THEM IS THE
 *    EASIEST MISTAKE IN THIS PHASE, AND THE FALLBACKS BELOW INVITE IT.
 * ═══════════════════════════════════════════════════════════════════════
 *
 *   1. NO THEME DOCUMENT AT ALL  →  NO STYLING WHATSOEVER. This file is not
 *      referenced, the token block is not emitted, and the page renders
 *      exactly as it did before Phase 8 — byte for byte. The `<link>` is
 *      CONDITIONAL, which is what makes TOK-04 literally true rather than
 *      approximately true. An absent theme adds ZERO bytes to the document.
 *
 *   2. A THEME DOCUMENT SETTING ONLY SOME KEYS  →  FULLY STYLED, with the
 *      per-key fallbacks below supplying the rest.
 *
 * >>> A READER WHO SEES THE FALLBACKS WILL OTHERWISE CONCLUDE THAT CASE 1 IS
 * >>> COVERED BY THEM TOO, and "simplify" the conditional link away. It is
 * >>> not. The switch is the DOCUMENT's existence; individual keys are
 * >>> optional with sane defaults. Both facts are stated here, adjacent, on
 * >>> purpose.
 *
 * ═══════════════════════════════════════════════════════════════════════
 *  NO FRAMEWORK, NO PREPROCESSOR, NO CSS BUILD STEP — AND THE REASON IS
 *  THE DEPLOY TIER, NOT TASTE
 * ═══════════════════════════════════════════════════════════════════════
 *
 * A pipeline would contradict TOK-02's no-build-step promise directly, and it
 * would add a build dependency to a project whose lowest deploy tier is
 * uploaded over FTP with no shell to run anything on. This file is plain CSS,
 * authored once, served as-is.
 *
 * ═══════════════════════════════════════════════════════════════════════
 *  THIS FILE IS GATED AGAINST THE VOCABULARY IN BOTH DIRECTIONS
 * ═══════════════════════════════════════════════════════════════════════
 *
 * `src/__tests__/theme-stylesheet-coverage.test.ts` asserts SET EQUALITY
 * between `TOKEN_KEYS` and the `var(--noon-…)` references here. A token an
 * agent can set that nothing consumes is a silent no-op — the exact failure
 * the closed vocabulary exists to prevent — and a reference naming no token is
 * a dead default. Both are build failures, so a later edit cannot quietly add
 * either.
 *
 * EVERY TOKEN REFERENCE CARRIES AN EXPLICIT FALLBACK. The three
 * `--noon-stack-*` properties below are the ONE exception and are not tokens
 * at all: they are internal constants defined unconditionally by this same
 * file, so a fallback on them would be unreachable code.
 *
 * ═══════════════════════════════════════════════════════════════════════
 *  ✓ D-97 CLOSED — THE VISIBLE STYLING IS NOW MACHINE-VERIFIED (BTF-02, DOC-03)
 * ═══════════════════════════════════════════════════════════════════════
 *
 * THE COVERAGE GATE STILL DOES NOT PROVE COMPUTED STYLE — AND IT NO LONGER HAS
 * TO CARRY THE VISUAL CLAIM ALONE. As of Phase 16 (BTF-01/BTF-02) a real
 * headless-browser computed-style suite proves the theme's AND the regions'
 * visible styling BY MACHINE, so the styling claim is no longer a human check:
 *
 *   - `src/__tests__/theme-computed-style.test.ts` graduates matrix rows
 *     1, 2, 3, 5: the cascade APPLIES (computed body background = the token),
 *     the D-93 font chain resolves end to end (computed body font-family = the
 *     serif stack literal), per-block computed style (the ProductGrid grid and
 *     the HeroSection CTA accent), and unthemed absence at the computed layer.
 *   - `src/__tests__/region-computed-style.test.ts` graduates row 4: the
 *     region wrappers' visible styling — the sidebar's computed width and the
 *     region layout's computed flex — which is the "regions' visible styling"
 *     clause D-97 and DOC-03 name.
 *
 * >>> THE COEXISTENCE RULE: the browser layer ADDS a stratum, it REPLACES
 * >>> nothing. This gate's FOUR byte/parse properties REMAIN and stay
 * >>> AUTHORITATIVE ABOUT BYTES — the sheet is SERVED (200, CSS content type),
 * >>> it PARSES, every token reference names a real key and sits in a LIVE
 * >>> RULE rather than a comment, and every block selector matches a
 * >>> `data-block` value the app actually emits. If a browser assertion and
 * >>> this gate ever disagree, this gate is right about the bytes and the
 * >>> browser is right about the rendering — they answer different questions.
 *
 * >>> D-93 REMAINS THE WORKED PROOF that the closed gap could have hidden a
 * >>> LIVE DEFECT. Emitting a bare font-stack KEY as a family name would have
 * >>> rendered nothing at all while passing every text assertion: the bytes in
 * >>> the page, the declaration well-formed, the font never changing. Row 2 of
 * >>> the theme computed-style suite above is exactly that defect made
 * >>> machine-detectable.
 *
 * ═══════════════════════════════════════════════════════════════════════
 *  NO EXTERNAL REQUEST OF ANY KIND
 * ═══════════════════════════════════════════════════════════════════════
 *
 * No `@import`, no `url()`, no font CDN, no image reference. This file is
 * served on EVERY themed page, so an external reference here would be a
 * third-party dependency in the render path of every request — added silently
 * in a phase about colours. The coverage gate asserts their absence, and the
 * token grammars make one inexpressible from the dynamic side independently.
 */

/* ==================================================================== */
/*  D-93 — THE THREE FONT STACKS THE SERIALIZER'S EXPANSIONS RESOLVE TO  */
/* ==================================================================== */

/*
 * THESE ARE THE CONSUMING HALF OF D-93'S MECHANISM, AND THE FEATURE IS DEAD
 * WITHOUT THEM.
 *
 * `packages/plugin-theme/src/serialize.ts` maps a font enum key through a
 * FROZEN three-entry table to the exact literal `var(--noon-stack-sans)` (or
 * `-serif` / `-mono`). These declarations are what those literals resolve to.
 *
 * >>> IF THEY ARE ABSENT, THE FONT TOKEN SETS A CUSTOM PROPERTY THAT RESOLVES
 * >>> TO NOTHING, the `var()` fallback does NOT fire — because the property IS
 * >>> set — and the feature is silently dead while every response-text
 * >>> assertion in this phase stays green. The coverage gate therefore asserts
 * >>> all three are defined in a live rule.
 *
 * They carry the commas, spaces and quotes the token grammar deliberately
 * cannot express, which is the whole reason the indirection exists.
 */
:root {
  --noon-stack-sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --noon-stack-serif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif;
  --noon-stack-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
  /*
   * SIDEBAR INTERNAL CONSTANTS (T-1, 260722-fbg) — the same idiom as the
   * three stack properties above: an internal constant an agent never sets
   * directly, existing only so the REGIONS rules below (and the coverage
   * gate's INTERNAL_CONSTANTS set) reference ONE declaration instead of the
   * literal `16rem`/`calc(var(--noon-spacing-unit,8px)*4)` repeated four
   * times each. `--noon-region-gutter` is deliberately the SAME expression
   * as the region gap below it — the coupling between "how much room the
   * sidebar's width reservation needs" and "how big the gap between it and
   * the content column is" is structural, not two numbers that happen to
   * agree today.
   */
  --noon-sidebar-width: 16rem;
  --noon-region-gutter: calc(var(--noon-spacing-unit, 8px) * 4);
}

/* ==================================================================== */
/*  PAGE — TYPOGRAPHY, COLOUR AND RHYTHM FROM THE BODY DOWN             */
/* ==================================================================== */

:root {
  font-size: var(--noon-base-font-size, 17px);
  color-scheme: light dark;
}

body {
  margin: 0;
  background: var(--noon-background, #fbfbfd);
  color: var(--noon-text, #16161a);
  font-family: var(--noon-body-font, var(--noon-stack-sans));
  /* A real reading measure rather than the full width of a monitor. The
   * horizontal padding is derived from the spacing unit so the page breathes
   * in proportion to the rest of the rhythm. */
  line-height: 1.65;
  -webkit-text-size-adjust: 100%;
}

main {
  max-width: var(--noon-measure, 68ch);
  /* As a flex item inside [data-region-layout], min-width defaults to `auto`,
   * so a wide, non-wrapping child (a <pre> code block, a long token) forces the
   * column past the frame and one page renders wider than a prose-only page.
   * `min-width: 0` lets the column shrink so the <pre>'s own overflow-x:auto
   * scrolls WITHIN a consistent-width column. */
  min-width: 0;
  margin: 0 auto;
  padding: calc(var(--noon-spacing-unit, 8px) * 6) calc(var(--noon-spacing-unit, 8px) * 3);
}

/*
 * THE TYPE SCALE IS DERIVED HERE, NOT STORED AS MORE TOKENS.
 *
 * `scale-ratio` is one number and the heading sizes are computed from it by
 * repeated multiplication — CSS has no exponentiation, so `r^3` is spelled as
 * three multiplications. A second, third and fourth size token would be four
 * values an agent could set into disagreement with each other; one ratio
 * cannot be internally inconsistent.
 */
h1,
h2,
h3,
h4,
h5,
h6 {
  font-family: var(--noon-heading-font, var(--noon-stack-sans));
  line-height: 1.15;
  letter-spacing: -0.015em;
  margin: calc(var(--noon-spacing-unit, 8px) * 4) 0 calc(var(--noon-spacing-unit, 8px) * 2);
  text-wrap: balance;
}

h1 {
  font-size: calc(
    1rem * var(--noon-scale-ratio, 1.25) * var(--noon-scale-ratio, 1.25) *
      var(--noon-scale-ratio, 1.25)
  );
}

h2 {
  font-size: calc(1rem * var(--noon-scale-ratio, 1.25) * var(--noon-scale-ratio, 1.25));
}

h3 {
  font-size: calc(1rem * var(--noon-scale-ratio, 1.25));
}

p,
ul,
ol {
  margin: 0 0 calc(var(--noon-spacing-unit, 8px) * 2);
}

a {
  color: var(--noon-accent, #4338ca);
  text-underline-offset: 0.2em;
  text-decoration-thickness: 0.06em;
}

a:hover {
  text-decoration-thickness: 0.12em;
}

hr {
  border: 0;
  border-top: 1px solid var(--noon-border, #e3e3ea);
  margin: calc(var(--noon-spacing-unit, 8px) * 5) 0;
}

code,
pre,
kbd {
  font-family: var(--noon-stack-mono);
  font-size: 0.9em;
}

pre {
  background: var(--noon-surface, #ffffff);
  border: 1px solid var(--noon-border, #e3e3ea);
  border-radius: var(--noon-radius, 10px);
  padding: calc(var(--noon-spacing-unit, 8px) * 2);
  overflow-x: auto;
}

table {
  width: 100%;
  border-collapse: collapse;
  margin: 0 0 calc(var(--noon-spacing-unit, 8px) * 3);
}

th,
td {
  border-bottom: 1px solid var(--noon-border, #e3e3ea);
  padding: var(--noon-spacing-unit, 8px) 0;
  text-align: left;
}

th {
  font-family: var(--noon-heading-font, var(--noon-stack-sans));
  font-size: 0.85rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--noon-muted, #6b6b76);
}

/* ==================================================================== */
/*  BLOCKS — ONE RULE SET PER `data-block`, THE ATTRIBUTE ALREADY IN     */
/*  PRODUCTION HTML. NO COMPONENT SOURCE CHANGE ADDS ANY HOOK HERE.      */
/* ==================================================================== */

/*
 * The selector list is DERIVED from the schema package's frozen component
 * tuple by the coverage gate, never hand-listed there — the renderer
 * registry's own pattern. A fifth component added to the union in Phase 9
 * fails that gate until it is either styled here or given an explicitly
 * commented empty rule. An omission must be a statement, not a gap.
 */

[data-block="HeroSection"] {
  padding: calc(var(--noon-spacing-unit, 8px) * 5) 0 calc(var(--noon-spacing-unit, 8px) * 4);
  border-bottom: 1px solid var(--noon-border, #e3e3ea);
  margin-bottom: calc(var(--noon-spacing-unit, 8px) * 5);
}

[data-block="HeroSection"] h1 {
  margin-top: 0;
}

/* The subhead is the one paragraph in the hero, and it reads as secondary. */
[data-block="HeroSection"] p {
  color: var(--noon-muted, #6b6b76);
  font-size: calc(1rem * var(--noon-scale-ratio, 1.25));
  max-width: var(--noon-measure, 68ch);
}

/* The call to action is the one place `accent-contrast` earns its keep:
 * text ON the accent rather than text IN it. */
[data-block="HeroSection"] a {
  display: inline-block;
  margin-top: calc(var(--noon-spacing-unit, 8px) * 2);
  padding: var(--noon-spacing-unit, 8px) calc(var(--noon-spacing-unit, 8px) * 2.5);
  background: var(--noon-accent, #4338ca);
  color: var(--noon-accent-contrast, #ffffff);
  border-radius: var(--noon-radius, 10px);
  text-decoration: none;
  font-weight: 600;
}

[data-block="RichText"] {
  margin-bottom: calc(var(--noon-spacing-unit, 8px) * 4);
}

[data-block="RichText"] blockquote {
  margin: calc(var(--noon-spacing-unit, 8px) * 3) 0;
  padding-left: calc(var(--noon-spacing-unit, 8px) * 2);
  border-left: 3px solid var(--noon-accent, #4338ca);
  color: var(--noon-muted, #6b6b76);
}

[data-block="ImageBlock"] {
  margin: calc(var(--noon-spacing-unit, 8px) * 4) 0;
}

[data-block="ImageBlock"] img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--noon-radius, 10px);
  border: 1px solid var(--noon-border, #e3e3ea);
  background: var(--noon-surface, #ffffff);
}

[data-block="ImageBlock"] figcaption {
  margin-top: var(--noon-spacing-unit, 8px);
  color: var(--noon-muted, #6b6b76);
  font-size: 0.9rem;
}

[data-block="ProductGrid"] {
  display: grid;
  gap: calc(var(--noon-spacing-unit, 8px) * 3);
  margin: calc(var(--noon-spacing-unit, 8px) * 4) 0;
}

[data-block="ProductGrid"] article {
  background: var(--noon-surface, #ffffff);
  border: 1px solid var(--noon-border, #e3e3ea);
  border-radius: var(--noon-radius, 10px);
  padding: calc(var(--noon-spacing-unit, 8px) * 2.5);
  display: flex;
  flex-direction: column;
  gap: var(--noon-spacing-unit, 8px);
}

[data-block="ProductGrid"] article h2 {
  margin: 0;
  font-size: calc(1rem * var(--noon-scale-ratio, 1.25));
}

[data-block="ProductGrid"] [data-product-price] {
  margin: 0;
  color: var(--noon-muted, #6b6b76);
  font-family: var(--noon-stack-mono);
}

[data-block="ProductGrid"] [data-checkout-link] {
  margin-top: auto;
  align-self: flex-start;
  padding: var(--noon-spacing-unit, 8px) calc(var(--noon-spacing-unit, 8px) * 2);
  background: var(--noon-accent, #4338ca);
  color: var(--noon-accent-contrast, #ffffff);
  border-radius: var(--noon-radius, 10px);
  text-decoration: none;
  font-weight: 600;
}

/* ==================================================================== */
/*  CALLOUT — ADMONITION BLOCK, VARIANT-KEYED (16-02; AMD-01)            */
/* ==================================================================== */

/*
 * The Callout keys on `data-variant` exactly as ProductGrid keys on
 * `data-columns` — an EXISTING attribute idiom, not a new pattern. Every
 * colour comes from an EXISTING token (surface / border / text / muted /
 * accent), each with a fallback, so the coverage gate's set-equality and
 * fallback assertions stay green and NO new token key is introduced (that
 * would be its own vocabulary amendment — Open Question 2).
 *
 * >>> THE ACCENT IS RESERVED FOR note / tip. warning and danger get NO
 * >>> dedicated hue (the 14-key vocabulary has none): they lean on the neutral
 * >>> muted / text tokens for their left-border emphasis. The variant → token
 * >>> mapping below is pinned by `callout-computed-style.test.ts`, one computed
 * >>> colour per variant, so whatever mapping ships is machine-proven visible.
 */

[data-block="Callout"] {
  margin: calc(var(--noon-spacing-unit, 8px) * 4) 0;
  padding: calc(var(--noon-spacing-unit, 8px) * 2) calc(var(--noon-spacing-unit, 8px) * 2.5);
  background: var(--noon-surface, #ffffff);
  border: 1px solid var(--noon-border, #e3e3ea);
  border-left: calc(var(--noon-spacing-unit, 8px) * 0.5) solid var(--noon-border, #e3e3ea);
  border-radius: var(--noon-radius, 10px);
  color: var(--noon-text, #16161a);
}

[data-block="Callout"] [data-callout-title] {
  margin: 0 0 var(--noon-spacing-unit, 8px);
  font-family: var(--noon-heading-font, var(--noon-stack-sans));
  font-weight: 600;
  color: var(--noon-muted, #6b6b76);
}

/* note / tip — the accent earns its keep as the emphasis hue. */
[data-block="Callout"][data-variant="note"] {
  border-left-color: var(--noon-accent, #4338ca);
}

[data-block="Callout"][data-variant="tip"] {
  border-left-color: var(--noon-accent, #4338ca);
}

/* warning — muted neutral emphasis, no dedicated hue. */
[data-block="Callout"][data-variant="warning"] {
  border-left-color: var(--noon-muted, #6b6b76);
}

/* danger — the strongest neutral (text), no dedicated hue. */
[data-block="Callout"][data-variant="danger"] {
  border-left-color: var(--noon-text, #16161a);
}

/* ==================================================================== */
/*  REGIONS — HEADER, FOOTER AND SIDEBAR SLOTS (09-04; REG-01a, REG-03)  */
/* ==================================================================== */

/*
 * These rules key on `data-region`, NOT `data-block`, so they are OUTSIDE the
 * coverage gate's block-selector contract by construction — a region wrapper is
 * emitted by the render seam, not by a component in the frozen tuple. They use
 * only EXISTING token keys, each with a fallback, so the token set-equality and
 * fallback assertions in `theme-stylesheet-coverage.test.ts` stay green.
 *
 * >>> AN ABSENT REGION EMITS NO WRAPPER AT ALL (the seam's zero-bytes rule), so
 * >>> none of these selectors matches anything on a region-less deployment —
 * >>> which is why the no-region baseline is byte-identical. They style the
 * >>> slots only once an agent publishes a template of that type.
 */

[data-region="header"] {
  max-width: calc(var(--noon-sidebar-width) + var(--noon-measure, 68ch) + var(--noon-region-gutter));
  margin: 0 auto;
  padding: calc(var(--noon-spacing-unit, 8px) * 3) calc(var(--noon-spacing-unit, 8px) * 3) 0;
}

[data-region="footer"] {
  max-width: calc(var(--noon-sidebar-width) + var(--noon-measure, 68ch) + var(--noon-region-gutter));
  margin: calc(var(--noon-spacing-unit, 8px) * 6) auto 0;
  padding: calc(var(--noon-spacing-unit, 8px) * 4) calc(var(--noon-spacing-unit, 8px) * 3);
  border-top: 1px solid var(--noon-border, #e3e3ea);
  color: var(--noon-muted, #6b6b76);
}

/* The sidebar shares this flow wrapper with the page's own <main>. The wrapper
 * is emitted ONLY when a sidebar template is published, so its own max-width
 * governs the pair; the nested <main>'s auto margins still center the content
 * column within the remaining space. */
[data-region-layout] {
  display: flex;
  gap: var(--noon-region-gutter);
  align-items: flex-start;
  max-width: calc(var(--noon-sidebar-width) + var(--noon-measure, 68ch) + var(--noon-region-gutter));
  margin: 0 auto;
}

[data-region="sidebar"] {
  flex: 0 0 auto;
  width: var(--noon-sidebar-width);
  padding-top: calc(var(--noon-spacing-unit, 8px) * 6);
  color: var(--noon-muted, #6b6b76);
  border-right: 1px solid var(--noon-border, #e3e3ea);
}

/* On a narrow viewport the sidebar stacks above the content rather than
 * squeezing it — the same device-reality override the product grid uses. */
@media (max-width: 40rem) {
  [data-region-layout] {
    flex-direction: column;
  }

  [data-region="sidebar"] {
    width: auto;
    border-right: 0;
    border-bottom: 1px solid var(--noon-border, #e3e3ea);
  }
}

/* ==================================================================== */
/*  D-88 — THE FOUR COLUMN RULES THAT REPLACE THE DELETED INLINE STYLE   */
/* ==================================================================== */

/*
 * `apps/web/src/components/product-grid.tsx` USED to carry an inline
 * `style={{ gridTemplateColumns: repeat(<columns>, 1fr) }}`. It was DELETED,
 * and these four rules are its exact functional replacement.
 *
 * >>> THEY ARE EXHAUSTIVE BY CONSTRUCTION, NOT BY INSPECTION. `packages/schema`
 * >>> bounds `columns` to `z.int().min(1).max(4)`, so the value space is
 * >>> finite and these four rules cover ALL of it. The component's own
 * >>> `DEFAULT_COLUMNS` (3) is inside that range and the `data-columns`
 * >>> attribute is emitted UNCONDITIONALLY — verified in the pre-phase
 * >>> rendered HTML, not inferred — so every rendered grid matches a rule.
 *
 * >>> THE COVERAGE GATE ASSERTS THIS RULE SET AGAINST THE SCHEMA'S OWN MAX
 * >>> RATHER THAN AGAINST THE LITERAL FOUR. If a later phase widens the bound
 * >>> to 6, the gate fails instead of the grid silently losing its columns —
 * >>> which is the difference between a bound that is enforced and a bound
 * >>> that happened to be true when someone last looked.
 *
 * Nothing was ADDED to the component. One prop was removed. That is why this
 * does not breach the phase's anti-goal on component changes: the deletion
 * only became possible because a previous phase emitted the semantic
 * `data-columns` attribute ALONGSIDE its presentational output.
 */

[data-block="ProductGrid"][data-columns="1"] {
  grid-template-columns: repeat(1, 1fr);
}

[data-block="ProductGrid"][data-columns="2"] {
  grid-template-columns: repeat(2, 1fr);
}

[data-block="ProductGrid"][data-columns="3"] {
  grid-template-columns: repeat(3, 1fr);
}

[data-block="ProductGrid"][data-columns="4"] {
  grid-template-columns: repeat(4, 1fr);
}

/* A narrow viewport collapses every column count to one. The static rules
 * above are the authored intent; this is the device reality. */
@media (max-width: 40rem) {
  [data-block="ProductGrid"] {
    grid-template-columns: 1fr;
  }
}

/* FLOATING DISPLAY-RULE ISLAND (10-05; REG-01b/REG-04, D-127) — CUT from v1.1
 * (D-131). Its CSS, client script, island component and column were removed
 * together; REG-01b/REG-04 return in v1.2 with browser-test infra. */
