/* ==========================================================================
   Components: lockup, links, buttons, fields.
   ========================================================================== */

/* ── Brand lockup ───────────────────────────────────────────────────── */
.lockup { display: inline-flex; align-items: center; gap: var(--sp-3); }

/* The art sits in a fixed box so the Lottie can replace the inline SVG
   without the surrounding layout moving by a pixel. */
.lockup__art {
  display: block;
  flex: none;
  width: var(--lockup-size, 30px);
  height: var(--lockup-size, 30px);
}
.lockup__art > svg { width: 100%; height: 100%; }

/* Only the inline mark steps aside. Targeting bare `svg` would hide the
   Lottie's own SVG along with it and leave an empty box. */
.lockup__art.has-lottie > .lockup__mark { display: none; }

.lockup__mark { display: block; width: 100%; height: 100%; }

.lockup--sm { --lockup-size: 24px; }
.lockup--md { --lockup-size: 30px; }
.lockup--lg { --lockup-size: 44px; }
.lockup--xl { --lockup-size: 72px; }

.lockup__word {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 1.0625rem;
  letter-spacing: -0.02em;
  white-space: nowrap;
}
.lockup--lg .lockup__word { font-size: 1.5rem; }

/* ── The action link ────────────────────────────────────────────────── */
/* The primary interactive form on this site. A rule under the label that
   sweeps in from the left, and an arrow that leaves with it. No pills. */
.action {
  --action-fs: var(--fs-sm);
  display: inline-grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: var(--sp-6);
  min-height: 48px;
  padding-block: var(--sp-3);
  font-size: var(--action-fs);
  font-weight: 600;
  letter-spacing: 0.01em;
  position: relative;
  background: none;
  border: 0;
  cursor: pointer;
  text-align: left;
}

.action::before,
.action::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 1px;
}
.action::before { background: var(--hairline-strong); }
.action::after {
  background: var(--text);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--dur) var(--ease);
}
.action:hover::after,
.action:focus-visible::after { transform: scaleX(1); }

.action__arrow {
  display: inline-block;
  transition: transform var(--dur) var(--ease);
}
.action:hover .action__arrow { transform: translate(4px, -4px); }
.action--right .action__arrow { transform: none; }
.action--right:hover .action__arrow { transform: translateX(6px); }

.action--lg { --action-fs: var(--fs-md); min-height: 60px; }

/* Accent variant. Used once or twice a page, never as ornament. */
.action--accent { color: var(--accent-ink); }
.action--accent::after { background: var(--accent-500); }

/* ── Buttons ────────────────────────────────────────────────────────── */
/* A real button, because the typographic rows it replaced were not being
   recognised as one. Square-ish rather than a pill, so it still belongs to
   an engineered visual system — but with a fill, a border and padding, which
   is what people actually read as "press this". */
.btn {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  /* 44px is the touch-target floor; the pair has to sit on one line beside
     a headline, so it stops there rather than going bigger. */
  min-height: 44px;
  padding: var(--sp-2) var(--sp-4);
  border: 1px solid var(--hairline-strong);
  border-radius: var(--radius);
  font-size: var(--fs-xs);
  font-weight: 600;
  letter-spacing: 0.01em;
  color: var(--text);
  white-space: nowrap;
  cursor: pointer;
  transition: background var(--dur) var(--ease),
              border-color var(--dur) var(--ease),
              color var(--dur) var(--ease);
}

.btn:hover, .btn:focus-visible {
  border-color: var(--text);
  background: var(--surface, rgba(255, 255, 255, 0.05));
}

.btn__arrow { display: inline-block; transition: transform var(--dur) var(--ease); }
.btn:hover .btn__arrow { transform: translate(3px, -3px); }

/* The primary of the pair. One per group — two filled buttons side by side
   ask the reader to choose without telling them which is which. */
.btn--solid {
  background: var(--text);
  border-color: var(--text);
  color: var(--bg);
}
.btn--solid:hover, .btn--solid:focus-visible {
  background: var(--brand-400);
  border-color: var(--brand-400);
  color: #fff;
}

/* nowrap, so the pair reads as one row of choices rather than a stack that
   happens to be two buttons. */
.hero__actions {
  display: flex;
  flex-wrap: nowrap;
  gap: var(--sp-3);
  margin-top: var(--sp-2);
}

@media (prefers-reduced-motion: reduce) {
  .btn__arrow { transition: none; }
}

