/* ==========================================================================
   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);
}

/* BRIEF OVERRIDE, and it is arithmetic rather than taste. brief §5.5's chrome
   term is a MEASURED 44 + 56 + 1 = 101px stack at every width 320-1600, and
   every first-screen row on this site is computed on it. .btn's block padding
   plus its 1px border makes the control 46.8px at 1440, which pushes the whole
   stack to 103.8 and every measured row 2.8px down. Dropping the block padding
   leaves min-height (= --layout-tap-min) as the control's only height source,
   so the control is exactly 44px, the tap floor is still met, and the bar is
   the height the brief computed against. */
.btn--buy {
  padding-block: 0;
}

/* 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. */

/* ==========================================================================
   BRIEF OVERRIDE — Green Lawn Care & Landscape Inc., brief §6 "Sticky chrome
   (both bars, on every page)".

   The brief says so, so the site's copy of this file adds it, exactly as the
   fence above prescribes. Both bars are `sticky`, never `fixed`: sticky
   occupies flow height from first paint, so the stack costs no CLS.

   The brief re-derived its first screen with the stack included — §5.5's
   chrome term is the MEASURED height of the stack, never the sum of its
   tokens: --expiry-height 44 + a 56px site header + the header's 1px
   border-bottom = 101px, at every width 320–1600.

   COLOUR OVERRIDE, also the brief's (§6, sticky chrome 1): the bar takes
   --color-band as its ground and --color-accent-onband as its ink — 8.34:1,
   brief §4.1 pair 9 — rather than the house default ink/bg inversion, so the
   preview strip sits with the page's one dark moment instead of introducing
   a second inversion. The Buy control keeps the page's own pair.
   ========================================================================== */

.expiry {
  position: sticky;
  inset-block-start: 0;
  background-color: var(--color-band);
  color: var(--color-accent-onband);
}

.site-header {
  position: sticky;
  inset-block-start: var(--expiry-height);
  z-index: var(--z-header);
}

section[id] {
  scroll-margin-block-start: calc(var(--expiry-height) + var(--header-height) + var(--border-hairline));
}

/* NO focus-ring override is added here. The bar's only focusable element is
   the Buy control, and the house default already contains its ring INSIDE the
   control, where it is the site's own ink/bg pair (14.60:1) rather than an
   unvalidated pairing against the band. A .expiry :focus-visible rule would
   have out-ordered .btn--buy:focus-visible at equal specificity and put
   --color-accent-onband on the --color-bg chip, which is 1.51:1.
   Checked, not assumed. */
