/* ============================================================
   MUTĖ MILLINERY — motion
   "Gilt & Shadow". One idea at three scales: light raked across
   a dark ground, and a line drawn by hand.

   Rules of the house:
     · Only transform / opacity / filter / background-position
       carry the movement. Colour and hairline transitions are
       allowed because they are the ground settling, not motion.
     · Nothing bounces, springs or rotates for effect. The only
       rotation on the page is the burger becoming a cross and
       the FAQ plus becoming a minus — both functional.
     · Every hidden start-state is scoped to `.js-ready`, which
       js/main.js puts on <html> as its first statement. No JS
       -> nothing is hidden, and the page renders complete.
     · All timings come from the --dur-* / --ease-* / --stagger
       tokens. No number in this file is a duration.
     · This sheet loads LAST, so it wins ties against
       components.css. It never uses !important outside the
       reduced-motion block, where !important is the point.
   ============================================================ */


/* ============================================================
   0. FAIL-SAFES — content must never be permanently invisible
   ------------------------------------------------------------
   Two ways this page could strand its own words, and the
   answer to both.
   ============================================================ */

/* (a) base.css paints `.gild` by clipping a gradient to the
   glyphs and setting `color: transparent`. On a browser that
   cannot clip a background to text, that leaves the hero word
   "Mutė" and the footer wordmark rendered in nothing at all.
   Put the ink back before anything else runs. */
@supports not ((-webkit-background-clip: text) or (background-clip: text)) {
  .gild {
    background-image: none;
    color: var(--parchment);
    -webkit-text-fill-color: var(--parchment);
  }
}

/* (b) The reveal system, the filigree draw and the drawer all
   start from a hidden state. Every one of those rules below is
   prefixed `.js-ready `. Nothing in this file hides anything
   without that prefix — that is the single rule the whole
   project is built on, and it is worth re-reading the file for
   before shipping. Search for `opacity: 0` and confirm every
   hit sits behind `.js-ready` or inside a @keyframes block. */


/* ============================================================
   1. THE REVEAL SYSTEM — one shared mechanism
   ------------------------------------------------------------
   `.reveal` on its own does NOTHING. The hidden start-state
   only exists under `.js-ready`, so a page whose script never
   ran simply renders every element at rest, in place.

   `--i` is set inline in the HTML on staggered groups and only
   ever feeds a transition-delay, so a missing value degrades
   to "arrives with its neighbours", never to "does not arrive".

   The other failure mode — the script running but its
   callbacks never firing, because rAF and IntersectionObserver
   are both suspended while a document is not being painted
   (background tab, lazily-attached iframe, prerender, some
   in-app webviews) — is handled by the rAF watchdog in
   main.js. It is deliberately NOT handled here: a timed CSS
   animation cannot tell "the environment is broken" from "this
   element is simply still below the fold", so it would cancel
   every scroll reveal on the page.
   ============================================================ */

/* Opacity and transform share one duration on purpose. They used
   to run on --dur-med / --dur-slow, which meant the fade reached
   full opacity 580ms before the slide finished settling — content
   read as "fully arrived" while it was visibly still moving,
   which is the "hasn't quite finished" look. Landing together
   reads as one deliberate settle instead of two overlapping ones. */
.js-ready .reveal {
  opacity: 0;
  transform: translateY(1.25rem);
  transition:
    opacity var(--dur-slow) var(--ease-out),
    transform var(--dur-slow) var(--ease-out);
  transition-delay: calc(var(--i, 0) * var(--stagger));
}

.js-ready .reveal.is-in {
  opacity: 1;
  transform: none;
}

/* --- The eyebrow rule draws out from its start ---------------
   `.eyebrow__dash` is a hairline before (and, when centred,
   after) the label. It grows along its own axis rather than
   fading, which reads as an engraver's rule being pulled. */
.js-ready .reveal .eyebrow__dash {
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform var(--dur-slow) var(--ease-out);
  transition-delay: calc(var(--i, 0) * var(--stagger) + var(--dur-fast));
}

.js-ready .reveal.is-in .eyebrow__dash {
  transform: none;
}

/* The centred eyebrow has a dash on each side; the trailing one
   should grow the other way so the pair opens outward from the
   words rather than both marching right. */
.js-ready .eyebrow--centred.reveal .eyebrow__dash:last-child {
  transform-origin: right center;
}

