/* ============================================================================
   Nouri Narie 2.0 — element defaults and layout primitives

   Loads after tokens.css and before components.css. Nothing here knows about a
   specific page. Three rules carried over from v1 because they were each
   written to fix a real bug, and dropping them would reintroduce it:
   img{height:auto}, the anchor scroll offset, and the reduced-motion gate.
   ========================================================================= */

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

/* An author display rule must never beat the HTML hidden state. cart.js,
   account.js and admin.js all toggle [hidden] to switch panels. */
[hidden] { display: none !important; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}

/* The ticker and nav are both fixed, so an un-offset anchor jump parks the
   target's eyebrow and heading behind them. --h-chrome is the sum of the two,
   not the nav alone — see tokens.css. */
:where([id]) { scroll-margin-top: var(--h-chrome); }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-text);
  font-size: var(--t-body);
  line-height: var(--lh-prose);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  /* clip, not hidden: `hidden` on body silently disables position:sticky in
     every descendant, which the PDP buy box and the ticker both depend on. */
  overflow-x: clip;
}

/* `height: auto` is load-bearing. Every <img> carries width and height
   attributes so the browser can reserve space and CLS stays at zero; without
   this rule the attribute height wins whenever CSS constrains the width, and a
   900x900 product shot renders 430x900 — stretched. */
img, svg, video {
  display: block;
  max-width: 100%;
  height: auto;
}

a { color: inherit; }

a:not([class]) {
  color: var(--sage-deep);
  text-underline-offset: 0.18em;
  text-decoration-thickness: 1px;
}

:focus-visible {
  outline: 2px solid var(--sage-deep);
  outline-offset: 3px;
  border-radius: var(--r-xs);
}

::selection {
  background: var(--sage-wash);
  color: var(--ink);
}

/* ── type ──────────────────────────────────────────────────────────────────
   Archivo is a variable face. Setting the width axis explicitly matters: at
   the default width it is noticeably wider than Rektorat and the display sizes
   stop looking like the reference. 88 is the value that matches. */

h1, h2, h3, h4 {
  margin: 0;
  font-family: var(--font-display);
  font-variation-settings: 'wght' 800, 'wdth' 88;
  font-weight: 800;
  line-height: var(--lh-head);
  letter-spacing: var(--ls-head);
  text-wrap: balance;
}

h1 { font-size: var(--t-h1); }
h2 { font-size: var(--t-h2); }
h3 { font-size: var(--t-h3); }
h4 { font-size: var(--t-h4); }

p { margin: 0; text-wrap: pretty; }

/* Display sizes. Deliberately opt-in classes rather than element defaults:
   they belong on index, story and the footer wordmark and nowhere else, and
   the contrast against the interior scale is what makes a homepage read as a
   homepage. */
.t-mega {
  font-family: var(--font-display);
  font-variation-settings: 'wght' 800, 'wdth' 88;
  font-weight: 800;
  font-size: var(--t-mega);
  line-height: var(--lh-mega);
  letter-spacing: var(--ls-mega);
  text-transform: uppercase;
  margin: 0;
}

.t-display {
  font-family: var(--font-display);
  font-variation-settings: 'wght' 800, 'wdth' 90;
  font-weight: 800;
  font-size: var(--t-display);
  line-height: var(--lh-tight);
  letter-spacing: var(--ls-head);
  margin: 0;
}

.t-lede    { font-size: var(--t-lede); line-height: var(--lh-body); color: var(--ink-soft); }
.t-sm      { font-size: var(--t-sm); }
.t-caption { font-size: var(--t-caption); color: var(--ink-muted); }

/* The mono is a label face, never a body face. Anything set in it is short,
   uppercase and tracked — step indices, SKUs, order numbers, eyebrows. */
.t-micro {
  font-family: var(--font-mono);
  font-size: var(--t-micro);
  font-weight: 500;
  letter-spacing: var(--ls-micro);
  text-transform: uppercase;
  line-height: 1.3;
}

.t-eyebrow {
  font-family: var(--font-mono);
  font-size: var(--t-micro);
  font-weight: 500;
  letter-spacing: var(--ls-micro);
  text-transform: uppercase;
  color: var(--ink-muted);
  display: block;
  margin-bottom: var(--s-3);
}

