/* ==========================================================================
   components.css — nav, buttons, cards, feed, forms, modal, toast, footer.
   ========================================================================== */

/* ==========================================================================
   Buttons — pill, with the small square glyph from the reference
   ========================================================================== */
.btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-3);
  padding: 0.95em 1.5em;
  border: 1px solid transparent;
  border-radius: var(--radius-pill);
  font-size: var(--step-sm);
  font-weight: 600;
  letter-spacing: -0.01em;
  white-space: nowrap;
  cursor: pointer;
  /* Removes the legacy 300ms click delay without disabling pinch-zoom on the
     page as a whole. */
  touch-action: manipulation;
  transition:
    background-color var(--dur) var(--ease),
    border-color var(--dur) var(--ease),
    color var(--dur) var(--ease),
    transform var(--dur-fast) var(--ease);
}

/* Sized by its label, a 13px pill lands around 38px tall — under the 44px a
   fingertip actually needs. Only touch pointers pay the extra height, so the
   desktop proportions are untouched. */
@media (pointer: coarse) {
  .btn { min-height: var(--tap); }
  .btn--ghost { min-height: 0; padding: 0.7em 0.9em; }
}

/* The trailing glyph: a small rotated square that spins on hover. */
.btn::after {
  content: '';
  width: 7px;
  height: 7px;
  flex: none;
  background: currentColor;
  transform: rotate(45deg);
  transition: transform var(--dur) var(--ease);
}

.btn--noglyph::after { content: none; }

.btn:not(:disabled):hover::after { transform: rotate(135deg); }
.btn:not(:disabled):active { transform: translateY(1px); }

.btn:disabled,
.btn[aria-disabled='true'] {
  opacity: 0.4;
  cursor: not-allowed;
  transform: none;
}

/* White pill on dark — the reference's primary. */
.btn--primary {
  background: var(--text);
  color: var(--ink);
}

.btn--primary:hover { background: var(--sun); }

/* Gold pill, for the moments that should shout. */
.btn--sun {
  background: var(--sun);
  color: var(--ink);
}

.btn--sun:hover { background: var(--sun-deep); color: var(--text); }

/* Filled dark pill sitting on a card. */
.btn--fill {
  background: var(--surface-2);
  color: var(--text);
  border-color: var(--line);
}

.btn--fill:hover { background: var(--sun); color: var(--ink); border-color: var(--sun); }

.btn--outline {
  border-color: var(--line-strong);
  color: var(--text);
}

.btn--outline:hover { background: var(--text); color: var(--ink); border-color: var(--text); }

.btn--ghost {
  padding: 0.5em 0.85em;
  color: var(--text-soft);
}

.btn--ghost::after { content: none; }
.btn--ghost:hover { color: var(--text); }

.btn--lg { padding: 1.1em 1.9em; font-size: var(--step-0); }

.btn--block { width: 100%; white-space: normal; }

.btn__icon { width: 1.05em; height: 1.05em; flex: none; }

.btn[aria-busy='true'] { color: transparent; pointer-events: none; }
.btn[aria-busy='true']::after {
  position: absolute;
  inset: 0;
  margin: auto;
  width: 1.05em;
  height: 1.05em;
  border: 2px solid rgba(0, 0, 0, 0.25);
  border-top-color: var(--ink);
  border-radius: 50%;
  background: none;
  transform: none;
  animation: spin 640ms linear infinite;
}

.btn--outline[aria-busy='true']::after,
.btn--fill[aria-busy='true']::after {
  border-color: var(--line-strong);
  border-top-color: var(--text);
}

@keyframes spin { to { transform: rotate(360deg); } }

@media (prefers-reduced-motion: reduce) {
  .btn[aria-busy='true']::after { animation-duration: 1.8s; }
}

/* ==========================================================================
   Header — transparent over the hero, hairline-divided cells
   ========================================================================== */
.site-header {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: var(--z-nav);
  border-bottom: 1px solid var(--line);
  transition: background-color var(--dur) var(--ease), backdrop-filter var(--dur) var(--ease);
}

/* anim.js adds .is-stuck once the hero is scrolled past. */
.site-header.is-stuck {
  background: rgba(11, 11, 11, 0.82);
  -webkit-backdrop-filter: blur(16px);
  backdrop-filter: blur(16px);
}

/* No backdrop-filter (older Android WebViews, and anything with GPU
   compositing disabled) would leave the header translucent over moving
   content. Fall back to an opaque bar. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .site-header.is-stuck { background: var(--ink); }
}

/* The drawer is open: the header must be opaque regardless of scroll
   position, or the nav links show through it. */