/* --- Layered detail inside a plate ---------------------------
   The silhouette settles a beat after its frame, so the card
   reads as two planes arriving rather than one block sliding.
   Only transform moves; the resting opacity of `.plate__hat`
   belongs to components.css and must not be stomped. */
.js-ready .plate .plate__hat {
  transform: scale(1.06);
  transition: transform var(--dur-slow) var(--ease-out);
  transition-delay: calc(var(--i, 0) * var(--stagger) + var(--dur-fast));
}

.js-ready .plate.is-in .plate__hat {
  transform: none;
}

/* --- The quote carousel crossfade -----------------------------
   Opacity only, on both the outgoing and incoming panel, so the
   two genuinely cross rather than one popping in after the other
   pops out. `main.js` swaps `.is-active` from one `.quote` to the
   next; this is the only rule that has to react to that class —
   the resting/hidden states are components.css's job. */
.quote {
  transition: opacity var(--dur-slow) var(--ease-soft);
}


/* ============================================================
   2. THE GILD — the signature
   ------------------------------------------------------------
   base.css gives `.gild` a 300%-wide gradient parked at
   `background-position: 100% 0`, which shows the last third of
   the ramp: flat parchment. Sweeping the position leftwards
   rakes the specular gold-leaf crest across the letterforms.

   The maths. With `background-size: 300%`, a position of X
   offsets the image by -2W * X, where W is the element width.
   The pattern therefore repeats every 150% of X, and every
   X ≡ 100% (mod 150%) shows the same flat parchment. Running
   100% -> -50% is exactly ONE full rake: the crest crosses the
   glyphs once and the animation lands back where it started.

   That endpoint is deliberate. Stopping literally at -100%
   would freeze the highlight mid-word, and any interruption
   would leave the text half-lit. -50% is visually identical to
   the resting rule, so the animation needs no fill-mode at
   all: before it, during its delay, and after it ends, the
   word is plain, readable parchment.

   Once, not looped. A gilder lays leaf and the light catches
   it as you walk past — it does not pulse.
   ============================================================ */

@keyframes gild-rake {
  from { background-position: 100% 0; }
  to   { background-position: -50% 0; }
}

/* Two ways in, one animation.

   `.reveal.is-in .gild` covers the hero word, which sits inside
   a reveal target and rakes as its line arrives.

   `.gild.is-in` covers the footer wordmark, which is not inside
   any reveal. main.js observes bare `.gild` elements separately
   and marks them `.is-in` when they scroll into view, so the
   foot of the page gets the same single, deliberate rake at the
   moment somebody is actually looking at it — rather than a
   slow infinite loop nobody asked for. */
.js-ready .reveal.is-in .gild,
.js-ready .gild.is-in {
  animation: gild-rake var(--dur-gild) var(--ease-soft)
             calc(var(--i, 0) * var(--stagger) + var(--dur-med)) 1;
}


/* ============================================================
   3. THE FILIGREE DRAW — the second signature
   ------------------------------------------------------------
   Every path in the ornament sprite carries `pathLength="1"`.
   That normalises each path's length to 1 user unit whatever
   its real geometry, so `stroke-dasharray: 1` is exactly one
   dash the length of the path, and `stroke-dashoffset: 1`
   pushes that dash entirely off the end — the line vanishes.
   Winding the offset back to 0 draws it, nib-first.

   Why it is set on the <svg> and not on the paths: the paths
   live inside a `<use>` shadow tree and cannot be reached by a
   descendant selector. `stroke`, `stroke-dasharray` and
   `stroke-dashoffset` are all INHERITED SVG properties, so a
   declaration on the host `.orn` element propagates into the
   shadow content — and because inheritance passes the parent's
   currently-computed value, the children follow the parent's
   transition frame by frame. This is the whole reason the
   sprite was authored with pathLength in the first place.

   Scope: ornaments that sit inside a reveal target, or that
   are a reveal target themselves. The footer fleuron is
   neither, so it is never hidden and never needs a script to
   bring it back.
   ============================================================ */

.js-ready .reveal .orn,
.js-ready .orn.reveal {
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
  transition:
    stroke-dashoffset var(--dur-draw) var(--ease-out),
    stroke var(--dur-med) var(--ease-soft),
    opacity var(--dur-med) var(--ease-out);
  transition-delay: calc(var(--i, 0) * var(--stagger) + var(--dur-fast));
}

