/* ==========================================================================
   preview.css — PREVIEW-ONLY CHROME. Nothing in this file belongs to the
   business whose site it sits on, and nothing in it survives purchase.

   WHY THIS IS IN THE SKELETON AT ALL, given that the skeleton ships no
   components: because it is not one. It carries no business content, decides
   nothing about how the site looks, and is DELETED AT PURCHASE — so it can
   never appear in the artifact the DNA registry measures, which is the whole
   reason site.css ships no hero and no card grid. It is the second member of
   a category tweaks.js already established: house-wide machinery that serves
   the preview window and that the buyer never sees. (house-tech-spec §2, §5,
   §7; ad-ruling--bar-in-skeleton §2.4.)

   AT PURCHASE, delete: this file, its <link> in every page, the
   @preview:expiry block in every page, buy.html, and initExpiry() and its
   call in js/main.js. README.md carries the checklist; head-audit.mjs
   BLOCKERs a `status: sold` site that still carries any of it.

   THE 40 KB CSS CAP DOES NOT MEASURE THIS FILE. It is excluded by name in
   scripts/qa/lib/site.mjs, exactly as shippedJsFiles excludes tweaks.js, and
   for the same stated reason: deletability at purchase, not byte count
   (house-tech-spec §7, amended 2026-08-20).

   THE FENCE — read this before editing anything below.
   This file may reference ONLY tokens that tokens.css guarantees on every
   site, plus the one preview-only token declared below. It may NOT introduce
   a colour, a radius, a shadow, a motion value or a breakpoint. That fence is
   what keeps preview chrome from becoming a shipped design component.

   A BRIEF MAY OVERRIDE ANY OF IT, in this file, in the site's own copy —
   exactly as a brief extends site.css. Two overrides are common and both are
   the brief's, never the skeleton's:
     1. Colour, when the brief has a chrome ground of its own.
     2. STICKY STACKING. The house default is static flow (see below).
   ========================================================================== */

:root {
  /* The one preview-only token. It cannot live in tokens.css: that is the file
     the buyer inherits, and this value must not survive purchase. It resolves
     to --layout-tap-min rather than restating 44px, because a raw size literal
     outside tokens.css is a house blocker (spec §3) and because the bar's
     height IS the tap minimum — that is where the number comes from (spec §8). */
  --expiry-height: var(--layout-tap-min);
}

/* THE HOUSE DEFAULT IS STATIC FLOW, first in <body>, above the header.

   It is deliberately NOT sticky. A sticky bar puts a permanent offset into
   every first-screen calculation on the page, and the first screen is a brief
   commitment computed per site at every breakpoint (design-rules §5, §7.1). A
   skeleton default that a brief must UNDO is a design decision; a default a
   brief may EXTEND is a contract. This one can only be extended.

   To make it sticky, the BRIEF says so and the site's copy of this file adds:
     .expiry     { position: sticky; inset-block-start: 0; }
     .site-header{ position: sticky; inset-block-start: var(--expiry-height); }
     section[id] { scroll-margin-block-start:
                     calc(var(--expiry-height) + <the brief's header height>); }
   ...and the brief re-derives its first screen with the stack included. Both
   bars must be `sticky`, never `fixed`: sticky occupies flow height from first
   paint, so the stack costs no CLS.

   THE GROUND is the site's own --color-ink with --color-bg as its ink: the
   page's own pair, inverted. Three reasons, none of them a preference.
     1. It is the ONE pair every brief has already measured for AA (§4 of the
        brief, the body-text row), and inverting a pair does not change its
        ratio — so this bar passes contrast on every site without a new row.
     2. The page never uses that pair as a full-width field, so the strip reads
        as scaffolding rather than as one of the page's own sections.
     3. --color-accent is NOT available: it is the one colour that means "act"
        (tokens.css), and handing it to non-content demotes the page's own
        primary CTA 100px below it.                                          */
.expiry {
  z-index: var(--z-header);
  background-color: var(--color-ink);
  color: var(--color-bg);
}

.expiry__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
  min-height: var(--expiry-height);
}

/* One line, never two. If a brief makes the bar sticky, the header's offset is
   a fixed token and a bar that wrapped would let the page slide under the
   header — there is no graceful degradation available. The copy is written to
   a measured width budget at 320px, and what yields is the button label first
   (design-rules §5: every repeated ornament states its minimum and what yields
   at that minimum). */
.expiry__text {
  font-size: var(--text-eyebrow);
  line-height: var(--leading-snug);
}

/* The Buy control. Geometry, tap target and transition are .btn's; only the
   colour pair and the side padding change. It is a chip of the page sitting on
   the chrome — the same ink/bg pair again, so the ratio is the site's own
   body-text ratio and is AA by construction. */
.btn--buy {
  flex: 0 0 auto;
  padding-inline: var(--space-sm);
  background-color: var(--color-bg);
  color: var(--color-ink);
}

/* THE FOCUS RING IS INSET, and this is a house default rather than a per-site
   fix for a measured reason. --color-focus is validated against --color-bg and
   --color-surface only (tokens.css) — it says nothing about a dark ground, and
   the first two briefs this pipeline produced BOTH failed WCAG 1.4.11 on
   exactly that. An outward ring would also land on the page below the bar,
   which is a second unvalidated pairing. Contained inside the control, the ring
   is the site's own ink/bg pair again and cannot fail on any site. */
.btn--buy:focus-visible {
  outline-color: var(--color-ink);
  outline-offset: calc(var(--space-3xs) * -1);
}

/* NO MOTION. Nothing in the bar reveals, transitions or animates beyond .btn's
   inherited hover. It never carries data-reveal. Chrome that stays still does
   not compete with the page's one hero moment (design-rules §0), and
   initExpiry()'s single textContent write happens inside a fixed-height,
   non-wrapping box, so it cannot reflow anything below it. Under
   prefers-reduced-motion nothing about the bar changes, because nothing about
   it was moving. */
