/* ============================================================
   Frost Science · base layer
   Loads after tokens.css and fonts.css. Nothing else before it.

   Rules this file exists to enforce, so components don't have to:
     · Every ink is bound to a named surface (see "surface contexts")
     · 16px floor on every form control, including textarea and select
     · 44px touch targets, 48px on the purchase path
     · Focus ring visible on every surface in the palette
     · Zero CLS after first paint

   No literal hex, px radius or font name below. Tokens only.
   ============================================================ */

/* ---------- 1 · Reset ---------- */

*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;   /* iOS must not inflate type in landscape */
  text-size-adjust: 100%;
  scroll-behavior: smooth;
}
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}

body,
h1, h2, h3, h4, h5, h6,
p, figure, blockquote, dl, dd, ol, ul {
  margin: 0;
}

ul[role='list'], ol[role='list'] { list-style: none; padding: 0; }

/* Media never causes layout shift. width and height attributes are required
   in every render.php, and modern browsers derive the ratio from them
   automatically — no CSS needed.

   There used to be an `aspect-ratio: attr(width) / attr(height)` rule here.
   attr() is only supported for the `content` property, so the aspect-ratio
   was silently dropped — but `height: auto` in the same rule survived, at a
   specificity of (0,2,1), which beat every block's `.fs-…__img { height: 100% }`.
   Framed images stopped cropping and rendered at full intrinsic height. The
   symptom was enormous cards on the homepage. */
img, picture, video, canvas, svg, iframe {
  display: block;
  max-width: 100%;
}
img, video { height: auto; }

/* Inputs do not inherit type by default. They must. */
input, button, textarea, select { font: inherit; color: inherit; }

textarea { resize: vertical; }

table { border-collapse: collapse; border-spacing: 0; }

/* Tabular figures anywhere a number is compared: prices, times, capacities. */
.fs-numeric,
td.fs-numeric,
[data-numeric] {
  font-variant-numeric: var(--fs-numeric);
}


/* ---------- 2 · Document and type scale ---------- */

body {
  margin: 0;
  background: var(--fs-surface);
  color: var(--fs-text);
  font-family: var(--fs-font-text);
  font-size: var(--fs-body);
  font-weight: 400;
  line-height: 1.6;
  min-height: 100dvh;              /* dvh, not vh — iOS Safari's URL bar */
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

h1, h2, h3, .fs-display {
  font-family: var(--fs-font-display);
  font-weight: 700;
  color: var(--fs-on-surface, var(--fs-text));
  text-wrap: balance;
}

h1, .fs-h1 { font-size: var(--fs-h1); line-height: .98; letter-spacing: -.03em; }
h2, .fs-h2 { font-size: var(--fs-h2); line-height: 1.1;  letter-spacing: -.02em; }
h3, .fs-h3 { font-size: var(--fs-h3); line-height: 1.25; letter-spacing: -.01em; }

p { text-wrap: pretty; }
p + p { margin-top: var(--fs-space-4); }

.fs-lead { font-size: var(--fs-h3); line-height: 1.45; }
/* §2.5 convention — the lead paragraph's first sentence renders bold. */
.fs-lead > strong:first-child,
.fs-lead::first-line { font-weight: 500; }

small, .fs-body-sm { font-size: var(--fs-body-sm); }
.fs-caption { font-size: var(--fs-caption); color: var(--fs-on-surface-quiet, var(--fs-text-quiet)); }

.fs-eyebrow {
  font-family: var(--fs-font-text);
  font-size: var(--fs-eyebrow);
  font-weight: 700;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--fs-on-surface-quiet, var(--fs-text-quiet));
}


/* ---------- 3 · Surface contexts ---------- */
/*
   The defect this prevents: an ink measured on white, used on canvas.
   It happened three times in design (06-ACCESSIBILITY.md).

   Components must never reference --fs-text, --fs-link or an -ink token
   directly. They reference --fs-on-surface, --fs-on-surface-muted,
   --fs-on-surface-quiet, --fs-on-surface-link, --fs-focus-ring. Each
   surface class below binds those to the values measured on THAT ground.
   Change the surface, the inks follow. There is no way to get it wrong.

   Verified by tools/contrast.mjs — CI fails on any pairing not sanctioned.
*/

:root,
.fs-surface-white {
  --fs-surface-current:    var(--fs-surface);
  --fs-on-surface:         var(--fs-text);
  --fs-on-surface-muted:   var(--fs-text-muted);
  --fs-on-surface-quiet:   var(--fs-text-quiet);
  --fs-on-surface-link:    var(--fs-link);
  --fs-on-surface-accent:  var(--fs-action-ink);   /* white-only. See below. */
  --fs-focus-ring:         var(--fs-text);
}