.js-ready .reveal.is-in .orn,
.js-ready .orn.reveal.is-in {
  stroke-dashoffset: 0;
}

/* --- The four corner brackets draw in sequence ---------------
   Drawing all four together reads as a rubber stamp. Drawn
   clockwise from the top-left, a beat apart, the frame reads
   as something ruled by hand around the plate. The offsets are
   multiples of --stagger so they stay in step with the rest of
   the page if that token is retuned.

   `.orn--fleuron` gets no extra delay — it is a single
   horizontal flourish and should simply draw. */
/* Used to spread across a full 3×--stagger (270ms) on top of a
   950ms draw — the last corner of the first plate wasn't finished
   until ~2.2s after the card started revealing, well past a normal
   scroll-and-look dwell. Halved to 1.5×--stagger total spread so
   the whole frame resolves while a reader is still looking at it. */
.js-ready .reveal .orn--tl { transition-delay: calc(var(--i, 0) * var(--stagger) + var(--dur-fast)); }
.js-ready .reveal .orn--tr { transition-delay: calc(var(--i, 0) * var(--stagger) + var(--dur-fast) + 0.5 * var(--stagger)); }
.js-ready .reveal .orn--br { transition-delay: calc(var(--i, 0) * var(--stagger) + var(--dur-fast) + 1 * var(--stagger)); }
.js-ready .reveal .orn--bl { transition-delay: calc(var(--i, 0) * var(--stagger) + var(--dur-fast) + 1.5 * var(--stagger)); }

/* The fleuron is a wider figure than a corner bracket, so it
   gets the full draw duration and a touch more air in front of
   it — it is usually the last thing in its section. */
.js-ready .orn--fleuron.reveal {
  transition-delay: calc(var(--i, 0) * var(--stagger) + var(--dur-med));
}


/* ============================================================
   4. THE SWEEP — the same light, at hardware scale
   ------------------------------------------------------------
   `.btn__sweep` is a narrow diagonal band of gilt light that
   crosses the button once on hover or keyboard focus. It is
   80% as wide as its parent and rests at -150% of its own
   width (= -120% of the parent): fully parked off to the left.
   It travels to 250% (= 200% of the parent), fully off to the
   right. No fill-mode, so it silently re-parks on the left if
   the pointer leaves mid-sweep.

   The resting position belongs to components.css. The geometry
   is restated here defensively with identical values — a no-op
   if components.css already declares it, and insurance if the
   band would otherwise sit in the middle of the button with
   nothing to move it.
   ============================================================ */

@keyframes gilt-sweep {
  from { transform: translateX(-150%); }
  to   { transform: translateX(250%); }
}

.btn {
  position: relative;
  overflow: hidden;
  transition:
    color var(--dur-fast) var(--ease-soft),
    background-color var(--dur-fast) var(--ease-soft),
    border-color var(--dur-fast) var(--ease-soft),
    transform var(--dur-fast) var(--ease-out);
}

.btn__sweep {
  position: absolute;
  top: -2px;
  bottom: -2px;
  left: 0;
  width: 80%;
  z-index: 0;
  pointer-events: none;
  transform: translateX(-150%);
  will-change: transform;
  /* Fallback first: rgba tokens, understood everywhere. */
  background-image: linear-gradient(
    105deg,
    transparent 38%,
    var(--gilt-dim) 50%,
    transparent 62%
  );
  /* Then the real thing — a hot specular core, mixed from the
     gilt tokens so no hex is written here. */
  background-image: linear-gradient(
    105deg,
    transparent 36%,
    color-mix(in srgb, var(--gilt-hot) 34%, transparent) 46%,
    color-mix(in srgb, var(--gilt-hot) 72%, transparent) 50%,
    color-mix(in srgb, var(--gilt-hot) 34%, transparent) 54%,
    transparent 64%
  );
}

.btn:hover .btn__sweep,
.btn:focus-visible .btn__sweep {
  /* forwards: holds the band at 250% (parked off-screen right,
     same as its un-animated -150% resting position is off-screen
     left — both are outside .btn's overflow:hidden clip, so
     nothing visibly jumps) rather than leaving it to snap back to
     the resting declaration the instant the animation completes. */
  animation: gilt-sweep var(--dur-slow) var(--ease-soft) 1 forwards;
}