/* ── layout primitives ─────────────────────────────────────────────────── */

.wrap      { width: 100%; max-width: var(--measure);      margin-inline: auto; padding-inline: var(--gutter); }
.wrap-text { width: 100%; max-width: var(--measure-text); margin-inline: auto; padding-inline: var(--gutter); }
.wrap-wide { width: 100%; max-width: var(--measure-wide); margin-inline: auto; padding-inline: var(--gutter); }

/* The page alternates between Rhode's putty and Kylie's blush. A band declares
   its own ground; it does not inherit one. */
.band       { padding-block: var(--section-y); }
.band--paper { background: var(--paper); }
.band--shell { background: var(--shell); }
.band--mist { background: var(--mist); }
.band--metal  { background: var(--alu-light); }
.band--graphite { background: var(--graphite); color: var(--on-dark); }
.band--graphite .t-caption,
.band--graphite .t-eyebrow { color: var(--on-dark-mute); }

.band--flush { padding-block: 0; }

.band-head { margin-bottom: var(--s-12); }
.band-head p { max-width: 56ch; color: var(--ink-soft); }

.stack > * + * { margin-top: var(--s-4); }
.stack-lg > * + * { margin-top: var(--s-8); }

.grid-3 {
  display: grid;
  gap: var(--s-6);
  grid-template-columns: 1fr;
}
@media (min-width: 768px)  { .grid-3 { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1024px) { .grid-3 { grid-template-columns: repeat(3, 1fr); gap: var(--s-8); } }

.grid-2 {
  display: grid;
  gap: var(--s-8);
  grid-template-columns: 1fr;
}
@media (min-width: 1024px) { .grid-2 { grid-template-columns: 1fr 1fr; gap: var(--s-16); } }

/* Long-form prose. --measure-text is Rhode's 1040px, but a reading column
   inside it still wants a character measure rather than the full width. */
.prose { max-width: 68ch; }
.prose > * + * { margin-top: var(--s-5); }
.prose h2 { margin-top: var(--s-12); }
.prose h3 { margin-top: var(--s-10); }
.prose p { color: var(--ink-soft); }

/* ── skip link ─────────────────────────────────────────────────────────── */

.skip-link {
  position: fixed;
  top: -100px;
  left: var(--s-4);
  z-index: var(--z-skip);
  padding: var(--s-3) var(--s-5);
  background: var(--ink);
  color: var(--on-dark);
  border-radius: var(--r-xs);
  font-size: var(--t-sm);
  text-decoration: none;
  transition: top var(--d-fast) var(--ease);
}
.skip-link:focus { top: var(--s-2); }

#main:focus-visible { outline: 2px solid var(--sage-deep); outline-offset: 3px; }

.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* ── disabled ──────────────────────────────────────────────────────────────
   Never opacity. Both reference systems name opacity-as-disabled an
   anti-pattern, and it is also a real accessibility problem: a 0.5-opacity
   label on a light ground drops below AA while still looking deliberate.
   js/stock.js disables sold-out buttons on every product surface, so this is a
   live path, not a theoretical one. */
[disabled],
[aria-disabled='true'] {
  color: var(--ink-disabled);
  background: var(--paper);
  border-color: var(--rule-soft);
  cursor: not-allowed;
}

/* ── reveal ────────────────────────────────────────────────────────────────
   The resting state for scroll-revealed blocks. site.js clears it, and this
   rule guarantees the content is visible anyway if GSAP never loads or the
   user asked for reduced motion — the film is not worth a blank page. */
.reveal {
  opacity: 0;
  transform: translateY(24px);
  will-change: opacity, transform;
}
.reveal.is-in {
  opacity: 1;
  transform: none;
  will-change: auto;
}

@media (prefers-reduced-motion: reduce) {
  .reveal {
    opacity: 1 !important;
    transform: none !important;
    will-change: auto;
  }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

/* Same guarantee for the no-JS case. site.js adds .js to <html> on parse. */
html:not(.js) .reveal { opacity: 1; transform: none; }