.fs-surface-alt {
  --fs-surface-current:    var(--fs-surface-alt);
  --fs-on-surface:         var(--fs-text);
  --fs-on-surface-muted:   var(--fs-text-muted);
  --fs-on-surface-quiet:   var(--fs-text-quiet);
  --fs-on-surface-link:    var(--fs-link-canvas);
  --fs-on-surface-accent:  var(--fs-text);         /* action-ink is white-only */
  --fs-focus-ring:         var(--fs-text);
}

.fs-surface-paper {
  --fs-surface-current:    var(--fs-surface-paper);
  --fs-on-surface:         var(--fs-text);
  --fs-on-surface-muted:   var(--fs-text-muted);
  --fs-on-surface-quiet:   var(--fs-text-quiet);
  --fs-on-surface-link:    var(--fs-link-canvas);
  --fs-on-surface-accent:  var(--fs-text);         /* action-ink is 4.3 here — navy instead */
  --fs-focus-ring:         var(--fs-text);
}

.fs-surface-canvas {
  --fs-surface-current:    var(--fs-surface-canvas);
  --fs-on-surface:         var(--fs-text);
  --fs-on-surface-muted:   var(--fs-text-muted);
  --fs-on-surface-quiet:   var(--fs-text-quiet);
  --fs-on-surface-link:    var(--fs-link-canvas);  /* aquatic-ink is 4.06 here — never */
  --fs-on-surface-accent:  var(--fs-text);
  --fs-focus-ring:         var(--fs-text);
}

/* Navy. Heroes, CTA bands, footer.
   OPEN-02: 06 says the focus ring is navy and "identical on every surface".
   Navy on navy is 1:1 — invisible, and SC 1.4.11 needs 3:1. Blue reaches
   9.38 here and is already in the palette, so the ring swaps to blue on this
   one surface. Awaiting sign-off; the alternative is a dual navy/white ring
   used everywhere. */
.fs-surface-deep {
  --fs-surface-current:    var(--fs-surface-deep);
  --fs-on-surface:         var(--fs-text-inverse);
  --fs-on-surface-muted:   var(--fs-text-inverse);
  --fs-on-surface-quiet:   var(--fs-text-inverse);
  --fs-on-surface-link:    var(--fs-blue);         /* 9.38 on navy */
  --fs-on-surface-accent:  var(--fs-orange);       /* 6.17 on navy */
  --fs-focus-ring:         var(--fs-blue);
}

.fs-surface-white, .fs-surface-alt, .fs-surface-paper,
.fs-surface-canvas, .fs-surface-deep {
  background-color: var(--fs-surface-current);
  color: var(--fs-on-surface);
}


/* ---------- 4 · Links ---------- */

a {
  color: var(--fs-on-surface-link, var(--fs-link));
  text-underline-offset: 3px;
  text-decoration-thickness: from-font;
}
a:hover { color: var(--fs-on-surface, var(--fs-text)); }
.fs-surface-deep a:hover { color: var(--fs-text-inverse); }

/* Links inside running text are always underlined. Colour alone is not a
   signal (WCAG 1.4.1) and on navy the link blue reads as decoration. */
.fs-prose a, p a, li a { text-decoration: underline; }


/* ---------- 5 · Focus ---------- */

:focus-visible {
  outline: var(--fs-control-border) solid var(--fs-focus-ring, var(--fs-text));
  outline-width: 3px;
  outline-offset: var(--fs-focus-offset);
  border-radius: var(--fs-radius-sm);
}

/* Never removed. Not on mouse-down, not on a "polished" custom control. */
:focus:not(:focus-visible) { outline: none; }


/* ---------- 6 · Form controls ---------- */
/*
   16px floor on every control, including textarea, select and the stepper's
   number field. Below 16px iOS Safari zooms on focus and throws the layout.
   This is a `min` so a component can go larger but never smaller.
*/

input, textarea, select, button {
  font-size: max(var(--fs-ui), 1rem);
  font-family: var(--fs-font-text);
  line-height: 1.2;
}

input:not([type='checkbox']):not([type='radio']):not([type='range']),
textarea,
select {
  width: 100%;
  min-height: var(--fs-control-height);
  padding: var(--fs-space-3) var(--fs-space-4);
  background: var(--fs-surface);
  color: var(--fs-text);
  border: var(--fs-control-border) solid var(--fs-text-disabled);
  border-radius: var(--fs-radius-sm);
  transition: border-color var(--fs-motion) var(--fs-ease);
}

input:hover, textarea:hover, select:hover { border-color: var(--fs-text-quiet); }
input:focus, textarea:focus, select:focus { border-color: var(--fs-text); }

/* Placeholders sit on white or paper only — 4.06 on canvas. Any field on a
   canvas ground must set its own background to --fs-surface-paper. */
::placeholder { color: var(--fs-text-placeholder); opacity: 1; }

/* No floating labels. They animate into a space too small to read and break
   at Spanish string lengths. A real label, above, always. */