/* On a gilt-filled button a gilt band is invisible, so the
   light has to read as a dark core with hot edges instead —
   the way a raised edge on leaf actually catches. */
.btn--solid .btn__sweep {
  background-image: linear-gradient(
    105deg,
    transparent 36%,
    color-mix(in srgb, var(--gilt-hot) 60%, transparent) 45%,
    color-mix(in srgb, var(--pitch) 30%, transparent) 50%,
    color-mix(in srgb, var(--gilt-hot) 60%, transparent) 55%,
    transparent 64%
  );
}


/* ============================================================
   5. NAV — the condense
   ------------------------------------------------------------
   main.js adds `.is-stuck` to `[data-nav]` once the page has
   scrolled past the hero. Three things change together so it
   reads as one movement: the ground darkens and takes a blur,
   a gilt hairline appears along the bottom edge, and the bar
   loses a little height.

   The height is the awkward one. `--nav-h` is a token, and
   custom properties do not interpolate without @property, so
   the transition is put on a real length. `min-height` is
   restated at its resting value here — identical to the token
   components.css will be using — precisely so there is a
   number to transition FROM. Without that restatement the
   resting value could be `auto`, which does not interpolate,
   and the bar would snap instead of settle. Snapping would be
   ugly, not broken; the restatement makes it neither.
   ============================================================ */

.nav {
  transition:
    background-color var(--dur-med) var(--ease-soft),
    border-color var(--dur-med) var(--ease-soft),
    box-shadow var(--dur-med) var(--ease-soft),
    -webkit-backdrop-filter var(--dur-med) var(--ease-soft),
    backdrop-filter var(--dur-med) var(--ease-soft);
}

.nav.is-stuck {
  background-color: color-mix(in srgb, var(--pitch) 82%, transparent);
  border-bottom-color: var(--gilt-dim);
  box-shadow: 0 24px 60px -48px rgba(0, 0, 0, 0.95);
  -webkit-backdrop-filter: blur(14px) saturate(140%);
  backdrop-filter: blur(14px) saturate(140%);
}

.nav__inner {
  min-height: var(--nav-h);
  transition: min-height var(--dur-med) var(--ease-out);
}

.nav.is-stuck .nav__inner {
  min-height: calc(var(--nav-h) - 16px);
}

/* The crest closes up with the bar rather than staying put and
   leaving a gap where it used to sit. */
.nav__crest {
  transition:
    transform var(--dur-med) var(--ease-out),
    color var(--dur-fast) var(--ease-soft);
}

.nav.is-stuck .nav__crest {
  transform: scale(0.9);
}

.nav__link,
.nav__brand,
.drawer__link,
.drawer__tel,
.foot__list a,
.hero__scroll {
  transition: color var(--dur-fast) var(--ease-soft);
}


/* ============================================================
   6. BURGER -> CROSS
   ------------------------------------------------------------
   Two bars. They meet in the middle and cross. The rotation is
   the meaning, not decoration: the control tells you what it
   will do next.
   ============================================================ */

.burger__bar {
  transition:
    transform var(--dur-fast) var(--ease-out),
    background-color var(--dur-fast) var(--ease-soft);
}

.burger[aria-expanded="true"] .burger__bar:first-child {
  transform: translateY(3px) rotate(45deg);
}

.burger[aria-expanded="true"] .burger__bar:last-child {
  transform: translateY(-3px) rotate(-45deg);
}


/* ============================================================
   7. DRAWER
   ------------------------------------------------------------
   The [hidden] attribute kills transitions outright, so
   main.js removes `hidden` first and adds `.is-open` on the
   next frame; on close it removes `.is-open`, waits out
   --dur-med, then restores `hidden`.

   Scoped to `.js-ready` for the same reason as everything
   else: only the script can ever open this panel, so only the
   script's own class may be allowed to hide it.
   ============================================================ */

.js-ready .drawer {
  opacity: 0;
  transition: opacity var(--dur-med) var(--ease-soft);
}

.js-ready .drawer.is-open {
  opacity: 1;
}

/* The links arrive one after another, as a list being read. */
.js-ready .drawer .drawer__link {
  opacity: 0;
  transform: translateY(0.75rem);
  transition:
    opacity var(--dur-med) var(--ease-out),
    transform var(--dur-med) var(--ease-out),
    color var(--dur-fast) var(--ease-soft);
}