/* ── Inline text link ───────────────────────────────────────────────── */
.link {
  position: relative;
  padding-bottom: 2px;
  background-image: linear-gradient(currentColor, currentColor);
  background-size: 0% 1px;
  background-repeat: no-repeat;
  background-position: left bottom;
  transition: background-size var(--dur) var(--ease);
}
.link:hover, .link:focus-visible { background-size: 100% 1px; }

/* ── Fields ─────────────────────────────────────────────────────────── */
/* Underlined, not boxed: lighter on the page and closer to the editorial
   register the rest of the site is in. */
.field { display: grid; gap: var(--sp-2); }

.field__label {
  font-family: var(--font-mono);
  font-size: var(--fs-2xs);
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--text-dim);
}

.field__input {
  width: 100%;
  min-height: 52px;
  padding: var(--sp-3) 0;
  background: transparent;
  color: var(--text);
  border: 0;
  border-bottom: 1px solid var(--hairline-strong);
  border-radius: 0;
  font-family: var(--font-body);
  /* 16px minimum, or iOS zooms the page on focus. */
  font-size: 1rem;
  transition: border-color var(--dur) var(--ease);
}
.field__input:focus { outline: none; border-bottom-color: var(--text); }
.field__input:focus-visible { outline: none; border-bottom-color: var(--accent-500); }
.field__input--area { min-height: 7rem; resize: vertical; }

/* The native select arrow is chrome we do not control; hide it and draw the
   caret with the same mark the action links use. */
select.field__input {
  appearance: none;
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
                    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: right 6px center, right 1px center;
  background-size: 6px 6px, 6px 6px;
  background-repeat: no-repeat;
  padding-right: var(--sp-6);
}
select.field__input option { background: var(--bg-raised); color: var(--text); }

.field--invalid .field__input { border-bottom-color: var(--danger); }
.field__error { font-size: var(--fs-xs); color: var(--danger); }

/* Off-screen rather than display:none — some bots skip hidden inputs. */
.honeypot {
  position: absolute;
  left: -9999px;
  width: 1px; height: 1px;
  overflow: hidden;
}

/* ── Toasts ─────────────────────────────────────────────────────────── */
/* Confirmations belong out of the reading flow. An inline block pushed the
   whole form down on submit, which moved the thing you were looking at.

   Server-rendered, so they exist without script; script only adds the slide
   and the timer. Bottom-right on a pointer, bottom-centre on a phone where
   the thumb and the corner disagree. */
.toasts {
  position: fixed;
  z-index: 1300;
  right: var(--gutter-safe);
  bottom: clamp(1rem, 3vw, 2rem);
  display: grid;
  gap: var(--sp-3);
  width: min(26rem, calc(100vw - var(--gutter-safe) * 2));
  pointer-events: none;
}

.toast {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: start;
  gap: var(--sp-4);
  padding: var(--sp-4) var(--sp-5);
  background: var(--bg-raised);
  border: 1px solid var(--hairline);
  /* The one place a shadow is warranted: the toast floats above content and
     needs to read as a separate layer rather than a panel in the page. */
  box-shadow: 0 18px 40px -18px rgba(0, 0, 0, 0.55);
  pointer-events: auto;
}

/* The status mark: a small square in the accent, echoing the gold stop. */
.toast__mark {
  width: 8px; height: 8px;
  margin-top: 0.45em;
  background: var(--success);
  flex: none;
}
.toast--error .toast__mark { background: var(--danger); }

.toast__text {
  font-size: var(--fs-sm);
  line-height: 1.5;
  color: var(--text);
}

.toast__close {
  width: 28px; height: 28px;
  margin: -4px -6px 0 0;
  display: grid; place-items: center;
  background: none;
  border: 0;
  color: var(--text-dim);
  font-size: var(--fs-xs);
  cursor: pointer;
  transition: color var(--dur-fast) var(--ease);
}
.toast__close:hover { color: var(--text); }

/* The moving states are added by script, so a toast without JavaScript is
   simply a visible message rather than one stuck off-screen. */