.site-header:has(.mobile-nav.is-open) { background: var(--ink-2); }

.site-header__inner {
  display: flex;
  align-items: stretch;
  height: var(--nav-height);
  padding-inline: 0;
  max-width: none;
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  flex: none;
  min-width: 0;
  /* The header is a full-bleed bar, so it carries the notch inset itself. */
  padding-inline: calc(var(--page-x) + var(--safe-l)) var(--page-x);
  /* The vertical rule after the wordmark is straight from the reference. */
  border-right: 1px solid var(--line);
  font-size: 1rem;
  font-weight: 700;
  letter-spacing: -0.03em;
  line-height: 1.05;
  color: var(--text);
}

.brand__mark {
  width: 28px;
  height: 28px;
  flex: none;
  background: none;
  transition: transform var(--dur-slow) var(--ease-out-expo);
}

.brand:hover .brand__mark { transform: rotate(180deg); }

/* Two lines, so a three-word name does not force the nav off the screen. */
.brand__name { display: block; }
.brand__name em { display: block; font-style: normal; color: var(--sun); }

.nav {
  display: none;
  align-items: center;
  gap: var(--space-6);
  margin-left: auto;
  padding-inline: var(--space-6);
}

.nav__link {
  position: relative;
  color: var(--text-soft);
  font-size: var(--step-sm);
  font-weight: 500;
  letter-spacing: -0.01em;
  transition: color var(--dur) var(--ease);
}

.nav__link::before {
  content: '+';
  margin-right: 2px;
  color: var(--text-muted);
  transition: color var(--dur) var(--ease);
}

.nav__link:hover,
.nav__link[aria-current='page'] { color: var(--sun); }

.nav__link:hover::before,
.nav__link[aria-current='page']::before { color: var(--sun); }

/* Far-right CTA cell, boxed by its own vertical rule. */
.site-header__cta {
  display: none;
  align-items: center;
  gap: var(--space-4);
  flex: none;
  padding-inline: var(--page-x) calc(var(--page-x) + var(--safe-r));
  border-left: 1px solid var(--line);
}

/* :not(.btn) matters. Without it this rule (specificity 0,1,1) beats
   .btn--primary (0,1,0) and paints the button label white on a white pill,
   i.e. invisible. */
.site-header__cta a:not(.btn) {
  font-size: var(--step-sm);
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--text);
  transition: color var(--dur) var(--ease);
}

.site-header__cta a:not(.btn):hover { color: var(--sun); }

/* ---------- Log in vs Join free ---------------------------------------
   These are different actions and must not read as the same control.
   Log in is a quiet hairline pill (a return trip); Join free is solid gold
   (the thing we actually want). They differ at rest, on hover and on press:
   the outline fills white and lifts, the gold darkens and presses IN.
   ---------------------------------------------------------------------- */
.btn--login {
  border-color: var(--line-strong);
  background: transparent;
  color: var(--text);
}

.btn--login:hover {
  background: var(--text);
  border-color: var(--text);
  color: var(--ink);
  transform: translateY(-1px);
}

.btn--login:active {
  background: var(--surface-2);
  border-color: var(--line-strong);
  color: var(--text);
  transform: translateY(1px);
}

.btn--join {
  background: var(--sun);
  border-color: var(--sun);
  color: var(--ink);
  font-weight: 700;
  box-shadow: 0 0 0 0 rgba(255, 196, 0, 0.5);
}

.btn--join:hover {
  background: var(--sun-deep);
  border-color: var(--sun-deep);
  color: var(--ink);
  transform: translateY(-1px);
  box-shadow: 0 8px 22px rgba(255, 196, 0, 0.28);
}

.btn--join:active {
  background: var(--green);
  border-color: var(--green);
  color: var(--ink);
  transform: translateY(1px);
  box-shadow: none;
}

[data-auth='out'] { display: contents; }
[data-auth='in'] { display: none; }
body.is-signed-in [data-auth='out'] { display: none; }
body.is-signed-in [data-auth='in'] { display: contents; }

.nav-toggle {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 5px;
  /* Never narrower than a fingertip, whatever --nav-height is doing. */
  width: max(var(--nav-height), var(--tap));
  min-height: var(--tap);
  flex: none;
  margin-left: auto;
  padding-inline: var(--space-4) calc(var(--space-4) + var(--safe-r));
  border-left: 1px solid var(--line);
  touch-action: manipulation;
}

/* Fixed bar width rather than 100% of the padding box: the glyph then stays
   the same size no matter how the surrounding cell is sized. */
.nav-toggle__bar {
  display: block;
  height: 1.5px;
  width: 22px;
  background: var(--text);
  transition: transform var(--dur) var(--ease), opacity var(--dur-fast) var(--ease);
}