.js-ready .drawer.is-open .drawer__link {
  opacity: 1;
  transform: none;
}

.js-ready .drawer.is-open .drawer__link:nth-child(1) { transition-delay: calc(0 * var(--stagger) + 60ms); }
.js-ready .drawer.is-open .drawer__link:nth-child(2) { transition-delay: calc(1 * var(--stagger) + 60ms); }
.js-ready .drawer.is-open .drawer__link:nth-child(3) { transition-delay: calc(2 * var(--stagger) + 60ms); }
.js-ready .drawer.is-open .drawer__link:nth-child(4) { transition-delay: calc(3 * var(--stagger) + 60ms); }
.js-ready .drawer.is-open .drawer__link:nth-child(5) { transition-delay: calc(4 * var(--stagger) + 60ms); }
.js-ready .drawer.is-open .drawer__link:nth-child(6) { transition-delay: calc(5 * var(--stagger) + 60ms); }

.js-ready .drawer .drawer__foot {
  opacity: 0;
  transition: opacity var(--dur-med) var(--ease-soft);
  transition-delay: calc(6 * var(--stagger));
}

.js-ready .drawer.is-open .drawer__foot {
  opacity: 1;
}


/* ============================================================
   8. HERO AMBIENCE
   ------------------------------------------------------------
   One moving thing in the whole hero, and it is a functional
   one: the scroll rail. A light runs down it and is gone.
   ============================================================ */

@keyframes rail-fall {
  0%   { transform: scaleY(0); transform-origin: 50% 0;    opacity: 0; }
  20%  { opacity: 1; }
  50%  { transform: scaleY(1); transform-origin: 50% 0;    opacity: 1; }
  51%  { transform: scaleY(1); transform-origin: 50% 100%; opacity: 1; }
  85%  { transform: scaleY(0); transform-origin: 50% 100%; opacity: 0; }
  100% { transform: scaleY(0); transform-origin: 50% 100%; opacity: 0; }
}

.hero__scroll-rail {
  animation: rail-fall 3200ms var(--ease-soft) infinite;
  will-change: transform;
}


/* ============================================================
   9. HOVER CHOREOGRAPHY
   ------------------------------------------------------------
   Restraint. A card lifts by a few pixels, its hairline warms
   from the ghost gilt to the real thing, and the corner
   ornaments come up from --gilt-dim toward --gilt. Nothing
   scales dramatically, nothing tilts, nothing glows.

   `stroke` is inherited, so setting it on the host `.orn`
   reaches the paths inside the <use> shadow tree — the same
   mechanism as the draw in section 3.
   ============================================================ */

.plate,
.occ__row {
  transition:
    transform var(--dur-med) var(--ease-out),
    border-color var(--dur-med) var(--ease-soft),
    box-shadow var(--dur-med) var(--ease-soft),
    background-color var(--dur-med) var(--ease-soft);
}

.plate:hover,
.plate:focus-within {
  transform: translateY(-4px);
  border-color: var(--gilt-dim);
  box-shadow: var(--shadow-lift);
}

/* A list row has no card to lift, so it steps in from its
   margin instead — the reading equivalent of leaning forward. */
.occ__row:hover,
.occ__row:focus-within {
  transform: translateX(4px);
  border-color: var(--gilt-dim);
}

/* --- Ornaments warm on hover -------------------------------- */
.orn {
  transition: stroke var(--dur-med) var(--ease-soft);
}

.plate:hover .orn,
.plate:focus-within .orn,
.form-well:focus-within .orn {
  stroke: var(--gilt);
}

/* --- The silhouette breathes, barely -------------------------
   `.js-ready .plate.is-in .plate__hat { transform: none }` above
   is (0,4,0) — four classes. A plain `.plate:hover .plate__hat`
   is only (0,3,0), so once a card has revealed (which, on a real
   page, is effectively always by the time anyone can hover it)
   the settle rule permanently outranked this one and the breathe
   never fired — the "animation doesn't do anything" bug. Matching
   `.is-in` in the selector brings this rule to the same (0,4,0)
   specificity, and because this rule sits LATER in the file, the
   tie goes to it. The plain `.plate:hover` selector is kept
   alongside for the rare case a card is hovered or focused before
   its own reveal has fired. */
.plate__hat {
  transition: transform var(--dur-slow) var(--ease-out);
}