.js .toast {
  opacity: 0;
  transform: translateY(12px);
  transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.js .toast.is-in { opacity: 1; transform: none; }
.js .toast.is-out { opacity: 0; transform: translateY(8px); }

@media (max-width: 620px) {
  .toasts {
    left: var(--gutter-safe);
    right: var(--gutter-safe);
    width: auto;
  }
}

@media (prefers-reduced-motion: reduce) {
  .js .toast { opacity: 1; transform: none; transition: none; }
}

/* ── Entry loader ───────────────────────────────────────────────────── */
/* Three seconds on a first arrival, then out of the way. theme.js adds the
   gate in <head>, before <body> exists, so no page content can flash first. */
.entry[hidden] { display: none; }
.entry-pending .entry[hidden] { display: grid; }

/* The page remains rendered and measurable, but nothing behind the branded
   entry can paint until its strict three-second deadline. */
.entry-pending body > :not(.entry) { visibility: hidden; }

.entry {
  position: fixed;
  inset: 0;
  z-index: 1400;
  display: grid;
  place-items: center;
  background: var(--bg);
  transition: opacity 620ms var(--ease), visibility 620ms;
}
.entry.is-done { opacity: 0; visibility: hidden; pointer-events: none; }

.entry__art { width: clamp(120px, 26vw, 200px); aspect-ratio: 1; }
.entry__art > svg { width: 100%; height: 100%; }
.entry__art .lockup { display: block; width: 100%; height: 100%; }
.entry__art .lockup__art { width: 100%; height: 100%; }
.entry__art.has-lottie .lockup { display: none; }

/* Nothing behind the veil should scroll while it is up. */
.is-entering body { overflow: hidden; }

@media (prefers-reduced-motion: reduce) {
  .entry { display: none; }
}

/* ── Busy buttons ───────────────────────────────────────────────────── */
/* A submit that gives no feedback gets pressed twice. Rather than bolting a
   borrowed spinner onto the button, the rule it already carries — the one
   that sweeps in from the left on hover — becomes an indeterminate bar in
   gold. The motion is the one already in the design language, so waiting
   looks like part of the site rather than a widget dropped into it. */
.action[data-busy="true"] {
  pointer-events: none;
  color: var(--text-muted);
}
.action[data-busy="true"] .action__arrow {
  opacity: 0;
  transition: opacity var(--dur-fast) var(--ease);
}

.action[data-busy="true"]::after {
  background: var(--accent-500);
  transform: none;
  animation: action-sweep 1150ms var(--ease-inout) infinite;
}

/* Enters from the left, fills, then leaves to the right — a loop that reads
   as progress without ever claiming to know how much is left. */
@keyframes action-sweep {
  0%   { clip-path: inset(0 100% 0 0); }
  45%  { clip-path: inset(0 0 0 0); }
  55%  { clip-path: inset(0 0 0 0); }
  100% { clip-path: inset(0 0 0 100%); }
}

/* The label breathes with it, so the button reads as busy even where the
   1px rule is easy to miss. */
.action[data-busy="true"] [data-busy-label] {
  animation: action-pulse 1150ms var(--ease-inout) infinite;
}
@keyframes action-pulse {
  0%, 100% { opacity: 0.55; }
  50%      { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .action[data-busy="true"]::after {
    animation: none;
    transform: scaleX(1);
  }
  .action[data-busy="true"] [data-busy-label] { animation: none; opacity: 1; }
}

/* ── Media frame ────────────────────────────────────────────────────── */
/* Images are framed by their own edges, not by rounded cards. The ratio is
   set on the frame so the box is reserved before the file arrives. */
.frame {
  /* display:block matters: these are often <span> elements, and an inline
     box ignores aspect-ratio and overflow entirely — which collapsed the
     whole gallery to zero height. */
  display: block;
  position: relative;
  overflow: hidden;
  background: var(--bg-panel);
  aspect-ratio: var(--frame-ratio, 3 / 2);
}
.frame > img,
.frame > video {
  width: 100%; height: 100%;
  object-fit: cover;
  transition: transform var(--dur-slow) var(--ease);
}
/* Ratios are classes, not an inline custom property: `style-src 'self'`
   forbids style attributes, so an inline ratio would silently not apply and
   the reserved box would collapse. */
.frame--landscape { --frame-ratio: 3 / 2; }
.frame--wide { --frame-ratio: 16 / 9; }
.frame--portrait { --frame-ratio: 4 / 5; }
.frame--square { --frame-ratio: 1 / 1; }

/* The placeholder that stands in until real media is supplied. Deliberately
   plain and labelled, so it can never be mistaken for finished design. */
.frame--placeholder {
  display: grid;
  place-content: center;
  gap: var(--sp-2);
  border: 1px dashed var(--hairline-strong);
  text-align: center;
  padding: var(--sp-5);
}
.frame__placeholder-label {
  font-family: var(--font-mono);
  font-size: var(--fs-2xs);
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--text-dim);
}
.frame__placeholder-note { font-size: var(--fs-xs); color: var(--text-dim); }

@media (prefers-reduced-motion: reduce) {
  /* The frames scale their image on hover and the action links sweep a rule
     across; neither should move for someone who asked for stillness. */
  .frame > img,
  .frame > video,
  .action::after,
  .action__arrow { transition: none; }
}