.nav-toggle[aria-expanded='true'] .nav-toggle__bar:nth-child(1) { transform: translateY(6.5px) rotate(45deg); }
.nav-toggle[aria-expanded='true'] .nav-toggle__bar:nth-child(2) { opacity: 0; }
.nav-toggle[aria-expanded='true'] .nav-toggle__bar:nth-child(3) { transform: translateY(-6.5px) rotate(-45deg); }

/* ---------- Mobile drawer ----------------------------------------------
   Closed state is `visibility: hidden` rather than the [hidden] attribute.
   [hidden] resolves to display:none, which cancels the max-height transition
   outright — the drawer used to snap open and snap shut. visibility still
   removes it from the accessibility tree and from the tab order, and its
   transition is delayed to the end of the collapse so the panel stays
   paintable while it animates away.

   [hidden] is kept on the element in the markup for the no-JS case, and
   nav.js removes it the moment it takes control.
   ------------------------------------------------------------------------ */
.mobile-nav {
  position: fixed;
  inset: var(--nav-height) 0 auto;
  z-index: var(--z-drawer);
  background: var(--ink-2);
  border-bottom: 1px solid var(--line);
  overflow: hidden;
  visibility: hidden;
  max-height: 0;
  transition:
    max-height var(--dur) var(--ease),
    visibility 0s linear var(--dur);
}

.mobile-nav.is-open {
  visibility: visible;
  /* svh, not vh: on iOS Safari vh is the *expanded* viewport, so a vh-sized
     drawer runs underneath the collapsed URL bar and the last link cannot be
     reached. */
  max-height: calc(100svh - var(--nav-height));
  /* Long menus scroll inside the panel; contain stops that scroll chaining
     into the page behind it once the list hits its end. */
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  transition: max-height var(--dur) var(--ease), visibility 0s;
}

/* Selected via the parent purely for specificity. base.css resets list
   padding with `ul[class]`, which is (0,1,1) and so beats a lone class — the
   drawer's gutter was being dropped and every link sat hard against the left
   edge of the screen. Two classes is (0,2,0), which wins. */
.mobile-nav .mobile-nav__list {
  padding:
    var(--space-2)
    calc(var(--page-x) + var(--safe-r))
    calc(var(--space-6) + var(--safe-b))
    calc(var(--page-x) + var(--safe-l));
}

.mobile-nav__list > li > a {
  display: flex;
  align-items: center;
  min-height: var(--tap);
  padding: var(--space-4) 0;
  border-bottom: 1px solid var(--line);
  font-size: var(--step-2);
  font-weight: 600;
  letter-spacing: var(--tracking-snug);
}

.mobile-nav__list > li > a:hover,
.mobile-nav__list > li > a:active { color: var(--sun); }

.mobile-nav__actions {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding-top: var(--space-5);
}

/* Tapping the page behind the open drawer should close it, not select text
   through it. The scrim is inside the drawer's stacking context but pinned to
   the viewport, and sits below the panel background. */
.nav-scrim {
  position: fixed;
  inset: var(--nav-height) 0 0;
  z-index: calc(var(--z-drawer) - 1);
  background: rgba(11, 11, 11, 0.55);
  opacity: 0;
  visibility: hidden;
  /* iOS honours overflow:hidden on <body> only loosely — a drag that starts
     over the page can still pan it behind the drawer. Refusing to treat any
     touch on the scrim as a pan gesture is what actually holds it still. */
  touch-action: none;
  transition: opacity var(--dur) var(--ease), visibility 0s linear var(--dur);
}

.nav-scrim.is-open { opacity: 1; visibility: visible; transition: opacity var(--dur) var(--ease), visibility 0s; }

@media (min-width: 1000px) {
  .nav, .site-header__cta { display: flex; }
  .nav-toggle { display: none; }
  .mobile-nav, .nav-scrim { display: none !important; }
}

/* ==========================================================================
   Avatar
   ========================================================================== */
.avatar {
  --avatar-size: 38px;
  position: relative;
  flex: none;
  width: var(--avatar-size);
  height: var(--avatar-size);
  border-radius: 50%;
  overflow: hidden;
  background: var(--grad-flag);
  display: grid;
  place-items: center;
}

.avatar img { width: 100%; height: 100%; object-fit: cover; background: transparent; }

.avatar__initial {
  font-size: calc(var(--avatar-size) * 0.42);
  font-weight: 700;
  line-height: 1;
  color: var(--ink);
  text-transform: uppercase;
  user-select: none;
}