.plate:hover .plate__hat,
.plate:focus-within .plate__hat,
.plate.is-in:hover .plate__hat,
.plate.is-in:focus-within .plate__hat {
  transform: scale(1.035);
}

/* --- Occasion numeral and tag ------------------------------- */
.occ__num,
.occ__tag,
.plate__name {
  transition: color var(--dur-fast) var(--ease-soft);
}

/* --- Small print and footer links --------------------------- */
.materials__item {
  transition: color var(--dur-fast) var(--ease-soft);
}

/* --- The skip link ------------------------------------------
   It is off-canvas until focused; move it, do not fade it, so
   it cannot be half-present while a keyboard user reads it. */
.skip {
  transition: transform var(--dur-fast) var(--ease-out);
}


/* ============================================================
   10. THE MODE TOGGLE — commission or hire
   ------------------------------------------------------------
   components.css owns the whole visibility contract, and it is
   CSS-only:

     #modeBuy:checked  ~ .form__pane--hire { display: none; }
     #modeHire:checked ~ .form__pane--buy  { display: none; }

   and it owns where `.mode__thumb` sits for each state. This
   sheet adds exactly one thing to the marker: the transition
   that makes it slide instead of jump. If components.css
   changes the geometry, nothing here needs touching.
   ============================================================ */

.mode__thumb {
  transition:
    transform var(--dur-med) var(--ease-out),
    border-color var(--dur-med) var(--ease-soft),
    background-color var(--dur-med) var(--ease-soft);
  will-change: transform;
}

.mode__opt,
.mode__opt-title,
.mode__opt-sub {
  transition: color var(--dur-fast) var(--ease-soft);
}

/* --- Panes: fade the arrival, do not fight the display -------
   `display: none` cannot be transitioned, and trying to work
   around it — swapping in visibility, or absolute-positioning
   the inactive pane out of flow — would break the no-JS
   fallback, which is the one thing this form must not lose.

   So the pane is left to appear instantly and the CONTENTS are
   given a short entrance animation instead. Because the
   element goes from `display: none` to displayed, the browser
   restarts the animation each time the mode changes, which is
   exactly the trigger we want and costs no script at all.

   Deliberately NOT scoped to `.js-ready`: this is the one
   piece of motion the JS-free page should still get, and an
   animation with no fill-mode has no hidden start-state to
   strand — it ends at rest whatever happens.
   ============================================================ */

@keyframes pane-in {
  from { opacity: 0; transform: translateY(0.5rem); }
  to   { opacity: 1; transform: none; }
}

.form__pane {
  animation: pane-in var(--dur-med) var(--ease-out) 1 forwards;
}

.form__pane-label {
  animation: pane-in var(--dur-med) var(--ease-out) 60ms 1 both;
}


/* ============================================================
   11. FIELDS, ERRORS AND THE CONFIRMATION
   ============================================================ */

.field__input {
  transition:
    border-color var(--dur-fast) var(--ease-soft),
    background-color var(--dur-fast) var(--ease-soft),
    color var(--dur-fast) var(--ease-soft),
    box-shadow var(--dur-fast) var(--ease-soft);
}

.check__input,
.check__label {
  transition: color var(--dur-fast) var(--ease-soft);
}

/* --- The error line ------------------------------------------
   components.css keeps `.field__error` at `display: none` and
   flips it to `display: block` under `.field.is-invalid`, so
   the baseline works with no script and no motion at all.
   `display` cannot be transitioned, so the entrance is an
   animation on the newly-displayed element rather than a
   transition from a hidden start-state. Same reasoning as the
   panes above — and the same benefit: there is no start-state
   here that could strand the message.

   Scoped to `.js-ready` because only the script ever sets
   `.is-invalid`; the class cannot appear on a scriptless page,
   so the rule may as well say so. */
@keyframes error-in {
  from { opacity: 0; transform: translateY(-0.25rem); }
  to   { opacity: 1; transform: none; }
}

.js-ready .field.is-invalid .field__error {
  animation: error-in var(--dur-fast) var(--ease-out) 1 forwards;
}

/* The invalid input itself: colour only, no shake. A shake is
   a scold, and this is a hat shop. */
.field.is-invalid .field__input,
.field.is-invalid .check__input {
  transition:
    border-color var(--dur-fast) var(--ease-soft),
    box-shadow var(--dur-fast) var(--ease-soft);
}