label {
  display: block;
  font-size: var(--fs-body-sm);
  font-weight: 500;
  margin-bottom: var(--fs-space-2);
  color: var(--fs-on-surface, var(--fs-text));
}

/* Mark optional, not required — required is the norm on these forms. */
.fs-optional {
  font-weight: 400;
  color: var(--fs-on-surface-quiet, var(--fs-text-quiet));
}

/* Errors carry a glyph and a sentence, never colour alone (WCAG 1.4.1). */
[aria-invalid='true'] { border-color: var(--fs-error); }

.fs-field-error {
  display: flex;
  gap: var(--fs-space-2);
  margin-top: var(--fs-space-2);
  font-size: var(--fs-body-sm);
  color: var(--fs-error);
}
.fs-field-error::before { content: '\2715'; flex: none; font-weight: 700; }

/* Reserved slot so the layout never jumps under a thumb when an error
   appears. Height is one line of --fs-body-sm at 1.4 plus its margin. */
.fs-field-error-slot { min-height: calc(var(--fs-body-sm) * 1.4 + var(--fs-space-2)); }

/* The honeypot. No CAPTCHA anywhere on this site. Off-screen rather than
   display:none, so a bot filling everything still trips it, and
   aria-hidden + tabindex keep it away from assistive tech. */
.fs-hp {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

fieldset { border: 0; padding: 0; margin: 0; min-width: 0; }
legend { padding: 0; font-weight: 700; }


/* ---------- 7 · Touch targets ---------- */
/*
   44px everywhere, 48px on the purchase path, 8px minimum gap between
   adjacent targets. WCAG 2.2 asks 24px; the museum has touch tanks and wet
   floors, so this more than doubles it.
*/

button, [role='button'], .fs-btn,
a.fs-btn, input[type='submit'], input[type='button'] {
  min-height: var(--fs-target-min);
  min-width: var(--fs-target-min);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--fs-space-2);
  padding: var(--fs-space-3) var(--fs-space-5);
  border: var(--fs-control-border) solid transparent;
  border-radius: var(--fs-radius-pill);
  font-weight: 700;
  text-decoration: none;
  cursor: pointer;
  transition: background-color var(--fs-motion) var(--fs-ease),
              color var(--fs-motion) var(--fs-ease);
}

/* Purchase path: everything grows to 48px and the token follows, so nested
   components inherit the larger target without knowing where they are. */
.fs-purchase-path { --fs-target-min: var(--fs-control-height); }

/* Whole rows are tappable, not just the label or the control. */
.fs-row-target { display: block; min-height: var(--fs-target-min); cursor: pointer; }

/* Adjacent targets never touch. */
.fs-target-group { display: flex; flex-wrap: wrap; gap: var(--fs-space-2); }

button[disabled], .fs-btn[aria-disabled='true'] {
  color: var(--fs-text-disabled);
  cursor: not-allowed;
  /* --fs-text-disabled is 1.79:1 and intentionally exempt under 1.4.3.
     The low contrast IS the affordance. Do not raise it. */
}


/* ---------- 8 · Layout primitives ---------- */

.fs-container {
  width: 100%;
  max-width: var(--fs-content-max);
  margin-inline: auto;
  padding-inline: var(--fs-space-4);
}
@media (min-width: 768px) {
  .fs-container { padding-inline: var(--fs-space-6); }
}

.fs-section { padding-block: var(--fs-space-7); }
@media (min-width: 768px) {
  .fs-section { padding-block: calc(var(--fs-space-7) * 1.5); }
}

/* Line length ceiling on anything that is read rather than scanned. */
.fs-measure { max-width: var(--fs-measure); }

.fs-stack > * + * { margin-block-start: var(--fs-space-4); }

/* Sticky chrome uses dvh, never vh. */
.fs-sticky { position: sticky; }


/* ---------- 9 · Utilities ---------- */

.fs-visually-hidden:not(:focus):not(:focus-within) {
  position: absolute !important;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* Skip link — hidden until focused, then unmissable. */
.fs-skip-link {
  position: absolute;
  top: var(--fs-space-2);
  left: var(--fs-space-2);
  z-index: 999;
  padding: var(--fs-space-3) var(--fs-space-4);
  background: var(--fs-surface-deep);
  color: var(--fs-text-inverse);
  border-radius: var(--fs-radius-sm);
  font-weight: 700;
  transform: translateY(-200%);
}
.fs-skip-link:focus {
  transform: translateY(0);
  color: var(--fs-text-inverse);
}

/* Anchored headings must clear the compressed 64px sticky header. */
:target { scroll-margin-top: calc(64px + var(--fs-space-6)); }


/* ---------- 10 · Print ---------- */
/* Guests print confirmations, maps and field-trip forms. */

@media print {
  .fs-no-print, .fs-skip-link { display: none !important; }
  body { background: #fff; color: #000; font-size: 12pt; }
  a[href^='http']::after { content: ' (' attr(href) ')'; font-size: 10pt; }
}