.avatar--lg { --avatar-size: 76px; }
.avatar--xl { --avatar-size: 116px; }
.avatar--ring { box-shadow: 0 0 0 1px var(--line-strong), 0 0 0 4px rgba(11, 11, 11, 0.6); }

/* ==========================================================================
   Badges
   ========================================================================== */
.badge {
  display: inline-flex;
  align-items: center;
  gap: 0.4em;
  padding: 0.35em 0.7em;
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
  font-size: var(--step-xs);
  font-weight: 600;
  letter-spacing: 0.02em;
  white-space: nowrap;
  color: var(--text-soft);
}

.badge--audio { background: var(--red-wash); border-color: transparent; color: var(--red-soft); }
.badge--image { background: var(--green-wash); border-color: transparent; color: var(--green-soft); }
.badge--sun { background: var(--sun-wash); border-color: transparent; color: var(--sun); }
.badge--green { background: var(--green-wash); border-color: transparent; color: var(--green-soft); }

.badge__icon { width: 0.85em; height: 0.85em; }

/* ==========================================================================
   Post card
   ========================================================================== */
.post {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  overflow: hidden;
  transition: border-color var(--dur) var(--ease);
}

.post:hover { border-color: var(--line-strong); }

.post__head {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-4);
}

.post__creator { display: flex; align-items: center; gap: var(--space-3); min-width: 0; }

.post__creator:hover .post__username { color: var(--sun); }

.post__byline { min-width: 0; line-height: 1.3; }

.post__username {
  display: block;
  font-weight: 600;
  font-size: var(--step-sm);
  letter-spacing: -0.01em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  transition: color var(--dur) var(--ease);
}

.post__date { display: block; color: var(--text-muted); font-size: var(--step-xs); }

.post__head-end { margin-left: auto; display: flex; align-items: center; gap: var(--space-2); }

.post__media { position: relative; background: var(--ink-2); line-height: 0; }

/* svh so a tall photo is capped against the *small* viewport. With vh the cap
   is computed against the expanded one, and the image ends up taller than the
   screen actually is while the URL bar is showing. */
.post__media img { width: 100%; max-height: 76vh; max-height: 76svh; object-fit: cover; }

/* Audio slot: a gold-washed well with cover art and the native player. */
.post__audio {
  padding: var(--space-4);
  background: linear-gradient(160deg, rgba(245, 166, 35, 0.14) 0%, rgba(245, 166, 35, 0.03) 100%);
  border-block: 1px solid var(--line);
}

.post__audio-top { display: flex; align-items: center; gap: var(--space-4); }

.post__cover {
  flex: none;
  width: 78px; height: 78px;
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--grad-cover);
  display: grid;
  place-items: center;
}

.post__cover img { width: 100%; height: 100%; object-fit: cover; background: transparent; }
.post__cover-glyph { width: 44%; height: 44%; color: rgba(255, 255, 255, 0.85); }

.post__audio-meta { min-width: 0; flex: 1; }
.post__audio-label { display: flex; align-items: center; gap: var(--space-2); margin-bottom: var(--space-2); }

.post__audio-title {
  font-size: var(--step-2);
  font-weight: 700;
  line-height: var(--leading-snug);
  letter-spacing: var(--tracking-snug);
  overflow-wrap: anywhere;
}

/* Native controls kept: keyboard, screen readers and OS media keys come free.
   Inverted so the light default chrome reads on the dark card. */
.post__player {
  width: 100%;
  height: 38px;
  margin-top: var(--space-4);
  border-radius: var(--radius-pill);
  background: rgba(255, 255, 255, 0.08);
  filter: invert(1) hue-rotate(180deg);
}

/* The native scrub handle is only a few pixels tall inside a 38px control.
   Touch pointers get the platform default height, where the whole strip is
   draggable. */
@media (pointer: coarse) { .post__player { height: 44px; } }

/* Cover art beside the title squeezes a long track name into two words per
   line on a 320px screen. Below that, art goes above the metadata. */
@media (max-width: 380px) {
  .post__audio-top { flex-direction: column; align-items: flex-start; gap: var(--space-3); }
  .post__cover { width: 92px; height: 92px; }
}

.post__body { padding: var(--space-4); }
.post__caption { color: var(--text-soft); overflow-wrap: anywhere; }
.post__caption--empty { color: var(--text-muted); font-style: italic; }

.post__foot {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: 0 var(--space-4) var(--space-4);
}

.post__foot .text-meta { margin-right: auto; }

.report-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.4em;
  padding: 0.4em 0.75em;
  border-radius: var(--radius-pill);
  color: var(--text-muted);
  font-size: var(--step-xs);
  font-weight: 600;
  transition: color var(--dur) var(--ease), background-color var(--dur) var(--ease);
}