/* --- The summary line ---------------------------------------
   role="status" aria-live="polite". It is announced whether or
   not it fades, so the fade is pure courtesy. */
.form__summary {
  transition: opacity var(--dur-fast) var(--ease-soft);
}

/* --- The confirmation panel ---------------------------------
   Fires the moment main.js removes the [hidden] attribute. The
   animation's `to` state already matches the element's natural
   resting style, so letting it revert to the un-animated cascade
   at the end was never actually visible — but "forwards"/"both"
   costs nothing and means that stays true even if a later edit
   changes the resting style and forgets to check this file. */
@keyframes rise-in {
  from { opacity: 0; transform: translateY(1.25rem); }
  to   { opacity: 1; transform: none; }
}

.form__done:not([hidden]) {
  animation: rise-in var(--dur-slow) var(--ease-out) 1 forwards;
}

/* The fleuron above the thank-you draws itself the same way
   every other ornament on the page does — this one keyed off
   the panel appearing rather than off a reveal. */
@keyframes fleuron-draw {
  from { stroke-dashoffset: 1; }
  to   { stroke-dashoffset: 0; }
}

.js-ready .form__done:not([hidden]) .orn--fleuron {
  stroke-dasharray: 1;
  animation: fleuron-draw var(--dur-draw) var(--ease-out) var(--dur-fast) 1 both;
}

.form__done:not([hidden]) .form__done-title {
  animation: rise-in var(--dur-slow) var(--ease-out) 120ms 1 both;
}

.form__done:not([hidden]) .form__done-body {
  animation: rise-in var(--dur-slow) var(--ease-out) 200ms 1 both;
}

/* The confirmation heading is given tabindex="-1" by main.js so
   focus can be moved to it; make sure that focus is visible and
   does not look like a mistake. */
.form__done-title:focus-visible {
  outline: 1px solid var(--gilt);
  outline-offset: 0.35em;
}


/* ============================================================
   12. FAQ
   ------------------------------------------------------------
   Two halves: the icon, and the panel.
   ============================================================ */

/* The plus becomes a minus by laying the upright bar flat.
   This assumes components.css builds `.faq__icon` as ::before
   (horizontal) + ::after (the same bar, rotated 90deg). Both
   are transitioned and both are driven to 0deg on open, so the
   icon resolves to a single horizontal rule whichever way
   round components.css has drawn it. */
.faq__icon::before,
.faq__icon::after {
  transition:
    transform var(--dur-med) var(--ease-out),
    opacity var(--dur-fast) var(--ease-soft),
    background-color var(--dur-fast) var(--ease-soft);
}

.faq__item[open] .faq__icon::before,
.faq__item[open] .faq__icon::after {
  transform: rotate(0deg);
}

.faq__q {
  transition: color var(--dur-fast) var(--ease-soft);
}

.faq__item {
  transition: border-color var(--dur-med) var(--ease-soft);
}

/* Panel open. `interpolate-size: allow-keywords` lets a modern
   UA interpolate to the `auto` height it would otherwise snap
   to. Older engines ignore both this and ::details-content
   entirely and simply open instantly — which is correct
   behaviour, not a broken fallback. The keyframed fade below
   covers both cases, so the answer never just appears. */
.faq {
  interpolate-size: allow-keywords;
}

.faq__item::details-content {
  block-size: 0;
  overflow: hidden;
  opacity: 0;
  /* `transition-behavior` is kept out of the shorthand on
     purpose: a parser that chokes on `allow-discrete` would
     drop the whole shorthand and take the size transition down
     with it. */
  transition:
    block-size var(--dur-med) var(--ease-out),
    content-visibility var(--dur-med) var(--ease-out),
    opacity var(--dur-fast) var(--ease-soft);
  transition-behavior: allow-discrete;
}

.faq__item[open]::details-content {
  block-size: auto;
  opacity: 1;
}

@keyframes answer-in {
  from { opacity: 0; transform: translateY(-0.375rem); }
  to   { opacity: 1; transform: none; }
}

.faq__item[open] .faq__a {
  animation: answer-in var(--dur-med) var(--ease-out) 1 forwards;
}