.report-btn:hover { background: var(--red-wash); color: var(--red-soft); }
.report-btn svg { width: 0.9em; height: 0.9em; }

@media (pointer: coarse) { .report-btn { min-height: 38px; padding-inline: 0.9em; } }

/* ==========================================================================
   Feed list + sentinel
   ========================================================================== */
.feed { display: flex; flex-direction: column; gap: var(--space-5); }

.feed__sentinel {
  min-height: 60px;
  padding: var(--space-7) 0 var(--space-8);
  text-align: center;
  color: var(--text-muted);
  font-size: var(--step-sm);
}

.feed__sentinel[data-state='loading']::after {
  content: '';
  display: inline-block;
  width: 24px; height: 24px;
  border: 2px solid var(--line-strong);
  border-top-color: var(--sun);
  border-radius: 50%;
  animation: spin 700ms linear infinite;
}

.feed__sentinel[data-state='end']::after {
  content: '✳  you have reached the end of the stream';
  letter-spacing: 0.02em;
}

.feed__sentinel[data-state='error'] .feed__retry { display: inline-flex; }
.feed__retry { display: none; }

.feed__skeleton { display: flex; flex-direction: column; gap: var(--space-5); }

.skeleton-card {
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--surface);
  padding: var(--space-4);
}

.skeleton-line {
  height: 11px;
  border-radius: var(--radius-pill);
  background: linear-gradient(90deg, rgba(255,255,255,0.05) 0%, rgba(255,255,255,0.12) 50%, rgba(255,255,255,0.05) 100%);
  background-size: 220% 100%;
  animation: skeleton-sweep 1.5s var(--ease) infinite;
}

.skeleton-line + .skeleton-line { margin-top: var(--space-3); }
.skeleton-line--head { width: 45%; height: 16px; }
.skeleton-line--media { height: 230px; border-radius: var(--radius); margin-top: var(--space-4); }
.skeleton-line--short { width: 70%; }

@keyframes skeleton-sweep {
  from { background-position: 120% 0; }
  to { background-position: -120% 0; }
}

@media (prefers-reduced-motion: reduce) { .skeleton-line { animation: none; } }

.empty-state {
  padding: var(--space-8) var(--space-5);
  border: 1px dashed var(--line-strong);
  border-radius: var(--radius-lg);
  text-align: center;
  color: var(--text-soft);
}

.empty-state__glyph { width: 44px; height: 44px; margin: 0 auto var(--space-4); color: var(--sun); }
.empty-state h3 { margin-bottom: var(--space-2); color: var(--text); }

/* ==========================================================================
   Forms
   ========================================================================== */
.form { display: flex; flex-direction: column; gap: var(--space-5); }

.field { display: flex; flex-direction: column; gap: var(--space-2); }

.field__label { font-size: var(--step-sm); font-weight: 600; letter-spacing: -0.01em; }
.field__hint { color: var(--text-muted); font-size: var(--step-xs); }

.field__input,
.field__textarea,
.field__select {
  width: 100%;
  padding: 0.9em 1.1em;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--ink-2);
  color: var(--text);
  transition: border-color var(--dur) var(--ease), background-color var(--dur) var(--ease);
}

.field__textarea { min-height: 118px; line-height: var(--leading-body); }

/* iOS Safari zooms the whole page in when a focused field is set below 16px,
   and then does not zoom back out — which is why a tap on "Email address"
   used to leave the form scrolled sideways and half off screen. The body
   scale is 15px, so every control gets nudged up to 16px on touch pointers.
   Desktop keeps the tighter setting. */
@media (pointer: coarse) {
  .field__input,
  .field__textarea,
  .field__select,
  .search__input {
    font-size: 1rem;
    min-height: var(--tap);
  }

  .field__textarea { min-height: 140px; }
}

.field__input:focus,
.field__textarea:focus,
.field__select:focus {
  outline: none;
  border-color: var(--sun);
  background: var(--surface);
}

.field__input::placeholder,
.field__textarea::placeholder { color: var(--text-muted); }

.field__error { display: none; color: var(--red-soft); font-size: var(--step-xs); font-weight: 600; }
.field.has-error .field__input,
.field.has-error .field__textarea { border-color: var(--red); }
.field.has-error .field__error { display: block; }

.field__count {
  align-self: flex-end;
  color: var(--text-muted);
  font-size: var(--step-xs);
  font-variant-numeric: tabular-nums;
}

.field__count.is-over { color: var(--red-soft); font-weight: 700; }

.check {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-4);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--ink-2);
  cursor: pointer;
  transition: border-color var(--dur) var(--ease), background-color var(--dur) var(--ease);
}

.check:hover { border-color: var(--line-strong); }
.check input { flex: none; width: 20px; height: 20px; margin: 1px 0 0; accent-color: var(--sun); cursor: pointer; }
.check__text { font-size: var(--step-sm); line-height: var(--leading-snug); color: var(--text-soft); }
.check__text strong { color: var(--text); }
.check:has(input:checked) { border-color: var(--sun); background: var(--sun-wash); }

.panel {
  padding: var(--space-6);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--surface);
}

@media (min-width: 640px) { .panel { padding: var(--space-7); } }

.panel__head { margin-bottom: var(--space-6); }
.panel__head h1, .panel__head h2 { margin-bottom: var(--space-3); }

.divider {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  color: var(--text-muted);
  font-size: var(--step-xs);
  font-weight: 600;
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
}

.divider::before, .divider::after { content: ''; flex: 1; height: 1px; background: var(--line); }

.form-status {
  padding: var(--space-4);
  border-radius: var(--radius);
  font-size: var(--step-sm);
  font-weight: 500;
}

.form-status:empty { display: none; }
.form-status[data-tone='ok'] { background: var(--green-wash); color: var(--green-soft); }
.form-status[data-tone='error'] { background: var(--red-wash); color: var(--red-soft); }
.form-status[data-tone='info'] { background: var(--sun-wash); color: var(--sun); }

.dropzone {
  position: relative;
  display: grid;
  place-items: center;
  gap: var(--space-3);
  padding: var(--space-7) var(--space-5);
  border: 1px dashed var(--line-strong);
  border-radius: var(--radius-lg);
  background: var(--ink-2);
  text-align: center;
  cursor: pointer;
  transition: border-color var(--dur) var(--ease), background-color var(--dur) var(--ease);
}

.dropzone:hover, .dropzone.is-dragover { border-color: var(--sun); background: var(--sun-wash); }
.dropzone input[type='file'] { position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0; cursor: pointer; }
.dropzone__glyph { width: 40px; height: 40px; color: var(--sun); }
.dropzone__title { font-weight: 600; }

.file-preview {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-4);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--ink-2);
}

.file-preview[hidden] { display: none; }
.file-preview__thumb {
  flex: none;
  width: 68px; height: 68px;
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: var(--grad-cover);
  display: grid;
  place-items: center;
}
.file-preview__thumb img { width: 100%; height: 100%; object-fit: cover; background: transparent; }
.file-preview__meta { min-width: 0; flex: 1; }
.file-preview__name { font-weight: 600; word-break: break-all; font-size: var(--step-sm); }

.progress { height: 4px; border-radius: var(--radius-pill); background: var(--line); overflow: hidden; }
.progress[hidden] { display: none; }
.progress__bar {
  height: 100%; width: 35%;
  border-radius: var(--radius-pill);
  background: var(--grad-sun);
  animation: progress-slide 1.2s var(--ease) infinite;
}

@keyframes progress-slide {
  0% { transform: translateX(-100%); }
  100% { transform: translateX(340%); }
}

/* ---------- Search field (creators directory) -------------------------- */
.search {
  position: relative;
  display: flex;
  align-items: center;
}

.search__icon {
  position: absolute;
  left: 1.1rem;
  width: 17px; height: 17px;
  color: var(--text-muted);
  pointer-events: none;
}

.search__input {
  width: 100%;
  padding: 1em 3rem 1em 2.9rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
  background: var(--ink-2);
  color: var(--text);
  transition: border-color var(--dur) var(--ease), background-color var(--dur) var(--ease);
}

.search__input::placeholder { color: var(--text-muted); }
.search__input:focus { outline: none; border-color: var(--sun); background: var(--surface); }

.search__clear {
  position: absolute;
  right: 0.85rem;
  display: grid;
  place-items: center;
  width: 26px; height: 26px;
  border-radius: 50%;
  color: var(--text-muted);
  transition: background-color var(--dur) var(--ease), color var(--dur) var(--ease);
}

.search__clear[hidden] { display: none; }
.search__clear:hover { background: var(--surface-2); color: var(--text); }
.search__clear svg { width: 13px; height: 13px; }

@media (pointer: coarse) {
  .search__clear { width: 34px; height: 34px; right: 0.5rem; }
  .search__input { padding-right: 3.2rem; }
}

/* ---------- Filter chips ------------------------------------------------ */
.chip-row { display: flex; gap: var(--space-2); flex-wrap: wrap; }