/* ============================================================
   13. prefers-reduced-motion: reduce
   ------------------------------------------------------------
   Everything present, everything at rest, nothing travelling.

   The important half of this block is not the killing — it is
   the guarantee that everything the reveal system and the
   filigree draw hide comes back. `.reveal` (with OR without
   `.is-in`) is forced to opacity 1 and no transform, and every
   `.orn` is forced to `stroke-dashoffset: 0`, so even if the
   observer never fires, nothing can be stranded invisible.

   !important is used freely here and nowhere else in this
   file. It is the correct tool: this block must win against
   every specificity in the project, including its own.
   ============================================================ */

@media (prefers-reduced-motion: reduce) {

  html { scroll-behavior: auto; }

  *,
  *::before,
  *::after {
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    animation-delay: 0ms !important;
    transition-duration: 120ms !important;
    transition-delay: 0ms !important;
  }

  /* --- Reveals resolve to rest, is-in or not ---------------- */
  .js-ready .reveal,
  .js-ready .reveal.is-in {
    opacity: 1 !important;
    transform: none !important;
    filter: none !important;
    transition: opacity 120ms linear !important;
  }

  .js-ready .reveal .eyebrow__dash,
  .js-ready .reveal.is-in .eyebrow__dash,
  .js-ready .plate .plate__hat,
  .js-ready .plate.is-in .plate__hat {
    transform: none !important;
  }

  /* --- The carousel still changes review, it just cuts rather
     than crossfades ------------------------------------------- */
  .quote { transition: none !important; }

  /* --- The filigree is simply drawn ------------------------- */
  .js-ready .reveal .orn,
  .js-ready .reveal.is-in .orn,
  .js-ready .orn.reveal,
  .js-ready .orn.reveal.is-in,
  .js-ready .form__done:not([hidden]) .orn--fleuron {
    stroke-dashoffset: 0 !important;
    animation: none !important;
    transition: stroke 120ms linear !important;
  }

  /* --- The gild stays lit, but never travels ---------------- */
  .gild,
  .js-ready .reveal.is-in .gild,
  .js-ready .gild.is-in {
    animation: none !important;
    background-position: 100% 0 !important;
  }

  /* --- No sweeps -------------------------------------------- */
  .btn__sweep,
  .btn:hover .btn__sweep,
  .btn:focus-visible .btn__sweep {
    animation: none !important;
    opacity: 0 !important;
  }

  /* --- No infinite loops ------------------------------------ */
  .hero__scroll-rail {
    animation: none !important;
    transform: none !important;
    opacity: 1 !important;
  }

  /* --- Nav: the ground may change, the height may not ------- */
  .nav__inner,
  .nav.is-stuck .nav__inner {
    min-height: var(--nav-h) !important;
    transition: none !important;
  }

  .nav__crest,
  .nav.is-stuck .nav__crest {
    transform: none !important;
  }

  /* --- Drawer: opacity only, no travel ---------------------- */
  .js-ready .drawer .drawer__link,
  .js-ready .drawer.is-open .drawer__link,
  .js-ready .drawer .drawer__foot,
  .js-ready .drawer.is-open .drawer__foot {
    transform: none !important;
  }

  .burger__bar,
  .burger[aria-expanded="true"] .burger__bar:first-child,
  .burger[aria-expanded="true"] .burger__bar:last-child {
    transition: none !important;
  }

  /* --- Cards sit still -------------------------------------- */
  .plate:hover,
  .plate:focus-within,
  .occ__row:hover,
  .occ__row:focus-within,
  .plate:hover .plate__hat,
  .plate:focus-within .plate__hat,
  .plate.is-in:hover .plate__hat,
  .plate.is-in:focus-within .plate__hat {
    transform: none !important;
  }

  /* --- Panes, errors and the confirmation: present at once -- */
  .form__pane,
  .form__pane-label,
  .js-ready .field.is-invalid .field__error,
  .form__done:not([hidden]),
  .form__done:not([hidden]) .form__done-title,
  .form__done:not([hidden]) .form__done-body,
  .faq__item[open] .faq__a {
    animation: none !important;
    opacity: 1 !important;
    transform: none !important;
  }

  /* --- The FAQ panel opens, it does not unfold -------------- */
  .faq__item::details-content,
  .faq__item[open]::details-content {
    block-size: auto;
    opacity: 1;
    transition: none !important;
  }

  /* --- The mode marker moves instantly ---------------------- */
  .mode__thumb {
    transition: none !important;
  }
}