.chip {
  touch-action: manipulation;
  padding: 0.5em 1em;
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--text-soft);
  font-size: var(--step-sm);
  font-weight: 500;
  letter-spacing: -0.01em;
  transition: border-color var(--dur) var(--ease), background-color var(--dur) var(--ease), color var(--dur) var(--ease);
}

.chip:hover { border-color: var(--line-strong); color: var(--text); }

.chip[aria-pressed='true'] {
  background: var(--sun);
  border-color: var(--sun);
  color: var(--ink);
  font-weight: 600;
}

@media (pointer: coarse) {
  .chip { min-height: 38px; padding-inline: 1.1em; }
}

/* ==========================================================================
   Modal
   ========================================================================== */
.modal {
  position: fixed;
  inset: 0;
  z-index: var(--z-modal);
  display: grid;
  place-items: center;
  padding:
    var(--space-4)
    calc(var(--space-4) + var(--safe-r))
    calc(var(--space-4) + var(--safe-b))
    calc(var(--space-4) + var(--safe-l));
  background: rgba(0, 0, 0, 0.72);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
}

.modal[hidden] { display: none; }

.modal__panel {
  width: 100%;
  max-width: 460px;
  /* dvh tracks the URL bar as it collapses, so the panel never extends under
     Safari's chrome and strand the submit button. vh first as the fallback
     for anything that does not know dvh. */
  max-height: 90vh;
  max-height: 88dvh;
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  padding: var(--space-6);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--surface);
  box-shadow: var(--shadow-lg);
  animation: modal-in var(--dur) var(--ease-out-expo);
}

@keyframes modal-in {
  from { opacity: 0; transform: translateY(16px) scale(0.985); }
  to { opacity: 1; transform: none; }
}

.modal__head { display: flex; align-items: flex-start; gap: var(--space-4); margin-bottom: var(--space-5); }
.modal__title { margin-right: auto; font-size: var(--step-2); }

.modal__close {
  flex: none;
  width: 34px; height: 34px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  color: var(--text-muted);
  transition: background-color var(--dur) var(--ease), color var(--dur) var(--ease);
}

.modal__close:hover { background: var(--surface-2); color: var(--text); }
.modal__close svg { width: 16px; height: 16px; }

.modal__actions { display: flex; gap: var(--space-3); justify-content: flex-end; margin-top: var(--space-5); }

@media (pointer: coarse) { .modal__close { width: var(--tap); height: var(--tap); } }

/* Two pills side by side overflow a 320px panel once each is 44px tall.
   Stack them and put the affirmative action on top, where the thumb is. */
@media (max-width: 419px) {
  .modal__actions { flex-direction: column-reverse; }
  .modal__actions .btn { width: 100%; }
}

.radio-list { display: flex; flex-direction: column; gap: var(--space-2); }

.radio-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  cursor: pointer;
  font-size: var(--step-sm);
  color: var(--text-soft);
  transition: border-color var(--dur) var(--ease), background-color var(--dur) var(--ease);
}

.radio-row:hover { border-color: var(--line-strong); }
.radio-row input { accent-color: var(--sun); width: 17px; height: 17px; flex: none; }

/* The whole row is the label, so growing the row grows the target — no need
   to inflate the radio glyph itself. */
@media (pointer: coarse) {
  .radio-row { min-height: var(--tap); padding: var(--space-3) var(--space-4); }
  .radio-row input { width: 20px; height: 20px; }
}
.radio-row:has(input:checked) { border-color: var(--sun); background: var(--sun-wash); color: var(--text); }

/* ==========================================================================
   Preview banner

   Pinned to the bottom rather than the top: the header is position:fixed, so
   anything at the top of the flow ends up underneath it and collides with the
   nav. Bottom also keeps it clear of the hero. Disappears on its own once real
   Supabase credentials are in config.js.
   ========================================================================== */
.preview-banner {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  /* Below the modal, above everything else. It used to sit at
     --z-toast - 10 (290), which is above --z-modal (200) — on a phone the
     report dialog is nearly full height, so the banner landed squarely on top
     of its Cancel button. Toasts still outrank it. */
  z-index: calc(var(--z-modal) - 10);
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding:
    var(--space-3)
    calc(var(--page-x) + var(--safe-r))
    calc(var(--space-3) + var(--safe-b))
    calc(var(--page-x) + var(--safe-l));
  border-top: 1px solid var(--line);
  background: rgba(19, 19, 19, 0.94);
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
  color: var(--text-soft);
  font-size: var(--step-xs);
}

.preview-banner[hidden] { display: none; }
.preview-banner p { margin-right: auto; }
.preview-banner strong { color: var(--sun); }

/* Three lines of copy plus a close button is most of a phone's bottom third.
   Tighten the leading and cap it so the footer stays reachable. */
@media (max-width: 639px) {
  .preview-banner { align-items: flex-start; gap: var(--space-3); }
  .preview-banner p { line-height: 1.35; }
}

.preview-banner__close {
  flex: none;
  display: grid;
  place-items: center;
  width: 28px; height: 28px;
  border: 1px solid var(--line);
  border-radius: 50%;
  color: var(--text-muted);
  transition: background-color var(--dur) var(--ease), color var(--dur) var(--ease);
}

.preview-banner__close:hover { background: var(--surface-2); color: var(--text); }
.preview-banner__close svg { width: 12px; height: 12px; }

@media (pointer: coarse) { .preview-banner__close { width: 36px; height: 36px; } }

/* ==========================================================================
   Toast
   ========================================================================== */
.toast-region {
  position: fixed;
  left: 50%;
  bottom: calc(var(--space-5) + var(--safe-b));
  z-index: var(--z-toast);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  /* 100vw includes the scrollbar gutter on desktop and would push the region
     off-centre; 100% of the viewport-width containing block does not. */
  width: min(420px, calc(100% - var(--space-6)));
  transform: translateX(-50%);
  pointer-events: none;
}

/* The preview banner owns the bottom edge while it is up — stack the toasts
   above it rather than on top of it. */
body.has-preview-banner .toast-region { bottom: calc(5.5rem + var(--safe-b)); }

@media (min-width: 640px) {
  body.has-preview-banner .toast-region { bottom: calc(3.5rem + var(--safe-b)); }
}

.toast {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-4);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface-2);
  color: var(--text);
  box-shadow: var(--shadow-lg);
  font-size: var(--step-sm);
  font-weight: 500;
  pointer-events: auto;
  animation: toast-in var(--dur) var(--ease-out-expo);
}

.toast[data-tone='ok'] { border-color: var(--green); }
.toast[data-tone='error'] { border-color: var(--red); }
.toast.is-leaving { animation: toast-out var(--dur) var(--ease) forwards; }

@keyframes toast-in { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: none; } }
@keyframes toast-out { to { opacity: 0; transform: translateY(10px); } }

/* ==========================================================================
   Footer
   ========================================================================== */
.site-footer {
  border-top: 1px solid var(--line);
  background: var(--ink);
  color: var(--text-muted);
  font-size: var(--step-sm);
}

.site-footer__grid {
  display: grid;
  gap: var(--space-6);
  grid-template-columns: 1fr;
  padding-block: var(--space-8);
}

@media (min-width: 760px) {
  .site-footer__grid { grid-template-columns: 1.6fr 1fr 1fr 1fr; gap: var(--space-7); }
}

.site-footer .brand { padding-inline: 0; border-right: 0; }

.site-footer h4 {
  margin-bottom: var(--space-4);
  color: var(--text);
  font-size: var(--step-xs);
  font-weight: 600;
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
}

/* The reset in base.css is `ul[class]`, and these lists carry no class — so
   they kept the UA disc marker and a 40px indent that pushed every footer
   link out of line with the heading above it. Reset them here rather than
   widening the base selector, which would also catch any prose list added to
   a page later. */
.site-footer ul {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: 0;
  list-style: none;
}
.site-footer a { transition: color var(--dur) var(--ease); }
.site-footer a:hover { color: var(--sun); }

/* 13px links stacked 12px apart are four separate 18px targets. On touch the
   link fills a full row so neighbouring entries stop competing for the tap. */
@media (pointer: coarse) {
  .site-footer ul { gap: 0; }
  .site-footer ul a { display: flex; align-items: center; min-height: var(--tap); }
}

/* Two columns of link lists rather than one long stack — the footer is
   otherwise the tallest thing on the page. */
@media (min-width: 420px) and (max-width: 759px) {
  .site-footer__grid { grid-template-columns: repeat(2, 1fr); gap: var(--space-6); }
  .site-footer__grid > :first-child { grid-column: 1 / -1; }
}

.site-footer__bottom {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3) var(--space-5);
  align-items: center;
  padding-block: var(--space-5);
  border-top: 1px solid var(--line);
  font-size: var(--step-xs);
}

.site-footer__bottom p { margin-right: auto; }

/* Oversized wordmark across the footer base, as in the reference. */
.footer-wordmark {
  display: block;
  padding-bottom: var(--space-4);
  font-size: clamp(3.5rem, 18vw, 17rem);
  font-weight: 700;
  line-height: 0.8;
  letter-spacing: -0.05em;
  color: transparent;
  -webkit-text-stroke: 1px var(--line-strong);
  user-select: none;
  overflow: hidden;
}
