/* Generated by includes/asset-bundle.php. Do not edit.
   Edit assets/css/components/*.css instead; this is rebuilt
   automatically whenever one of them changes. */

/* ===== assets/css/fonts.css ===== */
/* =========================================================
   Self-hosted fonts
   =========================================================
   Mirrors what the old Google Fonts link served, so nothing
   looks different, but the page no longer waits on (or sends
   every visitor's IP to) fonts.googleapis.com.

   Each file is a variable font covering a weight range, which
   is why one @font-face per character set replaces Google's
   one-per-weight list. Only latin and latin-ext are kept; any
   other script falls back to the system font for those
   characters.

   Space Grotesk tops out at 700. The site asks for 800 in a few
   places, and Google silently never served it, so those
   headings have always rendered at 700. Same here.

   The files are cached for a year (.htaccess), so a changed
   font must get a new filename, not be overwritten in place.

   All three families are SIL Open Font License 1.1; the licence
   and copyright notice travel inside each .woff2's metadata.
   ========================================================= */

@font-face {
  font-family: 'Space Grotesk';
  font-style: normal;
  font-weight: 600 700;
  font-display: swap;
  src: url("/assets/fonts/space-grotesk-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: 'Space Grotesk';
  font-style: normal;
  font-weight: 600 700;
  font-display: swap;
  src: url("/assets/fonts/space-grotesk-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'DM Sans';
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url("/assets/fonts/dm-sans-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: 'DM Sans';
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url("/assets/fonts/dm-sans-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url("/assets/fonts/jetbrains-mono-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url("/assets/fonts/jetbrains-mono-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}


/* ===== assets/css/style.css ===== */
/* =========================================================
   FluffyFile — Design Tokens
   ========================================================= */
:root {
  --bg-base: oklch(0.13 0.035 280);
  --bg-surface: oklch(0.19 0.055 285);
  --bg-surface-alt: oklch(0.25 0.075 288);
  --border-subtle: oklch(0.58 0.19 292 / 26%);
  --border-strong: oklch(0.58 0.19 292 / 40%);
  --text-primary: oklch(0.96 0.02 250);
  --text-muted: oklch(0.76 0.045 265);
  /* 72%, not 60%. At 60% this measured 3.88:1 on --bg-base, and it is
     what the footer copyright, all three footer legal links and the
     tool credits are painted in. 72% takes it to about 5.1:1. */
  --text-faint: oklch(0.76 0.045 265 / 72%);

  /* L 0.53, not 0.58. White text on the old value measured 4.13:1,
     under the 4.5:1 AA threshold, and this token is the fill behind the
     primary CTA, the back-to-top button, the process dots and every
     checkbox accent. Every one of its uses is a
     background or an accent-color - never a border or a glow - so
     darkening it is safe, and takes them all to 5.12:1 in one line. The
     gradient and shadow tokens hold their own oklch literals and are
     unaffected. */
  --accent-primary: oklch(0.53 0.19 292);
  --accent-secondary: oklch(0.85 0.15 202);
  --accent-sky: oklch(0.79 0.11 232);
  --accent-gold: oklch(0.78 0.135 84);
  --accent-gold-alt: oklch(0.63 0.09 62);
  --accent-error: oklch(0.7 0.19 25);
  --press-tint: oklch(0.85 0.15 202 / 14%);

  --gradient-primary: linear-gradient(100deg, oklch(0.58 0.19 292) 0%, oklch(0.7 0.16 262) 45%, oklch(0.85 0.15 202) 100%);
  --gradient-gold: linear-gradient(135deg, var(--accent-gold-alt), var(--accent-gold));
  --gradient-hero: radial-gradient(120% 90% at 50% 0%, oklch(0.42 0.17 288 / 48%) 0%, oklch(0.26 0.09 282 / 24%) 38%, oklch(0.13 0.035 280 / 0%) 72%);

  /* Primary CTA (.btn-primary). Violet into indigo and back, painted
     wider than the button so hover can slide it sideways. Both stops sit
     at or below L 0.53, so the light label clears AA across the whole
     fill: 5.12:1 on the violet, 6.03:1 on the indigo. Keep any new stop
     that dark - see the --accent-primary note above. */
  --accent-indigo: oklch(0.48 0.16 266);
  --gradient-cta: linear-gradient(110deg, var(--accent-primary) 0%, var(--accent-indigo) 50%, var(--accent-primary) 100%);
  --border-cta: oklch(0.7 0.16 292 / 45%);
  --shadow-cta: 0 1px 0 0 oklch(0.97 0.03 240 / 24%) inset, 0 12px 28px -14px oklch(0.58 0.19 292 / 70%);
  --shadow-cta-hover: 0 1px 0 0 oklch(0.97 0.03 240 / 24%) inset, 0 16px 36px -12px oklch(0.6 0.2 292 / 95%);

  --font-heading: 'Space Grotesk', system-ui, -apple-system, sans-serif;
  --font-body: 'DM Sans', system-ui, -apple-system, sans-serif;
  --font-mono: 'JetBrains Mono', ui-monospace, 'SFMono-Regular', Menlo, Consolas, monospace;

  --nav-height: 76px;
  --radius-sm: 8px;
  --radius-md: 12px;
  --radius-lg: 22px;
  /* One step beyond --radius-lg, for surfaces that should read as round
     rather than as rounded rectangles. Currently the services bubbles.
     Deliberately a token rather than an ad-hoc clamp() at the use site,
     so the shape scale stays a scale. */
  --radius-bubble: clamp(2rem, 5vw, 3.25rem);
  --radius-pill: 999px;

  --shadow-glow-violet: 0 0 0 1px oklch(0.85 0.15 202 / 26%), 0 1px 0 0 oklch(0.97 0.03 240 / 18%) inset, 0 18px 44px -18px oklch(0.58 0.19 292 / 65%);
  --shadow-glow-cyan: 0 0 0 1px oklch(0.85 0.15 202 / 26%), 0 26px 70px -30px oklch(0.85 0.15 202 / 45%);
  --shadow-card: 0 10px 30px rgba(0, 0, 0, 0.35);

  --transition-base: 0.3s ease;
  --transition-fast: 0.15s ease;

  --container-width: 1200px;
  --section-padding: clamp(2.5rem, 3vw, 3rem);

  /* Stacking scale. Every overlay in the site reads from this ladder so
     the order is declared in one place instead of emerging from whatever
     sequence the modal partials happen to be required in. The seven
     dialogs below --z-modal-backdrop/--z-modal are genuinely peers: only
     one is ever open at a time, apart from the legal pair, which is why
     the legal family gets its own rung above them. */
  --z-card-hover: 30;
  --z-back-to-top: 90;
  --z-nav-backdrop: 104;
  --z-nav-panel: 105;
  /* Above the mobile panel on purpose. .site-nav creates a stacking
     context, so the close button and the dropdowns inside it cannot
     climb past the panel on their own no matter what z-index they
     declare - the bar itself has to outrank it. Before this, the X
     stayed clickable only because the panel is offset below the bar. */
  --z-nav: 106;
  --z-dropdown: 110;
  --z-modal-backdrop: 200;
  --z-modal: 201;
  --z-modal-legal-backdrop: 210;
  --z-modal-legal: 211;
}

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

/* overflow-x: clip, not hidden. With hidden on both html and body, body
   becomes a scroll container of its own that never scrolls (the viewport
   does), and every position: sticky inside it stops sticking - the nav
   and the pricing table header included. clip trims the sideways
   overflow the same way without making a scroll container. hidden stays
   first as the fallback for browsers without clip (Safari before 16),
   which keep the old behaviour. */
html { scroll-behavior: smooth; overflow-x: hidden; overflow-x: clip; }

body {
  margin: 0;
  background: var(--bg-base);
  color: var(--text-primary);
  font-family: var(--font-body);
  line-height: 1.6;
  overflow-x: hidden;
  overflow-x: clip;
  -webkit-font-smoothing: antialiased;
}

html.nav-open { overflow: hidden; }
html.commission-modal-open { overflow: hidden; }
html.commission-auth-modal-open { overflow: hidden; }
html.terms-modal-open { overflow: hidden; }
html.review-modal-open { overflow: hidden; }
html.chat-modal-open { overflow: hidden; }
html.account-modal-open { overflow: hidden; }
html.report-modal-open { overflow: hidden; }

img { max-width: 100%; display: block; }
a { color: inherit; text-decoration: none; }
ul, ol { list-style: none; margin: 0; padding: 0; }
/* The `transition` here is the base easing for the press effect defined
   in the Buttons section below. Any class that declares its own
   `transition` shorthand overrides this, so those append `scale` to
   their own lists instead. */
button { font: inherit; cursor: pointer; }

h1, h2, h3, h4 {
  font-family: var(--font-heading);
  margin: 0;
  line-height: 1.15;
  letter-spacing: -0.02em;
}

p { margin: 0; }

/* =========================================================
   Layout utilities
   ========================================================= */

/* Text for screen readers only. Clipped rather than display:none or
   visibility:hidden, both of which take the text out of the
   accessibility tree along with the layout. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* First focusable thing on the page, off-screen until focused. Without
   it a keyboard visitor tabs through seven nav links on every page
   before reaching the content. */
.skip-link {
  position: absolute;
  top: 0.5rem;
  left: 0.5rem;
  z-index: calc(var(--z-nav) + 1);
  padding: 0.7rem 1.2rem;
  border-radius: var(--radius-sm);
  background: var(--bg-surface);
  border: 1px solid var(--border-strong);
  color: var(--text-primary);
  font-size: 0.9rem;
  font-weight: 600;
  transform: translateY(-160%);
  transition: transform var(--transition-fast);
}

.skip-link:focus-visible { transform: translateY(0); }

.container {
  width: 100%;
  max-width: var(--container-width);
  margin-inline: auto;
  padding-inline: clamp(1.25rem, 4vw, 2.5rem);
}

section {
  scroll-margin-top: var(--nav-height);
  padding-block: var(--section-padding);
  position: relative;
}

.section-header { max-width: 640px; margin-bottom: clamp(2.5rem, 6vw, 3.5rem); }

.section-header-row {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  flex-wrap: wrap;
  gap: 1rem;
  margin-bottom: clamp(2.5rem, 6vw, 3.5rem);
}

.section-header-row .section-header { margin-bottom: 0; }

.section-meta {
  font-family: var(--font-mono);
  font-size: 0.8rem;
  color: var(--accent-sky);
  letter-spacing: 0.02em;
  white-space: nowrap;
}

.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--accent-secondary);
  background: oklch(0.85 0.15 202 / 8%);
  border: 1px solid oklch(0.85 0.15 202 / 25%);
  padding: 0.4rem 0.9rem;
  border-radius: var(--radius-pill);
  margin-bottom: 1.25rem;
}

.section-title {
  font-size: clamp(1.9rem, 3.2vw + 1rem, 2.75rem);
  font-weight: 700;
  margin-bottom: 0.85rem;
}

.section-subtitle {
  color: var(--text-muted);
  font-size: clamp(1rem, 0.5vw + 0.9rem, 1.125rem);
}

/* =========================================================
   Icons

   Sized in em so an icon tracks the font-size of whatever it sits
   in, exactly like the Font Awesome <i> elements it replaced.

   fill: currentColor is what makes a single sprite work in every
   colour on the site. The symbols in assets/images/icons.svg carry
   no fill of their own, and fill inherits into the shadow tree that
   <use> creates, so an icon takes the colour of its container with
   no per-icon rule.
   ========================================================= */
.icon {
  width: 1em;
  height: 1em;
  fill: currentColor;
  vertical-align: -0.125em;
  flex-shrink: 0;
}

.text-gradient {
  background: var(--gradient-primary);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

/* =========================================================
   Buttons
   ========================================================= */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.6rem;
  padding: 0.9rem 1.75rem;
  border-radius: var(--radius-pill);
  font-weight: 600;
  font-size: 0.95rem;
  border: 1px solid transparent;
  transition: transform var(--transition-fast), box-shadow var(--transition-base), border-color var(--transition-base), color var(--transition-base);
  white-space: nowrap;
}

.btn:hover { transform: translateY(-3px); }

/* =========================================================
   Press feedback: a colour wash, never motion

   A shrink-on-press (`scale: 0.96`) used to live here and was removed:
   while `scale` animates, Chrome moves the button onto a GPU layer and
   redraws its text and 1px border differently, then switches back to
   normal painting when the animation ends. That switch was a visible
   flash on every click. Opacity and filter trigger the same swap, so
   they're out too.

   Instead a translucent cyan wash (the same hue as the hover borders
   and focus rings) is painted over the button's background while it's
   held. Only the background repaints - text and borders are untouched,
   so there is nothing to flash. It's a background-image layer, so it
   sits on top of whatever background-color the button already has
   and leaves box-shadow glows alone. It snaps on and off with no
   transition (gradients can't interpolate), like a native button.

   The :is()/:not() stack puts this at (0,3,0) specificity, above
   two-class states like `.account-modal-tab.is-active` that set the
   `background` shorthand and would otherwise wipe the wash. Excluded:
   buttons styled as bare text or icons, where a tinted box would read
   as a glitch rather than a press. A button with a gradient background
   of its own has to re-stack the wash on top of it, since both live in
   background-image.
   ========================================================= */
:is(button, .btn):active:not(:disabled, [aria-disabled="true"], .rf-star, .review-modal-link-btn, .nav-toggle) {
  background-image: linear-gradient(var(--press-tint), var(--press-tint));
}

/* The violet stays as background-color under the gradient, so anything
   that clears background-image still leaves a primary-looking button. */
.btn-primary {
  background-color: var(--accent-primary);
  background-image: var(--gradient-cta);
  background-size: 220% 100%;
  background-position: 0% 50%;
  border-color: var(--border-cta);
  color: var(--text-primary);
  box-shadow: var(--shadow-cta);
  transition: transform var(--transition-fast), box-shadow var(--transition-base), border-color var(--transition-base), color var(--transition-base), background-position 0.7s cubic-bezier(0.16, 1, 0.3, 1);
}

.btn-primary:hover:not(:disabled) {
  background-position: 100% 50%;
  box-shadow: var(--shadow-cta-hover);
}

.btn-primary:focus-visible { outline: 2px solid var(--accent-secondary); outline-offset: 3px; }

/* The press rule above sets background-image to the wash alone, which
   would flatten this button to plain violet while held. Same (0,3,0)
   weight and later in the file, so this wins and stacks the wash on top
   of the gradient instead. */
.btn-primary:active:not(:disabled, [aria-disabled="true"]) {
  background-image: linear-gradient(var(--press-tint), var(--press-tint)), var(--gradient-cta);
}

/* Every disabled primary button on the site is a form mid-send (sign in,
   commission, review, report, chat), usually with its spinner showing.
   So it keeps its look rather than greying out, and just stops reacting:
   no lift, no slide, no brighter glow. */
.btn-primary:disabled { cursor: progress; }
.btn-primary:disabled:hover { transform: none; }

.btn-outline {
  background: transparent;
  border-color: var(--border-strong);
  color: var(--text-primary);
}

.btn-outline:hover {
  border-color: oklch(0.85 0.15 202 / 60%);
  color: var(--accent-secondary);
}

/* =========================================================
   Effect utilities (ported from the reference's Tailwind @utility blocks)
   ========================================================= */
.gilded {
  border: 1px solid transparent;
  background-image: linear-gradient(var(--bg-surface), var(--bg-surface)),
    linear-gradient(140deg, oklch(0.85 0.15 202 / 50%), oklch(0.45 0.16 290 / 22%) 45%, oklch(0.7 0.18 292 / 40%));
  background-origin: border-box;
  background-clip: padding-box, border-box;
}

.ember-sheen { position: relative; overflow: hidden; }

.ember-sheen::after {
  content: '';
  position: absolute;
  inset: 0;
  background: radial-gradient(60% 40% at 15% 0%, oklch(0.85 0.15 202 / 14%), transparent 70%);
  pointer-events: none;
}

.sheen-hover { position: relative; overflow: hidden; }

.sheen-hover::before {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 65%;
  background: linear-gradient(90deg, transparent, oklch(0.95 0.05 220 / 14%), transparent);
  transform: translateX(-160%) skewX(-28deg);
  pointer-events: none;
}

.sheen-hover.is-sweeping::before { animation: sheen-sweep 0.9s ease-out; }

.grid-lines {
  background-image:
    linear-gradient(to right, oklch(0.85 0.15 202 / 10%) 1px, transparent 1px),
    linear-gradient(to bottom, oklch(0.85 0.15 202 / 10%) 1px, transparent 1px);
  background-size: 56px 56px;
  mask-image: radial-gradient(80% 60% at 50% 0%, black, transparent 75%);
  -webkit-mask-image: radial-gradient(80% 60% at 50% 0%, black, transparent 75%);
}

/* =========================================================
   Scroll reveal (driven by assets/js/modules/scroll-reveal.js
   via IntersectionObserver). Without JS nothing lifts the
   opacity, so index.php carries a <noscript> style that
   shows every [data-reveal] element outright.
   ========================================================= */
[data-reveal] {
  opacity: 0;
  transform: translateY(24px);
  transition: opacity 0.7s cubic-bezier(0.16, 1, 0.3, 1), transform 0.7s cubic-bezier(0.16, 1, 0.3, 1);
}

[data-reveal="left"] { transform: translateX(-24px); }
[data-reveal="right"] { transform: translateX(24px); }

[data-reveal].is-revealed {
  opacity: 1;
  transform: translate(0, 0);
}

@layer utilities {
  @supports (color: color-mix(in lab, red, red)) {
    .bg-surface\/40 {
      background-color: color-mix(in oklab, var(--bg-surface) 40%, transparent);
      border-top: 1px solid var(--border-subtle);
      border-bottom: 1px solid var(--border-subtle);
    }
  }
}

/* =========================================================
   Keyframes
   ========================================================= */
@keyframes sheen-sweep {
  0% { transform: translateX(-160%) skewX(-28deg); }
  100% { transform: translateX(240%) skewX(-28deg); }
}

/* =========================================================
   Responsive
   ========================================================= */
@media (max-width: 640px) {
  .section-header-row { flex-direction: column; align-items: flex-start; }
}

/* =========================================================
   Accessibility — reduced motion
   ========================================================= */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
  html { scroll-behavior: auto; }
}


/* ===== assets/css/components/ambient-background.css ===== */
/* =========================================================
   Ambient background (page-wide, fixed) — blobs + grid + embers
   ========================================================= */
.ambient-bg {
  position: fixed;
  inset: 0;
  z-index: -5;
  overflow: hidden;
  pointer-events: none;
}

/* The soft blobs are radial gradients, not blurred discs. They used to be
   solid circles under filter: blur(120-140px) while animating scale(),
   which makes the browser recompute a huge Gaussian blur every frame -
   the heaviest thing on the page, and slow enough on desktop Safari to
   stall the whole tab. A gradient is drawn once and then only moved,
   which the compositor does for free.

   Each box is the old circle grown by the old blur radius on every side,
   with the gradient holding full strength over the old core and fading
   out across the old blur band, so the size, colour and position read
   the same as before. */
.ambient-blob { position: absolute; }

.ambient-blob-1 {
  top: -17.5rem; left: -15.5rem; width: 49rem; height: 49rem;
  background: radial-gradient(closest-side, oklch(0.58 0.19 292 / 12%) 0%, oklch(0.58 0.19 292 / 12%) 35%, oklch(0.58 0.19 292 / 5%) 65%, transparent 100%);
  animation: drift-slow 26s ease-in-out infinite;
}

.ambient-blob-2 {
  top: calc(33% - 8rem); right: -18rem; width: 46rem; height: 46rem;
  background: radial-gradient(closest-side, oklch(0.85 0.15 202 / 10%) 0%, oklch(0.85 0.15 202 / 10%) 30%, oklch(0.85 0.15 202 / 4%) 62%, transparent 100%);
  animation: drift-slower 34s ease-in-out infinite;
}

.ambient-blob-3 {
  bottom: -22.75rem; left: calc(33% - 8.75rem); width: 45.5rem; height: 45.5rem;
  background: radial-gradient(closest-side, oklch(0.78 0.135 84 / 10%) 0%, oklch(0.78 0.135 84 / 10%) 23%, oklch(0.78 0.135 84 / 4%) 58%, transparent 100%);
  animation: breathe 12s ease-in-out infinite;
}

/* The grid drifts by transform, not background-position. Animating
   background-position repaints the whole viewport every frame; moving a
   layer 56px (one grid cell, so the loop is seamless) is a composite.
   The lines live on ::before so the fade-out mask from .grid-lines stays
   put on the parent instead of sliding with them. */
.ambient-grid {
  position: absolute;
  inset: 0;
  opacity: 0.18;
  overflow: hidden;
  background-image: none;
}

.ambient-grid::before {
  content: '';
  position: absolute;
  top: -56px;
  left: -56px;
  right: 0;
  bottom: 0;
  background-image:
    linear-gradient(to right, oklch(0.85 0.15 202 / 10%) 1px, transparent 1px),
    linear-gradient(to bottom, oklch(0.85 0.15 202 / 10%) 1px, transparent 1px);
  background-size: 56px 56px;
  animation: grid-drift 14s linear infinite;
}

.ember {
  position: absolute;
  bottom: -3rem;
  border-radius: 50%;
  background: var(--accent-secondary);
  box-shadow: 0 0 8px 2px oklch(0.85 0.15 202 / 45%);
  animation-name: ember;
  animation-timing-function: linear;
  animation-iteration-count: infinite;
}

/* =========================================================
   Keyframes
   ========================================================= */
@keyframes drift-slow {
  0%, 100% { transform: translate3d(0, 0, 0) scale(1); }
  50% { transform: translate3d(6rem, 3rem, 0) scale(1.12); }
}

@keyframes drift-slower {
  0%, 100% { transform: translate3d(0, 0, 0) scale(1.05); }
  50% { transform: translate3d(-5rem, -4rem, 0) scale(0.95); }
}

@keyframes breathe {
  0%, 100% { opacity: 0.5; transform: scale(0.95); }
  50% { opacity: 1; transform: scale(1.1); }
}

@keyframes grid-drift {
  from { transform: translate3d(0, 0, 0); }
  to { transform: translate3d(56px, 56px, 0); }
}

@keyframes ember {
  0% { transform: translate3d(0, 0, 0); opacity: 0; }
  12% { opacity: 0.9; }
  70% { opacity: 0.55; }
  100% { transform: translate3d(var(--ember-drift, 20px), -105vh, 0); opacity: 0; }
}

/* =========================================================
   Phones and touch devices

   This layer was the single most expensive thing on the site on
   mobile. Three elements running filter: blur(120-140px) over
   surfaces up to 544px square, each animating scale() - which
   invalidates the compositor's cached blur every frame, so the
   Gaussian is recomputed at 60fps - plus a full-viewport masked
   gradient animating background-position (a paint, not a
   composite), plus nine glowing embers. Thirteen infinite
   animations, all running before the first paint of any content,
   under a sticky nav that was backdrop-filtering the result.

   Below the layout breakpoint, all of it is replaced by a single
   static radial gradient in the same violet family. One paint, no
   blur, no animation. Desktop is untouched.

   (hover: none) is included so tablets and touch laptops get the
   cheap version too, not just narrow screens.
   ========================================================= */
@media (max-width: 900px), (hover: none) {
  .ambient-bg {
    background:
      radial-gradient(60% 40% at 8% 0%, oklch(0.58 0.19 292 / 14%), transparent 70%),
      radial-gradient(50% 35% at 100% 38%, oklch(0.85 0.15 202 / 10%), transparent 70%),
      radial-gradient(55% 30% at 45% 100%, oklch(0.78 0.135 84 / 8%), transparent 70%);
  }

  .ambient-blob,
  .ember { display: none; }

  .ambient-grid { opacity: 0.1; }
  .ambient-grid::before { animation: none; }
}


/* ===== assets/css/components/nav.css ===== */
/* =========================================================
   Nav
   ========================================================= */
/* Near-opaque instead of backdrop-filter: blur, on every screen size.
   The nav is sticky and sits over the animated ambient background for
   the whole visit, so a backdrop blur has to re-sample and re-blur what
   is behind it on every frame. Phones dropped it for that reason first;
   once the nav actually started sticking on desktop too (overflow-x:
   clip in style.css), the same per-frame cost landed on desktop
   browsers, worst in Safari. Behind an 80%-dark bar the blur was barely
   visible anyway. */
.site-nav {
  position: sticky;
  top: 0;
  z-index: var(--z-nav);
  height: var(--nav-height);
  display: flex;
  align-items: center;
  background: oklch(0.13 0.035 280 / 96%);
  border-bottom: 1px solid var(--border-subtle);
}

.nav-inner {
  width: 100%;
  max-width: var(--container-width);
  margin-inline: auto;
  padding-inline: clamp(1.25rem, 4vw, 2.5rem);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
}

.logo {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  font-family: var(--font-heading);
  font-size: 0.85rem;
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--text-primary);
}

.logo-mark {
  height: 32px;
  width: auto;
  flex-shrink: 0;
}

.logo-accent {
  background: var(--gradient-primary);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.nav-links { display: flex; align-items: center; gap: 2rem; }

.nav-link {
  position: relative;
  font-size: 0.95rem;
  font-weight: 500;
  color: var(--text-muted);
  padding-block: 0.4rem;
  transition: color var(--transition-fast);
}

.nav-link::after {
  content: '';
  position: absolute;
  left: 0;
  bottom: 0;
  height: 2px;
  width: 100%;
  background: var(--gradient-primary);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--transition-base);
}

.nav-link:hover, .nav-link.active { color: var(--accent-secondary); }
.nav-link:hover::after, .nav-link.active::after { transform: scaleX(1); }

.nav-actions { display: flex; align-items: center; gap: 1rem; }
.nav-cta { padding: 0.65rem 1.4rem; font-size: 0.875rem; }

.nav-toggle {
  display: none;
  background: none;
  border: none;
  color: var(--text-primary);
  font-size: 1.4rem;
  padding: 0.4rem;
  z-index: var(--z-dropdown);
}

/* A bottom sheet, not a floating card: anchored flush to the screen's own
   edges (left/right/bottom) so it reads as attached to the device rather
   than adrift with margins on every side. Only the top corners round off;
   the bottom ones stay square since they meet the viewport edge. */
.nav-mobile-panel {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  max-height: min(680px, 85vh);
  background: linear-gradient(165deg, var(--bg-surface-alt), var(--bg-surface));
  border: 1px solid var(--border-strong);
  border-bottom: none;
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  box-shadow: var(--shadow-glow-violet);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  transform: translateY(100%);
  transition: transform var(--transition-base);
  z-index: var(--z-nav-panel);
}

.nav-mobile-handle {
  width: 40px;
  height: 4px;
  margin: 0.65rem auto 0.15rem;
  flex-shrink: 0;
  border-radius: var(--radius-pill);
  background: var(--border-strong);
}

.nav-mobile-scroll {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 1rem;
  padding: 0.75rem clamp(1.25rem, 5vw, 1.75rem) max(1.5rem, calc(1rem + env(safe-area-inset-bottom)));
  overflow-y: auto;
  scrollbar-width: none;
  -ms-overflow-style: none;
}

.nav-mobile-scroll::-webkit-scrollbar { display: none; }

/* Overflow cue for a panel that hides its scrollbar: now a plain layer
   pinned to the shell's own bottom, since the handle above needs to stay
   put while only .nav-mobile-scroll scrolls beneath it. */
.nav-mobile-fade {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 2.75rem;
  background: linear-gradient(to top, var(--bg-surface), oklch(0.19 0.055 285 / 0%));
  pointer-events: none;
}

html.nav-open .nav-mobile-panel { transform: translateY(0); }

.nav-mobile-group {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  padding: 0.85rem 0.75rem;
  background: oklch(0.19 0.055 285 / 42%);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  animation: navGroupIn 0.32s cubic-bezier(0.16, 1, 0.3, 1) both;
}

.nav-mobile-group[hidden] { display: none; }

@keyframes navGroupIn {
  from { opacity: 0; transform: translateY(10px); }
  to { opacity: 1; transform: translateY(0); }
}

.nav-mobile-scroll > *:nth-child(1) { animation-delay: 0s; }
.nav-mobile-scroll > *:nth-child(2) { animation-delay: 0.05s; }
.nav-mobile-scroll > *:nth-child(3) { animation-delay: 0.1s; }
.nav-mobile-scroll > *:nth-child(4) { animation-delay: 0.15s; }
.nav-mobile-scroll > *:nth-child(5) { animation-delay: 0.2s; }

.nav-mobile-label {
  padding-inline: 0.25rem;
  font-family: var(--font-heading);
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-faint);
}

.nav-mobile-label-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.nav-mobile-label-row .nav-mobile-label { padding-inline: 0; }

.nav-mobile-count-pill {
  padding: 0.15rem 0.55rem;
  border-radius: var(--radius-pill);
  background: oklch(0.85 0.15 202 / 16%);
  color: var(--accent-secondary);
  font-size: 0.7rem;
  font-weight: 700;
}

.nav-mobile-links {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
}

.nav-mobile-links .nav-link {
  display: flex;
  align-items: center;
  padding: 0.65rem 0.75rem;
  border-radius: var(--radius-sm);
  color: var(--text-muted);
  font-size: 1.05rem;
  transition: background var(--transition-fast), color var(--transition-fast);
}

.nav-mobile-links .nav-link::after { content: none; }

.nav-mobile-links .nav-link:hover {
  background: oklch(0.85 0.15 202 / 10%);
  color: var(--text-primary);
}

.nav-mobile-links .nav-link.active,
.nav-mobile-links .nav-link[aria-current="page"] {
  background: var(--accent-primary);
  color: var(--text-primary);
  box-shadow: var(--shadow-glow-violet);
}

.nav-mobile-messages {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  max-height: 220px;
  overflow-y: auto;
  scrollbar-width: none;
  -ms-overflow-style: none;
}

.nav-mobile-messages::-webkit-scrollbar { display: none; }

.nav-mobile-message-item {
  display: flex;
  align-items: center;
  gap: 0.65rem;
  padding: 0.6rem 0.7rem;
  border-radius: var(--radius-sm);
  transition: background var(--transition-fast);
}

.nav-mobile-message-item:hover { background: oklch(0.85 0.15 202 / 10%); }

.nav-mobile-message-avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  flex-shrink: 0;
  overflow: hidden;
  border-radius: 50%;
  background: var(--gradient-primary);
  color: var(--text-primary);
  font-weight: 700;
  font-size: 0.8rem;
}

.nav-mobile-message-avatar img { width: 100%; height: 100%; object-fit: cover; }

.nav-mobile-message-text {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  min-width: 0;
  flex: 1;
}

.nav-mobile-message-title {
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--text-primary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.nav-mobile-message-meta {
  font-size: 0.72rem;
  color: var(--text-faint);
}

.nav-mobile-message-badge {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.3rem;
  height: 1.3rem;
  padding: 0 0.35rem;
  border-radius: var(--radius-pill);
  background: var(--accent-primary);
  color: var(--text-primary);
  font-size: 0.7rem;
  font-weight: 700;
}

.nav-mobile-promo {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  padding: 1.1rem;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-md);
  background: oklch(0.19 0.055 285 / 50%);
  box-shadow: 0 0 0 1px oklch(0.85 0.15 202 / 18%);
  animation: navGroupIn 0.32s cubic-bezier(0.16, 1, 0.3, 1) both;
}

.nav-mobile-promo-text {
  font-size: 0.85rem;
  line-height: 1.45;
  color: var(--text-muted);
  text-align: center;
}

.nav-mobile-promo-cta {
  align-self: center;
}

.nav-mobile-footer {
  display: flex;
  flex-direction: column;
  gap: 0.85rem;
  padding: 0.85rem 0.75rem;
  background: oklch(0.19 0.055 285 / 42%);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  animation: navGroupIn 0.32s cubic-bezier(0.16, 1, 0.3, 1) both;
}

/* Guests: Sign in + Sign up, side by side. */
.nav-mobile-auth {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0.6rem;
}

.nav-mobile-auth .btn { padding-inline: 1rem; }

@media (prefers-reduced-motion: reduce) {
  .nav-mobile-group, .user-chip-mobile, .nav-mobile-footer, .nav-mobile-promo {
    animation: none;
  }
}

.nav-backdrop {
  position: fixed;
  inset: 0;
  background: oklch(0.13 0.035 280 / 60%);
  backdrop-filter: blur(4px);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--transition-base);
  z-index: var(--z-nav-backdrop);
}

html.nav-open .nav-backdrop { opacity: 1; pointer-events: auto; }

/* =========================================================
   Responsive
   ========================================================= */
@media (max-width: 1100px) {
  .nav-links, .nav-actions .desktop-only { display: none; }
  .nav-toggle { display: block; }
}

@media (min-width: 1101px) {
  .nav-mobile-panel, .nav-backdrop { display: none; }
}

/* =========================================================
   Phones and touch devices

   The mobile menu's backdrop swaps its small blur for a flatter scrim.
   (.site-nav itself no longer blurs at any size, see the top of this
   file.)
   ========================================================= */
@media (max-width: 900px), (hover: none) {
  .nav-backdrop {
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    background: oklch(0.13 0.035 280 / 72%);
  }
}


/* ===== assets/css/components/auth-nav.css ===== */
/* =========================================================
   Auth nav (sign in / welcome)
   ========================================================= */
.auth-nav {
  position: relative;
  display: inline-flex;
}

/* Guests: Sign in + Sign up, side by side. */
.auth-nav-guest {
  align-items: center;
  gap: 0.5rem;
}

.user-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.4rem 0.9rem 0.4rem 0.4rem;
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-pill);
  background: oklch(0.19 0.055 285 / 50%);
  color: var(--text-primary);
  font-family: var(--font-body);
  font-size: 0.875rem;
  font-weight: 600;
  white-space: nowrap;
  transition: border-color var(--transition-fast), background var(--transition-fast);
}

.user-chip:hover,
.user-chip[aria-expanded="true"] {
  border-color: oklch(0.85 0.15 202 / 45%);
  background: var(--bg-surface-alt);
}

.user-chip-avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  overflow: hidden;
  background: var(--gradient-primary);
  color: var(--text-primary);
  font-weight: 700;
  font-size: 0.85rem;
  flex-shrink: 0;
}

.user-chip-avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.user-chip-caret {
  font-size: 0.65rem;
  color: var(--text-faint);
  transition: transform var(--transition-fast);
}

.user-chip[aria-expanded="true"] .user-chip-caret { transform: rotate(180deg); }

.user-chip-mobile {
  width: 100%;
  padding: 0.7rem 1.15rem 0.7rem 0.7rem;
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-pill);
  background: oklch(0.19 0.055 285 / 50%);
  box-shadow: var(--shadow-glow-violet);
  font-size: 0.95rem;
  transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
  animation: navGroupIn 0.32s cubic-bezier(0.16, 1, 0.3, 1) both;
}

.user-chip-mobile:hover,
.user-chip-mobile:focus-visible {
  border-color: oklch(0.85 0.15 202 / 45%);
  box-shadow: var(--shadow-glow-cyan);
}

.user-chip-mobile .user-chip-avatar {
  position: relative;
  width: 38px;
  height: 38px;
  font-size: 1rem;
}

/* A gradient ring peeking out 2px behind the avatar, since the pill shape
   already borrows the desktop chip's radius-pill: the mobile identity chip
   should read as more than another rectangular card in the stack below. */
.user-chip-mobile .user-chip-avatar::before {
  content: "";
  position: absolute;
  inset: -2px;
  z-index: -1;
  border-radius: 50%;
  background: var(--gradient-primary);
}

.user-chip-mobile-text {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  min-width: 0;
}

.user-chip-mobile .user-chip-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.user-chip-mobile-sub {
  font-size: 0.75rem;
  color: var(--text-faint);
}

.user-chip-mobile-caret {
  margin-left: auto;
  font-size: 0.75rem;
  color: var(--text-faint);
}

.auth-dropdown {
  position: absolute;
  top: calc(100% + 0.75rem);
  right: 0;
  min-width: 220px;
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  padding: 0.5rem;
  background: oklch(0.13 0.035 280 / 96%);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-card);
  z-index: var(--z-dropdown);
}

.auth-dropdown[hidden] {
  display: none;
}

.auth-provider-link {
  display: flex;
  align-items: center;
  gap: 0.65rem;
  padding: 0.65rem 0.75rem;
  border-radius: var(--radius-sm);
  font-size: 0.9rem;
  font-weight: 500;
  color: var(--text-primary);
  /* Harmless on the existing <a> tags (browsers give anchors no default
     chrome), but required now that this class is also used on a <button>
     (the "Continue with email" option) - this codebase has no global
     button background/border reset. */
  background: transparent;
  border: none;
  width: 100%;
  text-align: left;
  transition: background var(--transition-fast), color var(--transition-fast);
}

.auth-provider-link:hover {
  background: oklch(0.85 0.15 202 / 10%);
  color: var(--accent-secondary);
}

.auth-provider-link .icon {
  width: 1.1em;
  text-align: center;
}

/* The "or with email" rule on the account modal's sign-in tab. */
.auth-or {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin: 0.15rem 0.25rem;
  color: var(--text-faint);
  font-size: 0.75rem;
}

.auth-or::before,
.auth-or::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--border-subtle);
}

/* =========================================================
   Notification bell
   ========================================================= */
.notif-bell {
  position: relative;
  display: inline-flex;
}

.bell-trigger {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border: 1px solid var(--border-subtle);
  border-radius: 50%;
  background: oklch(0.19 0.055 285 / 50%);
  color: var(--text-muted);
  font-size: 1rem;
  transition: border-color var(--transition-fast), background var(--transition-fast), color var(--transition-fast);
}

.bell-trigger:hover,
.bell-trigger[aria-expanded="true"] {
  border-color: oklch(0.85 0.15 202 / 45%);
  background: var(--bg-surface-alt);
  color: var(--text-primary);
}

.bell-badge {
  position: absolute;
  top: -4px;
  right: -4px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 18px;
  height: 18px;
  padding: 0 0.3rem;
  border-radius: var(--radius-pill);
  background: var(--accent-error);
  box-shadow: 0 0 0 2px var(--bg-base);
  color: var(--text-primary);
  font-size: 0.65rem;
  font-weight: 700;
  line-height: 1;
}

.bell-dropdown {
  position: absolute;
  top: calc(100% + 0.75rem);
  right: 0;
  width: 320px;
  max-width: min(320px, calc(100vw - 2rem));
  display: flex;
  flex-direction: column;
  background: oklch(0.13 0.035 280 / 96%);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-card);
  z-index: var(--z-dropdown);
}

.bell-dropdown[hidden] {
  display: none;
}

.bell-dropdown-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.75rem 0.9rem;
  border-bottom: 1px solid var(--border-subtle);
  font-family: var(--font-heading);
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-primary);
}

.bell-dropdown-count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 20px;
  height: 20px;
  padding: 0 0.35rem;
  border-radius: var(--radius-pill);
  background: var(--accent-primary);
  color: var(--text-primary);
  font-size: 0.7rem;
  font-weight: 700;
}

.bell-dropdown-list {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  padding: 0.4rem;
  max-height: 320px;
  overflow-y: auto;
  scrollbar-width: none;
  -ms-overflow-style: none;
}

.bell-dropdown-list::-webkit-scrollbar { display: none; }

.bell-dropdown-item {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  padding: 0.65rem 0.7rem;
  border-radius: var(--radius-sm);
  color: var(--text-primary);
  transition: background var(--transition-fast);
}

.bell-dropdown-item:hover { background: oklch(0.85 0.15 202 / 10%); }

.bell-dropdown-item-title { font-size: 0.85rem; font-weight: 600; }
.bell-dropdown-item-meta { font-size: 0.75rem; color: var(--text-muted); }

.bell-dropdown-empty {
  padding: 1rem 0.9rem;
  font-size: 0.85rem;
  color: var(--text-muted);
}



/* ===== assets/css/components/footer.css ===== */
/* =========================================================
   Footer
   ========================================================= */
.site-footer { border-top: 1px solid var(--border-subtle); }

/* Section links repeated at the bottom of the page - see the comment in
   includes/partials/footer.php. */
.footer-nav {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1.75rem;
  padding-top: 2rem;
  font-size: 0.85rem;
}

.footer-nav a {
  display: inline-flex;
  align-items: center;
  min-height: 24px;
  color: var(--text-muted);
  transition: color var(--transition-fast);
}

.footer-nav a:hover,
.footer-nav a[aria-current="page"] { color: var(--accent-secondary); }

.footer-nav + .footer-inner { border-top: 1px solid var(--border-subtle); margin-top: 1.5rem; }

.footer-inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  padding-block: 2rem;
  font-size: 0.8rem;
  color: var(--text-faint);
}

.footer-tools { font-family: var(--font-mono); }

.footer-link { color: var(--text-faint); transition: color var(--transition-fast); }
.footer-link:hover { color: var(--accent-secondary); }

/* The back-to-top button is a real primary button (.btn .btn-primary in
   style.css): the fill, border, glow, hover slide, focus ring and press
   wash all come from that rule, so it can never fall out of step with
   it again. This only makes it a fixed circle and shows or hides it.

   .btn brings its own padding and a smaller font size; both are reset so
   the circle stays 46px with a 16px chevron (the cookie notice's cut-out
   in cookie-consent-modal.css depends on that width). It is hidden with
   visibility as well as opacity, so it takes no keyboard focus while it
   is invisible; the delay keeps it visible for the fade-out. */
.back-to-top {
  position: fixed;
  bottom: 1.75rem;
  right: 1.75rem;
  width: 46px;
  height: 46px;
  padding: 0;
  border-radius: 50%;
  font-size: 1rem;
  opacity: 0;
  visibility: hidden;
  transform: translateY(12px);
  pointer-events: none;
  transition: opacity var(--transition-base), transform var(--transition-base), box-shadow var(--transition-base), background-position 0.7s cubic-bezier(0.16, 1, 0.3, 1), visibility 0s linear 0.3s;
  z-index: var(--z-back-to-top);
}

.back-to-top.visible { opacity: 1; visibility: visible; transform: translateY(0); pointer-events: auto; transition-delay: 0s; }

/* .visible above outranks .btn:hover, so the lift is restated here. */
.back-to-top.visible:hover { transform: translateY(-3px); }

/* =========================================================
   Responsive
   ========================================================= */
@media (max-width: 640px) {
  .footer-inner { flex-direction: column; align-items: flex-start; }
}

/* WCAG 2.2 AA 2.5.8 wants 24x24 minimum. These sit at 13px/1.6, which
   is 20px tall, and they are the only route to the legal documents from
   most pages. */
.footer-link {
  display: inline-flex;
  align-items: center;
  min-height: 24px;
}

.footer-link[aria-current="page"] { color: var(--accent-secondary); }


/* ===== assets/css/components/terms-modal.css ===== */
/* =========================================================
   Terms & Conditions Modal
   ========================================================= */
.terms-modal-backdrop {
  position: fixed;
  inset: 0;
  background: oklch(0.13 0.035 280 / 70%);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--transition-base);
  z-index: var(--z-modal-legal-backdrop);
}

.terms-modal-backdrop.is-open { opacity: 1; pointer-events: auto; }

.terms-modal {
  position: fixed;
  inset: 0;
  z-index: var(--z-modal-legal);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: clamp(1rem, 4vw, 2.5rem);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity var(--transition-base), visibility 0s linear var(--transition-base);
}

.terms-modal.is-open {
  opacity: 1;
  visibility: visible;
  transition: opacity var(--transition-base), visibility 0s linear;
}

.terms-modal-panel {
  position: relative;
  width: 100%;
  max-width: 720px;
  max-height: min(88vh, 820px);
  overflow-y: auto;
  scrollbar-width: none;
  -ms-overflow-style: none;
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-glow-violet), var(--shadow-card);
  padding: clamp(1.75rem, 4vw, 2.75rem);
  transform: translateY(24px) scale(0.96);
  transition: transform var(--transition-base);
}

/* Only the panel takes clicks. The full-screen wrapper stays click-through,
   so a click beside the panel reaches the backdrop, which closes the modal. */
.terms-modal.is-open .terms-modal-panel { pointer-events: auto; transform: none; }

.terms-modal-panel::-webkit-scrollbar { display: none; }

.terms-modal-close {
  position: absolute;
  top: 1.25rem;
  right: 1.25rem;
  width: 40px;
  height: 40px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  border: 1px solid var(--border-subtle);
  background: transparent;
  color: var(--text-muted);
  font-size: 1rem;
  transition: border-color var(--transition-fast), color var(--transition-fast);
}

.terms-modal-close:hover {
  border-color: oklch(0.85 0.15 202 / 60%);
  color: var(--accent-secondary);
}

.terms-modal-title {
  font-size: clamp(1.4rem, 1.5vw + 1rem, 1.75rem);
  margin-top: 1rem;
  margin-bottom: 1.5rem;
}

/* ---------- Long-form legal content ---------- */
.terms-content h2 {
  font-size: 1.05rem;
  font-weight: 700;
  margin-top: 1.75rem;
  margin-bottom: 0.65rem;
}

.terms-content h2:first-child { margin-top: 0; }

.terms-content p {
  color: var(--text-muted);
  font-size: 0.9rem;
  line-height: 1.7;
}

.terms-content ul {
  margin-top: 0.75rem;
  display: flex;
  flex-direction: column;
  gap: 0.65rem;
}

.terms-content li {
  position: relative;
  padding-left: 1.1rem;
  color: var(--text-muted);
  font-size: 0.9rem;
  line-height: 1.7;
}

.terms-content li::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0.6em;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--accent-sky);
}

.terms-content li strong { color: var(--text-primary); }

.terms-content code {
  font-family: var(--font-mono);
  font-size: 0.85em;
  background: oklch(0.13 0.035 280 / 55%);
  border: 1px solid var(--border-subtle);
  border-radius: 4px;
  padding: 0.1rem 0.35rem;
}

/* Numbered clauses (<p class="clause"><span class="clause-no">2.1</span>).
   The number hangs in its own column so every clause's text lines up, the
   way contracts are set. */
.terms-content p.clause {
  position: relative;
  padding-left: 2.6rem;
}

.terms-content p.clause + p.clause { margin-top: 0.6rem; }

.terms-content .clause-no {
  position: absolute;
  left: 0;
  top: 0.2em;
  font-family: var(--font-mono);
  font-size: 0.74rem;
  font-variant-numeric: tabular-nums;
  color: var(--text-faint);
}

/* Links in the prose (contact addresses, the supervisory authority)
   must read as links, not as more text. */
.terms-content a {
  color: var(--accent-secondary);
  text-decoration: underline;
}

.terms-content a:hover { color: var(--text-primary); }

/* A list that belongs to the clause above it sits under that clause's text. */
.terms-content ul.clause-list { padding-left: 2.6rem; }

.terms-consent-note {
  margin-top: 2rem;
  color: var(--text-primary) !important;
  font-size: 0.85rem !important;
  font-style: italic;
  background: oklch(0.85 0.15 202 / 8%);
  border: 1px solid oklch(0.85 0.15 202 / 25%);
  border-radius: var(--radius-md);
  padding: 1rem 1.25rem;
}

/* Phones and touch devices: a modal backdrop blur forces a readback of
   everything painted behind it. Dropped in favour of a more opaque
   scrim, which reads the same once the ambient layer behind it is
   already static (see ambient-background.css). */
@media (max-width: 900px), (hover: none) {
  .terms-modal-backdrop {
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    background: oklch(0.13 0.035 280 / 88%);
  }
}

/* ---------- Shared with the standalone pages ---------- */

/* Sits between the dialog title and the prose. Machine-readable via
   <time>, and the same date seo_page_json_ld() reports as dateModified. */
.legal-updated {
  color: var(--text-faint);
  font-size: 0.8rem;
  margin-bottom: 1.5rem;
}

/* Escape hatch out of the modal, for someone reading this mid-form who
   wants to keep what they have typed. */
.legal-full-link {
  display: inline-flex;
  align-items: center;
  min-height: 24px;
  margin-top: 1.75rem;
  color: var(--accent-secondary);
  font-size: 0.85rem;
  font-weight: 600;
  text-decoration: underline;
}


/* ===== assets/css/components/commission-modal.css ===== */
/* =========================================================
   Commission Modal
   The shell: dialog, section tabs, summary rail and the sent screen.
   Choice cards, the artist picker and the image drop live in
   commission-picker.css.
   ========================================================= */
.commission-modal-backdrop {
  position: fixed;
  inset: 0;
  background: oklch(0.13 0.035 280 / 70%);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--transition-base);
  z-index: var(--z-modal-backdrop);
}

.commission-modal-backdrop.is-open { opacity: 1; pointer-events: auto; }

/* Clicks outside the shell fall through to the backdrop, which closes. */
.commission-modal {
  position: fixed;
  inset: 0;
  z-index: var(--z-modal);
  display: grid;
  place-items: center;
  padding: clamp(1rem, 4vw, 2.5rem);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity var(--transition-base), visibility 0s linear var(--transition-base);
}

.commission-modal.is-open {
  opacity: 1;
  visibility: visible;
  transition: opacity var(--transition-base), visibility 0s linear;
}

.cm-shell {
  position: relative;
  width: min(1000px, 100%);
  height: min(744px, 100%);
  border-radius: var(--radius-lg);
  overflow: hidden;
  box-shadow: var(--shadow-glow-violet);
  pointer-events: auto;
  transform: translateY(16px) scale(0.98);
  transition: transform 0.35s cubic-bezier(0.16, 1, 0.3, 1);
}

.commission-modal.is-open .cm-shell { transform: none; }

.cm-form { display: grid; grid-template-columns: minmax(0, 1fr) 316px; height: 100%; }
.cm-form[hidden] { display: none; }

.cm-close {
  position: absolute;
  top: 18px;
  right: 18px;
  z-index: 3;
  width: 40px;
  height: 40px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  border: 1px solid var(--border-subtle);
  background: var(--bg-base);
  color: var(--text-muted);
  font-size: 0.95rem;
  transition: border-color var(--transition-fast), color var(--transition-fast);
}

.cm-close:hover { border-color: color-mix(in oklab, var(--accent-secondary) 60%, transparent); color: var(--accent-secondary); }

/* ---------- Main column ---------- */
.cm-main { position: relative; display: flex; flex-direction: column; min-width: 0; min-height: 0; }

/* Fades the bottom edge so a scrollable section reads as one. */
.cm-main::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 36px;
  background: linear-gradient(to bottom, transparent, var(--bg-surface));
  pointer-events: none;
}

.cm-head { padding: 28px 32px 0; }

.cm-title {
  margin: 0;
  font-weight: 600;
  font-size: 1.6rem;
  line-height: 1.15;
  letter-spacing: -0.025em;
  text-wrap: balance;
}

.cm-sub { margin: 6px 0 0; max-width: 54ch; color: var(--text-muted); font-size: 0.93rem; line-height: 1.55; }

.cm-steps {
  display: flex;
  gap: 6px;
  margin-top: 18px;
  padding-bottom: 14px;
  border-bottom: 1px solid var(--border-subtle);
}

.cm-step {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 6px 12px 6px 6px;
  border-radius: var(--radius-sm);
  border: 1px solid transparent;
  background: transparent;
  color: var(--text-muted);
  font-size: 0.88rem;
  font-weight: 500;
  transition: color var(--transition-fast), background-color var(--transition-fast), border-color var(--transition-fast);
}

.cm-step:hover { color: var(--text-primary); }

.cm-step[aria-current="step"] {
  color: var(--text-primary);
  background: color-mix(in oklab, var(--accent-primary) 22%, transparent);
  border-color: var(--border-strong);
}

.cm-step-mark {
  width: 24px;
  height: 24px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  border: 1px solid var(--border-strong);
  font-family: var(--font-mono);
  font-size: 0.7rem;
  font-weight: 600;
  line-height: 1;
  color: var(--text-muted);
}

.cm-step-mark .icon { font-size: 0.62rem; }
.cm-step[aria-current="step"] .cm-step-mark { border-color: var(--accent-secondary); color: var(--accent-secondary); }
.cm-step.is-done .cm-step-mark { background: var(--accent-secondary); border-color: var(--accent-secondary); color: var(--bg-base); }
.cm-step.has-error .cm-step-mark { background: transparent; border-color: var(--accent-error); color: var(--accent-error); }

.cm-alert {
  margin: 14px 32px 0;
  padding: 10px 14px;
  border-radius: var(--radius-sm);
  font-size: 0.86rem;
  line-height: 1.5;
  color: var(--accent-error);
  background: color-mix(in oklab, var(--accent-error) 12%, transparent);
  border: 1px solid color-mix(in oklab, var(--accent-error) 32%, transparent);
}

.cm-alert[hidden] { display: none; }

.cm-panes {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: none;
  -ms-overflow-style: none;
  padding: 22px 32px 0;
}

.cm-panes::-webkit-scrollbar { display: none; }

/* Both paddings set: style.css gives every <section> page-level padding-block. */
.cm-pane { display: flex; flex-direction: column; gap: 26px; padding-block: 0 40px; }
.cm-pane[hidden] { display: none; }
.cm-pane.is-entering { animation: cm-pane-in 0.34s cubic-bezier(0.16, 1, 0.3, 1) both; }

@keyframes cm-pane-in {
  from { opacity: 0; transform: translateX(calc(var(--cm-dir, 1) * 14px)); }
  to { opacity: 1; transform: none; }
}

/* ---------- Summary rail ---------- */
.cm-rail {
  display: flex;
  flex-direction: column;
  gap: 16px;
  min-height: 0;
  overflow-y: auto;
  scrollbar-width: none;
  -ms-overflow-style: none;
  padding: 28px 24px 24px;
  background: var(--bg-base);
  border-left: 1px solid var(--border-subtle);
}

.cm-rail::-webkit-scrollbar { display: none; }

.cm-rail-body { display: flex; flex-direction: column; gap: 16px; }

.cm-rail-title {
  margin: 0;
  min-height: 40px;
  padding-right: 44px;
  display: flex;
  align-items: center;
  font-size: 0.98rem;
  font-weight: 600;
  letter-spacing: -0.01em;
}

.cm-price {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 3px;
  width: 100%;
  padding: 14px 16px;
  text-align: left;
  border-radius: var(--radius-md);
  border: 1px solid var(--border-subtle);
  background: var(--bg-surface);
  color: var(--text-primary);
  transition: border-color var(--transition-fast);
}

.cm-price:hover { border-color: var(--border-strong); }

.cm-price-value {
  font-family: var(--font-mono);
  font-size: 1.45rem;
  font-weight: 600;
  line-height: 1.15;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
}

.cm-price-value.is-empty { font-size: 1rem; color: var(--text-muted); }
.cm-price-name { font-size: 0.84rem; color: var(--text-muted); }

.cm-sum { margin: 0; }

.cm-sum-row {
  display: grid;
  grid-template-columns: 78px minmax(0, 1fr);
  gap: 10px;
  align-items: center;
  padding: 9px 0;
  border-top: 1px solid var(--border-subtle);
}

.cm-sum dt { font-size: 0.78rem; color: var(--text-faint); }
.cm-sum dd { margin: 0; min-width: 0; }

.cm-sum-btn {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  min-height: 26px;
  padding: 0;
  border: 0;
  border-radius: 4px;
  background: none;
  text-align: left;
  font-size: 0.87rem;
  color: var(--text-primary);
}

.cm-sum-btn:hover .cm-sum-val { text-decoration: underline; text-decoration-color: color-mix(in oklab, var(--accent-secondary) 60%, transparent); text-underline-offset: 3px; }

.cm-sum-btn.is-empty { color: var(--text-faint); }
.cm-sum-btn.is-missing { color: var(--accent-error); }
.cm-sum-val { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.cm-sum-av { width: 26px; height: 26px; flex-shrink: 0; border-radius: 6px; overflow: hidden; background: var(--bg-surface-alt); }
.cm-sum-av .cm-face { margin: 0; border-radius: 0; }
.cm-sum-av .cm-initials { font-size: 0.62rem; }

.cm-changed { animation: cm-value-in 0.32s cubic-bezier(0.16, 1, 0.3, 1); }

@keyframes cm-value-in { from { opacity: 0.25; transform: translateY(3px); } to { opacity: 1; transform: none; } }

.cm-rail-note { margin: 0; font-size: 0.78rem; line-height: 1.55; color: var(--text-faint); }
.cm-compact { display: none; }

.cm-rail-actions { margin-top: auto; display: flex; align-items: center; gap: 10px; }

/* Both actions share one size and the utility radius, so they read as a
   pair. The pill stays for page-level calls to action. */
.commission-modal .btn,
.cm-back {
  height: 44px;
  padding: 0 1.1rem;
  border-radius: var(--radius-sm);
  font-size: 0.92rem;
}

.commission-modal .btn:hover { transform: none; }

.cm-back {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--border-subtle);
  background: transparent;
  color: var(--text-muted);
  font-weight: 500;
  transition: color var(--transition-fast), border-color var(--transition-fast);
}

.cm-back:hover { color: var(--text-primary); border-color: var(--border-strong); }
.cm-next { flex: 1; }
.cm-next.is-loading .icon { opacity: 0; }
/* No global [hidden] rule, and an SVG ignores the attribute on its own. */
.cm-next .icon[hidden] { display: none; }

/* ---------- Shared form fields ----------
   The .cf-* rules below are used by the sign-in, report, review, chat,
   claim and reset-password forms too, which all load this file. */
.cf-field { display: flex; flex-direction: column; gap: 0.5rem; }

.cf-field label {
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--text-primary);
}

.cf-optional { color: var(--text-faint); font-weight: 500; }

.cf-hint {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  color: var(--text-faint);
}

/* The solid option background matters: the select's own background is
   translucent, and a browser that can't use it draws the list on white. */
.cf-field select option {
  background: var(--bg-surface);
  color: var(--text-primary);
}

.cf-field input[type="text"],
.cf-field input[type="email"],
.cf-field input[type="password"],
.cf-field select,
.cf-field textarea {
  width: 100%;
  background: oklch(0.13 0.035 280 / 55%);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  padding: 0.8rem 1rem;
  font-family: var(--font-body);
  font-size: 0.95rem;
  color: var(--text-primary);
  transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}

.cf-field input[type="text"]::placeholder,
.cf-field input[type="email"]::placeholder,
.cf-field input[type="password"]::placeholder,
.cf-field textarea::placeholder { color: var(--text-faint); }

.cf-field input[type="text"]:focus-visible,
.cf-field input[type="email"]:focus-visible,
.cf-field input[type="password"]:focus-visible,
.cf-field select:focus-visible,
.cf-field textarea:focus-visible {
  outline: none;
  border-color: oklch(0.85 0.15 202 / 60%);
  box-shadow: 0 0 0 3px oklch(0.85 0.15 202 / 18%);
}

.cf-field select {
  appearance: none;
  -webkit-appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='none' stroke='%23c4c2e0' stroke-width='1.6'%3E%3Cpath d='M5 7.5l5 5 5-5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 0.9rem center;
  background-size: 16px;
  padding-right: 2.5rem;
}

.cf-field textarea { resize: vertical; min-height: 120px; }

.cf-field.has-error input[type="text"],
.cf-field.has-error input[type="email"],
.cf-field.has-error input[type="password"],
.cf-field.has-error select,
.cf-field.has-error textarea {
  border-color: var(--accent-error);
}

.cf-error {
  display: none;
  font-family: var(--font-mono);
  font-size: 0.8rem;
  color: var(--accent-error);
}

.cf-field.has-error .cf-error { display: block; }

.cf-form-error {
  font-size: 0.875rem;
  color: var(--accent-error);
  background: color-mix(in oklab, var(--accent-error) 12%, transparent);
  border: 1px solid color-mix(in oklab, var(--accent-error) 30%, transparent);
  border-radius: var(--radius-md);
  padding: 0.75rem 1rem;
  margin: 0;
}

.cf-honeypot {
  position: absolute;
  left: -9999px;
  top: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.btn-submit { position: relative; }

.btn-submit .btn-spinner {
  display: none;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  border: 2px solid oklch(0.96 0.02 250 / 35%);
  border-top-color: var(--text-primary);
}

.btn-submit.is-loading .btn-label { opacity: 0; }
.btn-submit.is-loading .btn-spinner {
  display: block;
  position: absolute;
  inset: 0;
  margin: auto;
  animation: cf-spin 0.7s linear infinite;
}

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

/* ---------- Phones: a full-height sheet, the summary becomes a bottom bar ---------- */
@media (max-width: 760px) {
  .commission-modal { padding: 0; place-items: stretch; }
  .cm-shell { width: 100%; height: 100%; border-radius: 0; }
  .cm-form { grid-template-columns: minmax(0, 1fr); grid-template-rows: minmax(0, 1fr) auto; }
  .cm-close { top: 12px; right: 12px; }
  .cm-head { padding: 16px 64px 0 16px; }
  .cm-title { font-size: 1.3rem; }
  .cm-sub { font-size: 0.86rem; }
  .cm-steps { margin-top: 14px; margin-right: -48px; gap: 4px; }
  .cm-step { padding: 5px 9px 5px 5px; font-size: 0.82rem; gap: 6px; }
  .cm-alert { margin: 12px 16px 0; }
  .cm-panes { padding: 18px 16px 0; }

  .cm-rail {
    flex-direction: row;
    align-items: center;
    gap: 12px;
    overflow: visible;
    padding: 10px 16px calc(10px + env(safe-area-inset-bottom, 0px));
    border-left: 0;
    border-top: 1px solid var(--border-subtle);
  }

  .cm-rail-body { display: none; }
  .cm-compact { display: flex; flex-direction: column; min-width: 0; flex: 1; }
  .cm-compact-value { font-family: var(--font-mono); font-size: 1rem; font-weight: 600; white-space: nowrap; }
  .cm-compact-name { font-size: 0.76rem; color: var(--text-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .cm-rail-actions { margin-top: 0; flex-shrink: 0; gap: 8px; }
  .cm-next { flex: none; }
  .commission-modal .btn, .cm-back { padding: 0 0.95rem; }
}

/* Phones and touch devices: a modal backdrop blur forces a readback of
   everything painted behind it. Dropped in favour of a more opaque
   scrim, which reads the same once the ambient layer behind it is
   already static (see ambient-background.css). */
@media (max-width: 900px), (hover: none) {
  .commission-modal-backdrop {
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    background: oklch(0.13 0.035 280 / 88%);
  }
}


/* ===== assets/css/components/commission-picker.css ===== */
/* =========================================================
   Commission Modal: the choices
   Choice cards (type and package), base chips, the artist picker, the
   details fields, the reference image drop and the consent boxes. The
   shell they sit in is commission-modal.css.

   Every choice is a real radio inside a label. The radio is visually
   hidden and the card after it takes the checked and focus states.
   ========================================================= */
.cm-group { border: 0; margin: 0; padding: 0; min-width: 0; }

.cm-legend {
  padding: 0;
  margin-bottom: 12px;
  font-family: var(--font-heading);
  font-size: 1.04rem;
  font-weight: 600;
  line-height: 1.3;
  letter-spacing: -0.012em;
  color: var(--text-primary);
}

.cm-optional {
  margin-left: 6px;
  font-family: var(--font-body);
  font-size: 0.8rem;
  font-weight: 400;
  letter-spacing: 0;
  color: var(--text-faint);
}

.cm-group-hint { margin: -6px 0 12px; max-width: 60ch; font-size: 0.84rem; line-height: 1.5; color: var(--text-muted); }

.cm-error { display: none; font-size: 0.82rem; color: var(--accent-error); }
.has-error > .cm-error, .has-error .cm-error { display: block; }
.cm-group > .cm-error { margin-top: 8px; }

/* ---------- Commission line ----------
   Texturing or Blender, shown only while both can be booked
   (commission-line.js). Two options in one track at the utility scale: the
   chosen one lifts a surface step and takes the cyan hairline. */
.cm-line {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.35rem;
  padding: 0.3rem;
  border-radius: calc(var(--radius-sm) + 0.3rem);
  border: 1px solid var(--border-subtle);
  background-color: color-mix(in oklab, var(--bg-surface) 70%, transparent);
}

.cm-line-opt {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  height: 100%;
  padding: 0.55rem 0.85rem;
  border-radius: var(--radius-sm);
  border: 1px solid transparent;
  color: var(--text-muted);
  transition: background-color var(--transition-fast), color var(--transition-fast), border-color var(--transition-fast);
}

.cm-line-name { font-size: 0.92rem; font-weight: 600; }
.cm-line-sub { font-size: 0.78rem; line-height: 1.35; color: var(--text-faint); }
.cm-choice:hover .cm-line-opt { color: var(--text-primary); }

.cm-line input:checked + .cm-line-opt {
  background-color: var(--bg-surface-alt);
  border-color: color-mix(in oklab, var(--accent-secondary) 55%, transparent);
  color: var(--text-primary);
}

.cm-line input:checked + .cm-line-opt .cm-line-sub { color: var(--text-muted); }
.cm-line input:focus-visible + .cm-line-opt { outline: 2px solid var(--accent-secondary); outline-offset: 2px; }
[data-field="commission_line"].has-error .cm-line { border-color: color-mix(in oklab, var(--accent-error) 55%, transparent); }

/* ---------- Choice cards ---------- */
.cm-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; }
/* Blender has four kinds of job. Four across leaves each card too narrow
   for its title beside the summary rail, so they sit two by two. The class
   is set by commission-line.js; phones are at the end. */
.cm-grid--four { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.cm-choice { position: relative; display: block; cursor: pointer; }
/* A card of the commission line that is not chosen. */
.cm-choice[hidden] { display: none; }

.cm-card {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 4px;
  height: 100%;
  padding: 14px 16px;
  border-radius: var(--radius-md);
  border: 1px solid var(--border-subtle);
  background: color-mix(in oklab, var(--bg-base) 50%, transparent);
  transition: border-color var(--transition-fast), background-color var(--transition-fast), box-shadow var(--transition-fast);
}

.cm-choice:hover .cm-card { border-color: var(--border-strong); }

.cm-choice input:checked + .cm-card {
  border-color: color-mix(in oklab, var(--accent-secondary) 62%, transparent);
  background: color-mix(in oklab, var(--accent-secondary) 7%, var(--bg-surface));
  box-shadow: 0 0 0 1px color-mix(in oklab, var(--accent-secondary) 38%, transparent);
}

.cm-choice input:focus-visible + .cm-card,
.cm-choice input:focus-visible + .cm-chip {
  outline: 2px solid var(--accent-secondary);
  outline-offset: 2px;
}

[data-field="commission_type"].has-error .cm-card { border-color: color-mix(in oklab, var(--accent-error) 55%, transparent); }

.cm-tick {
  position: absolute;
  top: 10px;
  right: 10px;
  width: 20px;
  height: 20px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--accent-secondary);
  color: var(--bg-base);
  font-size: 0.6rem;
  opacity: 0;
  transform: scale(0.6);
  transition: opacity var(--transition-fast), transform 0.22s cubic-bezier(0.16, 1, 0.3, 1);
}

.cm-choice input:checked + .cm-card .cm-tick { opacity: 1; transform: none; }

.cm-choice-title { padding-right: 24px; font-size: 0.93rem; font-weight: 600; }
.cm-choice-desc { font-size: 0.8rem; line-height: 1.45; color: var(--text-muted); }

.cm-tier-name {
  padding-right: 24px;
  font-family: var(--font-heading);
  font-size: 0.98rem;
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: -0.01em;
}

.cm-tier-price {
  margin-top: 6px;
  font-family: var(--font-mono);
  font-size: 0.92rem;
  font-weight: 500;
  line-height: 1.3;
  font-variant-numeric: tabular-nums;
}

.cm-tilde { margin-left: 1px; font-weight: 500; color: var(--text-faint); }

.cm-tier-spec {
  font-family: var(--font-mono);
  font-size: 0.64rem;
  font-weight: 500;
  line-height: 1.45;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-faint);
}

.cm-tier-unsure .cm-tier-price { font-family: var(--font-body); font-size: 0.8rem; font-weight: 400; line-height: 1.45; color: var(--text-muted); }

/* ---------- Base chips ---------- */
.cm-chips { display: flex; flex-wrap: wrap; gap: 8px; }

.cm-chip {
  display: block;
  padding: 8px 14px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-subtle);
  background: color-mix(in oklab, var(--bg-base) 50%, transparent);
  font-size: 0.88rem;
  color: var(--text-muted);
  transition: border-color var(--transition-fast), color var(--transition-fast), background-color var(--transition-fast);
}

.cm-choice:hover .cm-chip { border-color: var(--border-strong); color: var(--text-primary); }

.cm-choice input:checked + .cm-chip {
  border-color: color-mix(in oklab, var(--accent-secondary) 62%, transparent);
  background: color-mix(in oklab, var(--accent-secondary) 9%, transparent);
  color: var(--text-primary);
}

/* Gold marks who can work on the chosen base, and only that, across the form. */
.cm-owners { margin-top: 12px; font-family: var(--font-mono); font-size: 0.78rem; font-weight: 500; line-height: 1.5; color: var(--accent-gold); }
.cm-owners.is-none { color: var(--text-faint); }
.cm-owners[hidden] { display: none; }

/* ---------- Artist picker ---------- */
.cm-filter { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-bottom: 12px; }

.cm-filter-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 11px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-subtle);
  background: transparent;
  font-size: 0.82rem;
  color: var(--text-muted);
  transition: color var(--transition-fast), border-color var(--transition-fast), background-color var(--transition-fast);
}

.cm-filter-btn:hover { color: var(--text-primary); border-color: var(--border-strong); }

.cm-filter-btn[aria-pressed="true"] {
  color: var(--text-primary);
  border-color: color-mix(in oklab, var(--accent-secondary) 62%, transparent);
  background: color-mix(in oklab, var(--accent-secondary) 9%, transparent);
}

.cm-filter-btn[data-filter="owns"] { color: var(--accent-gold); }

.cm-filter-btn[data-filter="owns"][aria-pressed="true"] {
  border-color: color-mix(in oklab, var(--accent-gold) 60%, transparent);
  background: color-mix(in oklab, var(--accent-gold) 10%, transparent);
}

.cm-filter-btn[hidden] { display: none; }
.cm-count { font-family: var(--font-mono); font-size: 0.72rem; font-weight: 500; line-height: 1; color: var(--text-faint); }

.cm-search { margin-left: auto; display: inline-flex; align-items: center; gap: 8px; font-size: 0.82rem; color: var(--text-muted); }

.cm-search input {
  width: 150px;
  padding: 6px 10px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-subtle);
  background: color-mix(in oklab, var(--bg-base) 50%, transparent);
  font-family: var(--font-body);
  font-size: 0.85rem;
  color: var(--text-primary);
}

.cm-search input:focus-visible,
.cm-input:focus-visible {
  outline: none;
  border-color: color-mix(in oklab, var(--accent-secondary) 62%, transparent);
  box-shadow: 0 0 0 3px color-mix(in oklab, var(--accent-secondary) 18%, transparent);
}

.cm-artists { display: grid; grid-template-columns: repeat(auto-fill, minmax(128px, 1fr)); gap: 10px; }
.cm-artist[hidden] { display: none; }
.cm-artist .cm-card { padding: 8px 8px 12px; gap: 0; }

.cm-face {
  position: relative;
  display: block;
  width: 100%;
  aspect-ratio: 1;
  margin-bottom: 10px;
  border-radius: 8px;
  overflow: hidden;
  background: var(--bg-surface-alt);
  transition: box-shadow var(--transition-fast);
}

/* The "Any artist" tile holds one set of faces per commission line. */
.cm-face[hidden] { display: none; }
.cm-face img { display: block; width: 100%; height: 100%; object-fit: cover; }

/* Same fallback as the homepage artist cards (artist-deck.css), on the artist's hue. */
.cm-initials {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  font-family: var(--font-heading);
  font-size: 1.6rem;
  font-weight: 600;
  line-height: 1;
  color: var(--text-primary);
  background: radial-gradient(120% 120% at 50% 18%, oklch(0.62 0.14 var(--avatar-hue, 292) / 55%) 0%, var(--bg-surface-alt) 72%);
}

.cm-mosaic { display: grid; grid-template-columns: 1fr 1fr; gap: 2px; background: var(--bg-base); }
.cm-mosaic--one { grid-template-columns: 1fr; }
.cm-mosaic .cm-face { margin: 0; border-radius: 0; }
.cm-mosaic .cm-initials { font-size: 0.9rem; }

/* A line's own set of faces fills its square with two or three as well: two
   stand side by side at full height, a third runs along the bottom. A
   restricted line often has that few open artists. The one set of a form
   with a single line carries no data-line and keeps the look it has. */
.cm-mosaic[data-line] > .cm-face:first-child:nth-last-child(2),
.cm-mosaic[data-line] > .cm-face:nth-child(2):last-child { aspect-ratio: 1 / 2; align-self: stretch; }
.cm-mosaic[data-line] > .cm-face:nth-child(3):last-child { grid-column: 1 / -1; aspect-ratio: 2 / 1; }

.cm-artist-name { padding-inline: 4px; font-size: 0.9rem; font-weight: 600; line-height: 1.25; }
.cm-artist-role { padding-inline: 4px; font-size: 0.75rem; color: var(--text-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* "First free Blender artist" is longer than a tile is wide, so the line
   that changes with the commission line may wrap. */
.cm-artist-role[data-line-text] { white-space: normal; line-height: 1.35; }

.cm-status { display: flex; align-items: center; gap: 6px; margin-top: 7px; padding-inline: 4px; font-size: 0.72rem; color: var(--text-muted); }
.cm-dot { width: 6px; height: 6px; flex-shrink: 0; border-radius: 50%; background: var(--text-faint); }
.cm-status--available .cm-dot { background: var(--accent-secondary); box-shadow: 0 0 8px var(--accent-secondary); }
.cm-status--at_capacity .cm-dot { background: var(--accent-gold-alt); }

.cm-slots { margin-top: 3px; padding-inline: 4px; font-family: var(--font-mono); font-size: 0.66rem; font-weight: 500; line-height: 1.3; color: var(--text-faint); }
.cm-owns { margin-top: 5px; padding-inline: 4px; font-family: var(--font-mono); font-size: 0.68rem; font-weight: 600; line-height: 1.3; color: var(--accent-gold); }
.cm-owns[hidden] { display: none; }
.cm-artist.owns .cm-face { box-shadow: 0 0 0 2px var(--accent-gold); }

/* A full queue is the only thing that locks an artist. */
.cm-artist.is-full { cursor: not-allowed; }
.cm-artist.is-full .cm-card { opacity: 0.55; }
.cm-artist.is-full .cm-face img,
.cm-artist.is-full .cm-initials { filter: grayscale(0.85); }
.cm-artist.is-full:hover .cm-card { border-color: var(--border-subtle); }

.cm-empty {
  grid-column: 1 / -1;
  padding: 18px;
  border-radius: var(--radius-md);
  border: 1px dashed var(--border-subtle);
  font-size: 0.88rem;
  color: var(--text-muted);
}

.cm-empty[hidden] { display: none; }

.cm-linkbtn {
  padding: 0;
  border: 0;
  background: none;
  font-weight: 600;
  color: var(--accent-secondary);
  text-decoration: underline;
  text-underline-offset: 3px;
}

.cm-artist-note { margin-top: 14px; font-size: 0.84rem; line-height: 1.5; color: var(--text-muted); }
.cm-artist-note[hidden] { display: none; }

.cm-profile-link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-top: 14px;
  font-size: 0.84rem;
  font-weight: 600;
  color: var(--accent-secondary);
  text-decoration: none;
}

.cm-profile-link:hover { text-decoration: underline; }
.cm-profile-link .icon { font-size: 0.72rem; }
.cm-profile-link[hidden] { display: none; }

/* ---------- Details ---------- */
.cm-field { display: flex; flex-direction: column; gap: 7px; }
.cm-label-row { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; }
.cm-label { font-size: 0.9rem; font-weight: 600; color: var(--text-primary); }
.cm-counter { font-family: var(--font-mono); font-size: 0.72rem; font-weight: 500; line-height: 1; font-variant-numeric: tabular-nums; color: var(--text-faint); }

.cm-input {
  width: 100%;
  padding: 11px 14px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-subtle);
  background: color-mix(in oklab, var(--bg-base) 50%, transparent);
  font-family: var(--font-body);
  font-size: 0.95rem;
  color: var(--text-primary);
  transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}

.cm-input::placeholder { color: var(--text-faint); }
textarea.cm-input { min-height: 132px; resize: vertical; line-height: 1.55; }
.cm-hint { font-size: 0.8rem; line-height: 1.45; color: var(--text-faint); }
.has-error .cm-input { border-color: var(--accent-error); }

/* The file input covers the whole zone, so a click opens the picker and a
   drop lands on it natively. */
.cm-drop {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  padding: 20px 16px;
  text-align: center;
  border-radius: var(--radius-md);
  border: 1px dashed var(--border-strong);
  transition: border-color var(--transition-fast), background-color var(--transition-fast);
}

.cm-drop:hover,
.cm-drop.is-over {
  border-color: color-mix(in oklab, var(--accent-secondary) 62%, transparent);
  background: color-mix(in oklab, var(--accent-secondary) 6%, transparent);
}

.cm-drop:focus-within { outline: 2px solid var(--accent-secondary); outline-offset: 2px; }
.cm-drop input { position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0; cursor: pointer; }
.cm-drop.is-full { opacity: 0.55; }
.cm-drop-main { font-size: 0.92rem; color: var(--text-primary); }
.cm-drop-main span { font-weight: 600; color: var(--accent-secondary); text-decoration: underline; text-underline-offset: 3px; }

.cm-thumbs { list-style: none; margin: 4px 0 0; padding: 0; display: flex; flex-wrap: wrap; gap: 12px; }
.cm-thumbs:empty { display: none; }
.cm-thumb { position: relative; width: 92px; }
.cm-thumb img { display: block; width: 92px; height: 92px; object-fit: cover; border-radius: 8px; border: 1px solid var(--border-subtle); }
.cm-thumb-name { display: block; margin-top: 5px; font-family: var(--font-mono); font-size: 0.66rem; line-height: 1.3; color: var(--text-faint); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

.cm-thumb-x {
  position: absolute;
  top: -8px;
  right: -8px;
  width: 26px;
  height: 26px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  border: 1px solid var(--border-strong);
  background: var(--bg-surface-alt);
  color: var(--text-primary);
  font-size: 0.6rem;
}

.cm-thumb-x:hover { border-color: color-mix(in oklab, var(--accent-secondary) 62%, transparent); color: var(--accent-secondary); }

.cm-consents {
  margin: 0;
  padding: 14px 16px 16px;
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  display: grid;
  gap: 12px;
}

.cm-consents legend { padding: 0 6px; margin-left: -6px; font-family: var(--font-heading); font-size: 0.92rem; font-weight: 600; }
.cm-check { display: flex; align-items: flex-start; gap: 11px; cursor: pointer; }

/* 24px: the WCAG 2.2 AA target floor these required boxes already met. */
.cm-check input { flex-shrink: 0; width: 24px; height: 24px; margin: 0; accent-color: var(--accent-primary); cursor: pointer; }
.cm-check span { font-size: 0.8rem; line-height: 1.55; color: var(--text-muted); }
.cm-check a { color: var(--accent-secondary); text-decoration: underline; }
.cm-check a:hover { color: var(--text-primary); }
.has-error .cm-check input { outline: 2px solid var(--accent-error); outline-offset: 2px; }

@media (max-width: 760px) {
  .cm-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .cm-grid--types { grid-template-columns: minmax(0, 1fr); }
  .cm-artists { grid-template-columns: repeat(auto-fill, minmax(100px, 1fr)); gap: 8px; }
  .cm-search { margin-left: 0; width: 100%; }
  .cm-search input { flex: 1; width: auto; }
}

/* Four kinds of job stay two by two on phones as well (hence after the
   block above, which sets the type cards one across). */
@media (max-width: 760px) {
  .cm-grid--four { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}


/* ===== assets/css/components/commission-sent.css ===== */
/* =========================================================
   Commission Modal: the sent screen
   Shown in place of the form once a request goes through: who has it,
   the order number, and what happens next. The shell is
   commission-modal.css; .cm-face comes from commission-picker.css.
   ========================================================= */
.cm-done {
  height: 100%;
  display: grid;
  place-items: center;
  padding: 48px 24px;
  overflow-y: auto;
  scrollbar-width: none;
  -ms-overflow-style: none;
}

.cm-done::-webkit-scrollbar { display: none; }
.cm-done[hidden] { display: none; }

.cm-done-inner { width: min(500px, 100%); display: flex; flex-direction: column; align-items: center; text-align: center; }

.cm-done-face { position: relative; width: 104px; margin-bottom: 20px; animation: cm-pop 0.45s cubic-bezier(0.16, 1, 0.3, 1) both; }
.cm-done-face > .cm-face { margin: 0; border-radius: 18px; box-shadow: var(--shadow-glow-violet); }

.cm-done-badge {
  position: absolute;
  right: -8px;
  bottom: -8px;
  width: 32px;
  height: 32px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--bg-surface);
  color: var(--accent-secondary);
  font-size: 1.6rem;
}

@keyframes cm-pop {
  from { opacity: 0; transform: scale(0.9); }
  to { opacity: 1; transform: none; }
}

.cm-done-title { margin: 0; font-size: 1.7rem; font-weight: 600; line-height: 1.15; letter-spacing: -0.025em; }
.cm-done-title:focus { outline: none; }
.cm-done-msg { margin: 8px 0 22px; max-width: 46ch; color: var(--text-muted); line-height: 1.6; }

.cm-code {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 12px 14px 12px 18px;
  border-radius: var(--radius-md);
  border: 1px solid var(--border-subtle);
  background: var(--bg-base);
  text-align: left;
}

.cm-code-label { display: block; font-size: 0.76rem; color: var(--text-faint); }

.cm-code-value {
  font-family: var(--font-mono);
  font-size: 1.15rem;
  font-weight: 600;
  line-height: 1.3;
  color: var(--accent-secondary);
}

.cm-copy {
  padding: 8px 12px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-subtle);
  background: transparent;
  font-size: 0.82rem;
  color: var(--text-muted);
  transition: color var(--transition-fast), border-color var(--transition-fast);
}

.cm-copy:hover { color: var(--text-primary); border-color: color-mix(in oklab, var(--accent-secondary) 60%, transparent); }

.cm-after-title { width: 100%; margin: 22px 0 0; text-align: left; font-size: 0.92rem; font-weight: 600; }

.cm-after {
  width: 100%;
  margin: 12px 0 26px;
  padding: 0;
  list-style: none;
  counter-reset: cm-after;
  display: grid;
  gap: 12px;
  text-align: left;
}

.cm-after li {
  counter-increment: cm-after;
  display: grid;
  grid-template-columns: 26px 1fr;
  gap: 12px;
  align-items: start;
  font-size: 0.9rem;
  line-height: 1.5;
  color: var(--text-muted);
}

.cm-after li::before {
  content: counter(cm-after);
  width: 26px;
  height: 26px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  border: 1px solid var(--border-strong);
  font-family: var(--font-mono);
  font-size: 0.72rem;
  font-weight: 600;
  line-height: 1;
  color: var(--text-primary);
}

.cm-done-actions { display: flex; flex-wrap: wrap; justify-content: center; gap: 10px; }



/* ===== assets/css/components/commission-auth-modal.css ===== */
/* =========================================================
   Commission Auth Modal (sign-in required prompt)
   ========================================================= */
.commission-auth-modal-backdrop {
  position: fixed;
  inset: 0;
  background: oklch(0.13 0.035 280 / 70%);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--transition-base);
  z-index: var(--z-modal-backdrop);
}

.commission-auth-modal-backdrop.is-open { opacity: 1; pointer-events: auto; }

.commission-auth-modal {
  position: fixed;
  inset: 0;
  z-index: var(--z-modal);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: clamp(1rem, 4vw, 2.5rem);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity var(--transition-base), visibility 0s linear var(--transition-base);
}

.commission-auth-modal.is-open {
  opacity: 1;
  visibility: visible;
  transition: opacity var(--transition-base), visibility 0s linear;
}

.commission-auth-modal-panel {
  position: relative;
  width: 100%;
  max-width: 480px;
  max-height: min(88vh, 780px);
  overflow-y: auto;
  scrollbar-width: none;
  -ms-overflow-style: none;
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-glow-violet), var(--shadow-card);
  padding: clamp(1.75rem, 4vw, 2.75rem);
  transform: translateY(24px) scale(0.96);
  transition: transform var(--transition-base);
}

.commission-auth-modal-panel::-webkit-scrollbar { display: none; }

/* Only the panel takes clicks. The full-screen wrapper stays click-through,
   so a click beside the panel reaches the backdrop, which closes the modal. */
.commission-auth-modal.is-open .commission-auth-modal-panel { pointer-events: auto; transform: none; }

.commission-auth-modal-close {
  position: absolute;
  top: 1.25rem;
  right: 1.25rem;
  width: 40px;
  height: 40px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  border: 1px solid var(--border-subtle);
  background: transparent;
  color: var(--text-muted);
  font-size: 1rem;
  transition: border-color var(--transition-fast), color var(--transition-fast);
}

.commission-auth-modal-close:hover {
  border-color: oklch(0.85 0.15 202 / 60%);
  color: var(--accent-secondary);
}

.commission-auth-modal-title {
  font-size: clamp(1.4rem, 1.5vw + 1rem, 1.75rem);
  margin-top: 1rem;
  margin-bottom: 0.5rem;
}

.commission-auth-modal-subtitle {
  color: var(--text-muted);
  font-size: 0.95rem;
  margin-bottom: 1.75rem;
}

.commission-auth-modal-providers {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  margin-bottom: 1.5rem;
}

.commission-auth-modal-providers .btn { width: 100%; }

.commission-auth-modal-actions {
  display: flex;
  justify-content: center;
}

/* Phones and touch devices: a modal backdrop blur forces a readback of
   everything painted behind it. Dropped in favour of a more opaque
   scrim, which reads the same once the ambient layer behind it is
   already static (see ambient-background.css). */
@media (max-width: 900px), (hover: none) {
  .commission-auth-modal-backdrop {
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    background: oklch(0.13 0.035 280 / 88%);
  }
}

/* Says what the brief will ask for, so the account step reads as part of
   a process rather than as a toll gate. */
.commission-auth-modal-preview {
  margin-top: 0.85rem;
  padding: 0.75rem 1rem;
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  background: oklch(0.13 0.035 280 / 55%);
  color: var(--text-muted);
  font-size: 0.85rem;
  line-height: 1.6;
}


/* ===== assets/css/components/account-modal.css ===== */
/* =========================================================
   Account Modal (sign in / create account)
   ========================================================= */
.account-modal-backdrop {
  position: fixed;
  inset: 0;
  background: oklch(0.13 0.035 280 / 70%);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--transition-base);
  z-index: var(--z-modal-backdrop);
}

.account-modal-backdrop.is-open { opacity: 1; pointer-events: auto; }

/* Deliberately opacity/visibility, never display:none - Turnstile renders
   implicitly into .cf-turnstile via its data-sitekey attribute with no
   explicit turnstile.render() call, which requires the container to have
   real layout dimensions at all times. Switching this to display:none
   later would silently break that. */
.account-modal {
  position: fixed;
  inset: 0;
  z-index: var(--z-modal);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: clamp(1rem, 4vw, 2.5rem);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity var(--transition-base), visibility 0s linear var(--transition-base);
}

.account-modal.is-open {
  opacity: 1;
  visibility: visible;
  transition: opacity var(--transition-base), visibility 0s linear;
}

.account-modal-panel {
  position: relative;
  width: 100%;
  max-width: 440px;
  max-height: min(88vh, 780px);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-glow-violet), var(--shadow-card);
  padding: clamp(1.75rem, 4vw, 2.75rem);
  transform: translateY(24px) scale(0.96);
  transition: transform var(--transition-base);
}


/* Only the panel takes clicks. The full-screen wrapper stays click-through,
   so a click beside the panel reaches the backdrop, which closes the modal. */
.account-modal.is-open .account-modal-panel { pointer-events: auto; transform: none; }

/* The register tab (three fields plus the Turnstile widget) is taller than
   a 900px-high window, and this panel hides its scrollbar by design, so the
   submit button used to sit below the fold with nothing at all to say so.

   Same shape as .chat-modal-panel: the panel clips and an inner region
   scrolls, which lets the fade below be painted on the panel itself. On a
   scrolling panel the fade would scroll away with the content. */
.account-modal-body {
  flex: 1;
  min-height: 0;
  max-height: 100%;
  overflow-y: auto;
  scrollbar-width: none;
  -ms-overflow-style: none;
}

.account-modal-body::-webkit-scrollbar { display: none; }

/* Overflow cue in place of the scrollbar this panel deliberately hides.
   Sits inside the panel's rounded corners and ignores pointer events so
   it never intercepts a click on the submit button behind it. */
.account-modal-panel::after {
  content: "";
  position: absolute;
  left: 1px;
  right: 1px;
  bottom: 1px;
  height: 2.5rem;
  border-radius: 0 0 var(--radius-lg) var(--radius-lg);
  background: linear-gradient(to top, var(--bg-surface), oklch(0.19 0.055 285 / 0%));
  pointer-events: none;
}

.account-modal-close {
  position: absolute;
  top: 1.25rem;
  right: 1.25rem;
  width: 40px;
  height: 40px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  border: 1px solid var(--border-subtle);
  background: transparent;
  color: var(--text-muted);
  font-size: 1rem;
  transition: border-color var(--transition-fast), color var(--transition-fast);
}

.account-modal-close:hover {
  border-color: oklch(0.85 0.15 202 / 60%);
  color: var(--accent-secondary);
}

.account-modal-title {
  font-size: clamp(1.4rem, 1.5vw + 1rem, 1.75rem);
  margin-top: 1rem;
  margin-bottom: 1.25rem;
}

.account-modal-tabs {
  display: flex;
  gap: 0.4rem;
  padding: 0.3rem;
  margin-bottom: 1.5rem;
  background: oklch(0.13 0.035 280 / 55%);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
}

.account-modal-tab {
  flex: 1;
  padding: 0.55rem 0.75rem;
  border-radius: calc(var(--radius-md) - 0.3rem);
  background: transparent;
  border: none;
  color: var(--text-muted);
  font-size: 0.9rem;
  font-weight: 600;
  transition: background var(--transition-fast), color var(--transition-fast);
}

.account-modal-tab.is-active {
  background: var(--accent-primary);
  color: var(--text-primary);
}

.account-form { display: flex; flex-direction: column; gap: 1.25rem; }

/* No global [hidden] rule, and both of these set display. */
.account-form[hidden],
.account-oauth[hidden] { display: none; }

/* Google / Discord (partials/oauth-providers.php), plus "Sign up with
   email" on the sign-up tab. */
.account-oauth {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}

.account-oauth .btn { width: 100%; }

/* "or with email" between the providers and the sign-in form. */
#account-panel-login .auth-or { margin: 1.1rem 0.25rem 1.25rem; }

.account-signup-back { align-self: center; }

.account-switch-line {
  margin: 1.25rem 0 0;
  text-align: center;
  color: var(--text-muted);
  font-size: 0.85rem;
}

/* Why the sign-up tab opened by itself (account-modal.php). */
.account-modal-notice {
  margin: 0 0 1.25rem;
  padding: 0.75rem 1rem;
  border-radius: var(--radius-md);
  background: color-mix(in oklab, var(--accent-secondary) 8%, transparent);
  border: 1px solid color-mix(in oklab, var(--accent-secondary) 35%, transparent);
  color: var(--text-primary);
  font-size: 0.875rem;
}

/* The Terms and Privacy box, last in the sign-up form. Same look as the
   commission form's .cm-check. */
.auth-consent {
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
  padding: 0.65rem 0.8rem;
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-sm);
  background: color-mix(in oklab, var(--bg-base) 55%, transparent);
  cursor: pointer;
  transition: border-color var(--transition-fast);
}

.auth-consent:has(input:checked) {
  border-color: color-mix(in oklab, var(--accent-secondary) 40%, transparent);
}

.auth-consent input {
  flex-shrink: 0;
  width: 20px;
  height: 20px;
  margin: 1px 0 0;
  accent-color: var(--accent-primary);
  cursor: pointer;
}

/* font-weight: .cf-field label sets 600 on the label. */
.auth-consent span {
  font-size: 0.8rem;
  font-weight: 400;
  line-height: 1.5;
  color: var(--text-muted);
}

.auth-consent a {
  color: var(--accent-secondary);
  text-decoration: underline;
}

.auth-consent a:hover { color: var(--text-primary); }

.cf-field.has-error .auth-consent { border-color: var(--accent-error); }

.account-modal-link-btn {
  background: transparent;
  border: none;
  /* 24px floor per WCAG 2.2 AA 2.5.8. These are standalone controls
     under a field, not links inside a sentence, so the inline
     exception does not cover them. */
  display: inline-flex;
  align-items: center;
  min-height: 24px;
  padding: 0;
  color: var(--accent-secondary);
  font-size: 0.85rem;
  font-weight: 600;
  text-decoration: underline;
}

/* Sits inside the password .cf-field, under the input, so it reads as
   attached to that field rather than as a third form control. */
.account-forgot-trigger {
  align-self: flex-start;
  font-size: 0.8rem;
}

.account-forgot-lead {
  color: var(--text-muted);
  font-size: 0.9rem;
  margin: 0;
}

.account-resend-prompt {
  padding: 0.75rem 1rem;
  background: oklch(0.19 0.055 285 / 50%);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  font-size: 0.85rem;
  color: var(--text-muted);
}

.account-resend-prompt p { margin: 0 0 0.4rem; }

.account-modal-success {
  text-align: center;
  padding: 1rem 0;
}

.account-modal-success .icon {
  font-size: 2.25rem;
  color: var(--accent-secondary);
  margin-bottom: 0.75rem;
}

.account-modal-success h3 { margin: 0 0 0.5rem; }

.account-modal-success p {
  color: var(--text-muted);
  margin: 0 0 1.25rem;
}

.cf-turnstile { margin: 0.25rem 0; }

.account-status-banner {
  max-width: 640px;
  margin: 1.5rem auto 0;
  padding: 0.9rem 1.25rem;
  border-radius: var(--radius-md);
  font-size: 0.9rem;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  background: oklch(0.19 0.055 285 / 50%);
}

.account-status-banner p { margin: 0; }

.account-status-banner--success { border: 1px solid color-mix(in oklab, var(--accent-secondary) 45%, transparent); }
.account-status-banner--error { border: 1px solid color-mix(in oklab, var(--accent-error) 45%, transparent); }

/* =========================================================
   Standalone auth page (reset-password.php)

   A full page rather than a modal, because it's the landing
   target of a link in an email. Reuses .account-form and the
   .cf-* form primitives so it stays in step with the modal's
   fields; only the page chrome around them is new.
   ========================================================= */
.auth-page {
  min-height: calc(100vh - var(--nav-height));
  display: flex;
  align-items: center;
  justify-content: center;
  padding: clamp(2rem, 6vw, 4rem) 1rem;
}

.auth-page-card {
  width: 100%;
  max-width: 440px;
  padding: clamp(1.75rem, 4vw, 2.5rem);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-glow-violet);
}

.auth-page-title {
  font-size: 1.5rem;
  font-weight: 700;
  margin-bottom: 0.5rem;
}

.auth-page-lead {
  color: var(--text-muted);
  font-size: 0.9rem;
  margin-bottom: 1.75rem;
}

/* Phones and touch devices: a modal backdrop blur forces a readback of
   everything painted behind it. Dropped in favour of a more opaque
   scrim, which reads the same once the ambient layer behind it is
   already static (see ambient-background.css). */
@media (max-width: 900px), (hover: none) {
  .account-modal-backdrop {
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    background: oklch(0.13 0.035 280 / 88%);
  }
}

/* Password reveal. The wrapper is relative so the button can sit inside
   the field's right edge; the input keeps room for it so long values do
   not run underneath. */
.cf-password-wrap { position: relative; display: block; }

.cf-password-wrap input { padding-right: 4.25rem; }

.cf-password-toggle {
  position: absolute;
  top: 50%;
  right: 0.5rem;
  transform: translateY(-50%);
  min-height: 24px;
  padding: 0.3rem 0.6rem;
  border-radius: var(--radius-sm);
  /* .btn is not in play here, and there is no global button reset, so
     both of these have to be explicit or the browser's own chrome shows
     through. */
  background: transparent;
  border: 1px solid var(--border-subtle);
  color: var(--text-muted);
  font-size: 0.75rem;
  font-weight: 600;
  transition: border-color var(--transition-fast), color var(--transition-fast);
}

.cf-password-toggle:hover {
  border-color: oklch(0.85 0.15 202 / 60%);
  color: var(--accent-secondary);
}

.cf-password-toggle:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px oklch(0.85 0.15 202 / 30%);
}


/* ===== assets/css/components/cookie-consent-modal.css ===== */
/* =========================================================
   Cookie notice

   A bottom banner, not a dialog - see the comment at the top of
   includes/partials/cookie-consent-modal.php for why it stopped being
   one. It must never cover content or trap focus: it sits below every
   overlay in the stacking scale and above nothing but the page.
   ========================================================= */
.cookie-banner {
  position: fixed;
  left: 1rem;
  right: 1rem;
  bottom: 1rem;
  margin-inline: auto;
  max-width: 44rem;
  z-index: var(--z-back-to-top);

  display: flex;
  align-items: center;
  gap: 1.25rem;
  padding: 0.9rem 1.1rem;

  background: var(--bg-surface);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-glow-violet);
}

.cookie-banner[hidden] { display: none; }

.cookie-banner-text {
  flex: 1;
  color: var(--text-muted);
  font-size: 0.85rem;
  line-height: 1.6;
}

.cookie-banner-text a {
  color: var(--accent-secondary);
  text-decoration: underline;
}

/* .btn declares a border but no background, so a variant that does not
   set one falls through to the browser's default button chrome. */
.cookie-banner-dismiss {
  flex-shrink: 0;
  background: transparent;
  padding: 0.6rem 1.25rem;
  font-size: 0.85rem;
}

/* The back-to-top button lives in the same corner. Clear it, rather than
   letting the two overlap on the one viewport width where both show. */
@media (min-width: 641px) {
  .cookie-banner { right: 5.5rem; }
}

@media (max-width: 640px) {
  .cookie-banner {
    flex-direction: column;
    align-items: stretch;
    gap: 0.85rem;
    text-align: center;
  }
}


/* ===== assets/css/components/contact.css ===== */
/* =========================================================
   Contact: the postcard

   Message side (topic chips and the ruled draft) and address side (To,
   Subject, send), with the sample-texture stamp stuck over the card's
   top edge. Markup and drafts: includes/partials/contact.php. Topic
   switching, the stamp and Copy: contact-postcard.js. The social
   buttons are social-buttons.css, shared with About.

   Cost: switching topics is opacity/transform on stacked drafts; the
   stamp canvas is repainted once per switch; the postmark "thunk" is a
   one-off transform animation. Nothing runs while idle.
   ========================================================= */
.contact {
  isolation: isolate;
  padding-block: clamp(3rem, 6vw, 5.5rem) clamp(3rem, 5vw, 4.5rem);
}

.contact::before {
  content: '';
  position: absolute;
  inset: 0;
  background: var(--gradient-hero);
  transform: rotate(180deg);
  z-index: -1;
  pointer-events: none;
}

.contact-head { max-width: 40rem; margin-bottom: clamp(2.25rem, 5vw, 3.25rem); }

.contact-title {
  font-size: clamp(2rem, 3.2vw + 1rem, 3rem);
  font-weight: 700;
  letter-spacing: -0.035em;
  line-height: 1.08;
  text-wrap: balance;
  margin-bottom: 1rem;
}

.contact-subtitle {
  color: var(--text-muted);
  font-size: clamp(1rem, 0.5vw + 0.9rem, 1.125rem);
  line-height: 1.7;
  max-width: 34rem;
}

/* ---------- The card ----------
   Its variables live on the card itself, not the section, because the
   careers page uses the same card (includes/partials/postcard.php). */
.postcard {
  --postcard-line: 2rem;
  --postcard-rule: color-mix(in oklch, var(--accent-primary) 16%, transparent);
  --postcard-cyan-edge: color-mix(in oklch, var(--accent-secondary) 60%, transparent);
  --postcard-ease: cubic-bezier(0.16, 1, 0.3, 1);
  --postcard-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
  --postcard-inset: clamp(1.25rem, 3vw, 2.5rem);
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1.45fr) 1px minmax(0, 1fr);
  gap: clamp(1.5rem, 3.5vw, 3rem);
  padding: clamp(1.5rem, 3.2vw, 2.75rem);
  border-radius: var(--radius-lg);
  border: 1px solid transparent;
  background-image:
    radial-gradient(70% 55% at 0% 0%, color-mix(in oklch, var(--accent-secondary) 6%, transparent), transparent 70%),
    linear-gradient(var(--bg-surface), var(--bg-surface)),
    linear-gradient(140deg, color-mix(in oklch, var(--accent-secondary) 50%, transparent), color-mix(in oklch, var(--accent-primary) 22%, transparent) 45%, color-mix(in oklch, var(--accent-primary) 40%, transparent));
  background-origin: padding-box, padding-box, border-box;
  background-clip: padding-box, padding-box, border-box;
  box-shadow: var(--shadow-glow-violet);
}

.postcard-divider {
  background: linear-gradient(to bottom, transparent, var(--border-strong) 12%, var(--border-strong) 88%, transparent);
}

/* ---------- Message side ---------- */
.postcard-message { display: flex; flex-direction: column; gap: 1.5rem; min-width: 0; }

/* No global [hidden] rule, and the chips only work with the script. */
.postcard-topics[hidden],
.postcard-copy[hidden] { display: none; }

.postcard-topics { border: 0; margin: 0; padding: 0; min-width: 0; }

.postcard-topics legend {
  padding: 0;
  margin-bottom: 0.6rem;
  font-size: 0.85rem;
  font-weight: 500;
  color: var(--text-muted);
}

.postcard-chips { display: flex; flex-wrap: wrap; gap: 0.5rem; }

.postcard-chip { position: relative; }

.postcard-chip input {
  position: absolute;
  inset: 0;
  opacity: 0;
  margin: 0;
  cursor: pointer;
}

.postcard-chip span {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  min-height: 38px;
  padding: 0.4rem 0.9rem;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-subtle);
  background: transparent;
  color: var(--text-muted);
  font-size: 0.9rem;
  font-weight: 500;
  line-height: 1.3;
  transition: color 0.2s ease, border-color 0.2s ease, background-color 0.2s ease;
}

.postcard-chip .icon {
  font-size: 0.8em;
  width: 0;
  opacity: 0;
  transition: width 0.25s var(--postcard-ease), opacity 0.2s ease;
}

.postcard-chip input:hover + span { color: var(--text-primary); border-color: var(--border-strong); }

.postcard-chip input:checked + span {
  color: var(--text-primary);
  background: var(--bg-surface-alt);
  border-color: var(--postcard-cyan-edge);
}

.postcard-chip input:checked + span .icon { width: 0.8em; opacity: 1; color: var(--accent-secondary); }
.postcard-chip input:focus-visible + span { outline: 2px solid var(--accent-secondary); outline-offset: 2px; }

/* The draft, ruled like a postcard. Every line is one --postcard-line
   tall, so the rules stay under the words whatever wraps. All drafts
   share one grid cell and the tallest sets the height. */
.postcard-letters {
  display: grid;
  font-size: 1.05rem;
  line-height: var(--postcard-line);
  /* One line-tall tile with a 1px rule at its foot, repeated. Firefox
     drew a repeating-linear-gradient with calc() stops as thick bands. */
  background: linear-gradient(to bottom, transparent calc(100% - 1px), var(--postcard-rule) 0) 0 0 / 100% var(--postcard-line) repeat-y;
}

.postcard-letter {
  grid-area: 1 / 1;
  display: grid;
  gap: var(--postcard-line);
  align-content: start;
  visibility: hidden;
  opacity: 0;
  transform: translateY(8px);
  transition: opacity 0.2s ease, transform 0.35s var(--postcard-ease), visibility 0s linear 0.35s;
}

.postcard-letter.is-active {
  visibility: visible;
  opacity: 1;
  transform: none;
  transition: opacity 0.3s ease 0.08s, transform 0.45s var(--postcard-ease) 0.08s, visibility 0s;
}

.contact-blank {
  font-family: var(--font-mono);
  font-size: 0.8em;
  font-weight: 500;
  line-height: 1; /* a mono inline box at the full line height would push its line off the rules */
  color: var(--text-muted);
  padding: 0.1rem 0.35rem;
  border-radius: 4px;
  background: color-mix(in oklch, var(--accent-secondary) 7%, transparent);
  -webkit-box-decoration-break: clone;
  box-decoration-break: clone;
}

.contact-blank::before { content: '['; }
.contact-blank::after { content: ']'; }

/* ---------- Address side ---------- */
.postcard-address {
  display: flex;
  flex-direction: column;
  gap: 1.35rem;
  min-width: 0;
  padding-top: clamp(8.5rem, 12vw, 10rem); /* the stamp's corner */
}

.postcard-field {
  display: grid;
  gap: 0.2rem;
  padding-bottom: 0.6rem;
  border-bottom: 1px solid var(--postcard-rule);
}

.postcard-label { font-size: 0.8rem; font-weight: 500; color: var(--text-muted); }

.postcard-to {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
}

.postcard-email {
  font-family: var(--font-heading);
  font-size: clamp(1.15rem, 1vw + 0.85rem, 1.4rem);
  font-weight: 600;
  letter-spacing: -0.02em;
  overflow-wrap: anywhere;
  user-select: all;
}

.postcard-copy {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  min-height: 32px;
  padding: 0.3rem 0.7rem;
  border-radius: var(--radius-sm);
  background: transparent;
  border: 1px solid var(--border-strong);
  color: var(--text-muted);
  font-size: 0.8rem;
  font-weight: 600;
  transition: color 0.2s ease, border-color 0.2s ease;
}

.postcard-copy:hover,
.postcard-copy.is-copied { color: var(--accent-secondary); border-color: var(--postcard-cyan-edge); }
.postcard-copy:focus-visible { outline: 2px solid var(--accent-secondary); outline-offset: 2px; }

.postcard-subjects { display: grid; }

.postcard-subject {
  grid-area: 1 / 1;
  font-weight: 500;
  visibility: hidden;
  opacity: 0;
  transform: translateY(6px);
  transition: opacity 0.2s ease, transform 0.35s var(--postcard-ease), visibility 0s linear 0.35s;
}

.postcard-subject.is-active {
  visibility: visible;
  opacity: 1;
  transform: none;
  transition: opacity 0.3s ease 0.05s, transform 0.4s var(--postcard-ease) 0.05s, visibility 0s;
}

.postcard-send { margin-top: auto; display: grid; justify-items: start; gap: 0.65rem; }
.postcard-send .btn { padding: 1rem 2rem; font-size: 1rem; }
.postcard-send .btn .icon { transition: transform 0.4s var(--postcard-spring); }

.postcard-send .btn:hover .icon,
.postcard-send .btn:focus-visible .icon { transform: translate(3px, -3px) rotate(-6deg); }

.postcard-hint { font-size: 0.85rem; line-height: 1.5; color: var(--text-muted); max-width: 30ch; }

/* ---------- Stamp and postmark ----------
   Two layers: the outer one carries the tilt and the glow, the inner
   one the perforated mask. On one element the mask would cut the glow
   off, since a mask applies after filters. */
.postcard-stamp,
.postcard-postmark { visibility: hidden; }

.postcard.is-live .postcard-stamp,
.postcard.is-live .postcard-postmark { visibility: visible; }

.postcard-stamp {
  position: absolute;
  top: -1.1rem;
  right: var(--postcard-inset);
  width: 118px;
  rotate: 4deg;
  filter: drop-shadow(0 14px 22px color-mix(in oklch, var(--accent-primary) 45%, transparent));
  z-index: 2;
}

/* Perforated edge: a solid middle plus a grid of punched holes, where
   only the holes along the border survive the union. */
.postcard-stamp-paper {
  padding: 7px;
  background: var(--text-primary);
  -webkit-mask:
    linear-gradient(#000 0 0) center / calc(100% - 10px) calc(100% - 10px) no-repeat,
    radial-gradient(circle 3.4px at 5px 5px, transparent 97%, #000) -5px -5px / 10px 10px;
  mask:
    linear-gradient(#000 0 0) center / calc(100% - 10px) calc(100% - 10px) no-repeat,
    radial-gradient(circle 3.4px at 5px 5px, transparent 97%, #000) -5px -5px / 10px 10px;
}

/* 4:5 on the art itself, matching the crop in contact-postcard.js. */
.postcard-stamp canvas { display: block; width: 100%; aspect-ratio: 4 / 5; background: var(--bg-base); }

.postcard-postmark {
  position: absolute;
  top: 2rem;
  right: calc(var(--postcard-inset) + 108px);
  width: 96px;
  height: 96px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  border: 2px solid color-mix(in oklch, var(--text-muted) 70%, transparent);
  box-shadow: inset 0 0 0 5px transparent, inset 0 0 0 6px color-mix(in oklch, var(--text-muted) 45%, transparent);
  color: var(--text-muted);
  text-align: center;
  font-family: var(--font-mono);
  font-size: 0.56rem;
  font-weight: 600;
  line-height: 1.4;
  letter-spacing: 0.04em;
  rotate: -14deg;
  z-index: 3;
  pointer-events: none;
  /* Light ink over the dark card, dark ink where it crosses the white
     stamp paper, like a real postmark. Blends within .contact's
     isolated group only. */
  mix-blend-mode: difference;
}

.postcard-postmark b { display: block; font-size: 0.64rem; letter-spacing: 0.05em; color: var(--text-primary); }

.postcard-postmark.is-stamping { animation: postcard-thunk 0.5s var(--postcard-spring); }
.postcard-stamp.is-stamping { animation: postcard-press 0.5s var(--postcard-ease); }

@keyframes postcard-thunk {
  0% { opacity: 0; transform: scale(1.45); }
  55% { opacity: 1; transform: scale(0.94); }
  100% { opacity: 1; transform: scale(1); }
}

@keyframes postcard-press {
  0%, 30% { transform: none; }
  45% { transform: scale(0.95); }
  100% { transform: none; }
}

/* ---------- Under the card ---------- */
.contact-after {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 1.25rem 2rem;
  margin-top: clamp(1.75rem, 3vw, 2.25rem);
}

.contact-socials,
.contact-book { display: flex; flex-wrap: wrap; align-items: center; gap: 0.9rem; }
.contact-socials-row { display: flex; gap: 0.7rem; }

.contact-socials > span,
.contact-book > span { font-size: 0.9rem; color: var(--text-muted); }

/* ---------- Narrow: one column, stamp tucked into the corner ---------- */
@media (max-width: 820px) {
  .postcard {
    grid-template-columns: minmax(0, 1fr);
    gap: 1.75rem;
    padding: 1.5rem 1.25rem 1.75rem;
  }

  .postcard-divider {
    height: 1px;
    background: repeating-linear-gradient(to right, var(--border-strong) 0 6px, transparent 6px 12px);
  }

  .postcard-stamp { width: 78px; top: -0.9rem; right: 1rem; }
  .postcard-stamp-paper { padding: 5px; }
  .postcard-postmark { width: 72px; height: 72px; top: 2.4rem; right: 4.9rem; font-size: 0.44rem; }
  .postcard-postmark b { font-size: 0.5rem; }

  /* Room for the stamp above the chips; without the script there are
     no chips and no stamp, so no room either. */
  .postcard.is-live .postcard-message { padding-top: 5.25rem; }
  .postcard-address { padding-top: 0; }

  .postcard-send { justify-items: stretch; }
  .postcard-send .btn { width: 100%; }
  .postcard-hint { max-width: none; }

  .contact-after { flex-direction: column; align-items: flex-start; }
  .contact-book { flex-direction: column; align-items: flex-start; gap: 0.75rem; }
}

@media (prefers-reduced-motion: reduce) {
  .postcard-letter,
  .postcard-letter.is-active,
  .postcard-subject,
  .postcard-subject.is-active { transform: none; transition: none; }

  .postcard-postmark.is-stamping,
  .postcard-stamp.is-stamping { animation: none; }

  .postcard-send .btn:hover .icon,
  .postcard-send .btn:focus-visible .icon { transform: none; }
}


/* ===== assets/css/components/careers.css ===== */
/* =========================================================
   Careers page: base, section heads, and the hero with its 3D viewport
   Markup: careers.php, includes/partials/careers-*.php.
   Scripts: assets/js/careers.js and modules/careers-*.js.
   The hero is carried over from the owner's careers draft, in the site's
   own fonts and colour tokens.

   Cost: the viewport canvas redraws every frame only while it is on
   screen (careers-hero.js pauses it off screen). Its chips are solid
   rather than blurred, so nothing re-blurs the animating model.
   ========================================================= */
.careers {
  --careers-ease: cubic-bezier(0.16, 1, 0.3, 1);
  --careers-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
}

.careers > section { padding-block: clamp(3.5rem, 7vw, 6rem); }

/* There is no global [hidden] rule, and each of these sets display. They
   stay hidden until their script runs. */
.careers-vp[hidden],
.careers-choice-visual[hidden],
.careers-fields[hidden],
.careers-load[hidden],
.careers-sticker[hidden] { display: none; }

/* ---------- Section heads ---------- */
.careers-head { max-width: 40rem; margin-bottom: clamp(2rem, 4.5vw, 3rem); }
.careers-head h2 {
  font-size: clamp(2rem, 3vw + 1rem, 3rem);
  font-weight: 700;
  letter-spacing: -0.035em;
  line-height: 1.06;
  text-wrap: balance;
  margin-bottom: 0.9rem;
}
.careers-head p { color: var(--text-muted); font-size: clamp(1rem, 0.5vw + 0.9rem, 1.125rem); line-height: 1.7; max-width: 36rem; }

/* Reveal: hidden only once careers-page.js has armed the page, so without
   it (or if it fails) everything stays visible. Not [data-reveal], which
   hides content with no way back when scripts don't run. */
[data-careers-reveal] {
  transition: opacity 0.8s var(--careers-ease), transform 0.8s var(--careers-ease);
  transition-delay: calc(var(--d, 0) * 90ms);
}
.careers.is-armed [data-careers-reveal]:not(.is-in) { opacity: 0; transform: translateY(22px); }

/* The standard live ping (chat-details.css), for "Open for commissions". */
.careers-ping {
  position: absolute;
  inset: -2px;
  z-index: -1;
  border-radius: 50%;
  background: var(--accent-secondary);
  opacity: 0;
  animation: careers-ping 1.6s cubic-bezier(0, 0, 0.2, 1) infinite;
}
@keyframes careers-ping {
  0% { transform: scale(1); opacity: 0.5; }
  60% { opacity: 0; }
  80%, 100% { transform: scale(2.2); opacity: 0; }
}

/* =========================================================
   Hero
   ========================================================= */
.careers > .careers-hero { padding-block: clamp(36px, 6vw, 88px) clamp(56px, 8vw, 104px); }
.careers-sprite { position: absolute; width: 0; height: 0; overflow: hidden; }
.careers-hero-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.04fr) minmax(0, 0.96fr);
  gap: clamp(32px, 5vw, 72px);
  align-items: center;
}
/* Text alone when the viewport isn't running. */
.careers-hero-grid:not(:has(.careers-vp:not([hidden]))) { grid-template-columns: minmax(0, 1fr); }
.careers-hero-title {
  font-size: clamp(2.7rem, 1.4rem + 4vw, 4.6rem);
  font-weight: 700;
  line-height: 1.02;
  letter-spacing: -0.045em;
  margin: 0 0 1.4rem;
  text-wrap: balance;
}
.careers-lede { font-size: clamp(1.12rem, 1rem + 0.45vw, 1.32rem); line-height: 1.6; color: var(--text-muted); max-width: 42ch; margin-bottom: 2.1rem; }
.careers-ctas { display: flex; flex-wrap: wrap; gap: 0.75rem; }

/* The letter pop, the one load-in moment, and a boing on hover. */
.careers-pop-w { display: inline-block; white-space: nowrap; }
.careers-pop-l { display: inline-block; transform-origin: 50% 90%; }
@media (prefers-reduced-motion: no-preference) {
  .careers-pop-l { animation: careers-letter-in 0.75s var(--careers-spring) both; animation-delay: calc(var(--i) * 26ms + 80ms); }
  .careers-pop.is-settled .careers-pop-l { animation: none; }
  .careers-pop.is-settled .careers-pop-l.is-boing { animation: careers-boing 0.6s var(--careers-spring); }
}
@keyframes careers-letter-in { from { opacity: 0; transform: translateY(0.55em) scale(0.5) rotate(-12deg); } }
@keyframes careers-boing {
  30% { transform: translateY(-0.2em) scale(1.12, 0.88) rotate(-7deg); }
  65% { transform: translateY(0) scale(0.94, 1.07) rotate(2deg); }
}

/* ---------- The 3D viewport ---------- */
.careers-vp {
  margin: 0;
  position: relative;
  isolation: isolate;
  background: color-mix(in oklch, var(--bg-base) 55%, var(--bg-surface));
  color: var(--text-primary);
  border-radius: var(--radius-lg);
  overflow: hidden;
  box-shadow: var(--shadow-glow-violet);
}
.careers-vp-stage { position: relative; height: clamp(360px, 44vw, 560px); }
.careers-vp-stage::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  background-image:
    linear-gradient(color-mix(in oklch, var(--accent-sky) 8%, transparent) 1px, transparent 1px),
    linear-gradient(90deg, color-mix(in oklch, var(--accent-sky) 8%, transparent) 1px, transparent 1px);
  background-size: 30px 30px;
  background-position: center;
  -webkit-mask-image: radial-gradient(ellipse 70% 70% at 50% 55%, black 30%, transparent 75%);
  mask-image: radial-gradient(ellipse 70% 70% at 50% 55%, black 30%, transparent 75%);
}
.careers-vp-stage canvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block; touch-action: pan-y; cursor: grab; }
.careers-vp-stage canvas:active { cursor: grabbing; }
.careers-vp.is-paint canvas { touch-action: none; cursor: crosshair; }
.careers-vp-stage canvas:focus-visible { outline: 3px solid var(--accent-secondary); outline-offset: -6px; border-radius: var(--radius-lg); }

.careers-vp-bar { position: absolute; top: 14px; left: 14px; right: 14px; display: flex; justify-content: space-between; gap: 10px; z-index: 2; pointer-events: none; }
.careers-vp-bar > * { pointer-events: auto; }
.careers-seg {
  position: relative;
  display: inline-flex;
  padding: 4px;
  background: color-mix(in oklch, var(--bg-base) 88%, transparent);
  border: 1px solid color-mix(in oklch, var(--text-primary) 12%, transparent);
  border-radius: var(--radius-pill);
}
.careers-seg-ind {
  position: absolute;
  top: 4px;
  bottom: 4px;
  left: 0;
  width: 0;
  background: var(--accent-primary);
  border-radius: var(--radius-pill);
  transition: transform 0.5s var(--careers-spring), width 0.5s var(--careers-spring);
}
.careers-seg button {
  position: relative;
  appearance: none;
  border: 0;
  background: transparent;
  cursor: pointer;
  color: var(--text-muted);
  font: 600 0.88rem/1 var(--font-body);
  min-height: 44px;
  padding: 0 0.95rem;
  border-radius: var(--radius-pill);
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  transition: color 0.2s;
}
.careers-seg .icon { font-size: 1.15rem; }
.careers-seg button[aria-pressed="true"] { color: var(--text-primary); }
.careers-seg button:focus-visible { outline: 2px solid var(--accent-secondary); outline-offset: 0; }
.careers-vp-reset {
  appearance: none;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  cursor: pointer;
  display: grid;
  place-items: center;
  color: var(--text-primary);
  font-size: 1.2rem;
  background: color-mix(in oklch, var(--bg-base) 88%, transparent);
  border: 1px solid color-mix(in oklch, var(--text-primary) 12%, transparent);
  transition: transform 0.5s var(--careers-spring);
}
.careers-vp-reset:hover { transform: rotate(-90deg); }
.careers-vp-reset:focus-visible { outline: 2px solid var(--accent-secondary); outline-offset: 2px; }

.careers-vp-foot {
  position: absolute;
  left: 14px;
  right: 14px;
  bottom: 14px;
  z-index: 2;
  display: flex;
  flex-wrap: wrap-reverse;
  align-items: flex-end;
  justify-content: space-between;
  gap: 10px;
  pointer-events: none;
}
.careers-vp-foot > * { pointer-events: auto; }
.careers-vp-hint { font-size: 0.86rem; font-weight: 500; color: var(--text-muted); max-width: 34ch; line-height: 1.4; }
.careers-swatches {
  display: flex;
  gap: 2px;
  padding: 3px;
  border-radius: var(--radius-pill);
  background: color-mix(in oklch, var(--bg-base) 88%, transparent);
  border: 1px solid color-mix(in oklch, var(--text-primary) 12%, transparent);
  transform: translateY(12px) scale(0.9);
  opacity: 0;
  visibility: hidden;
  transition: transform 0.45s var(--careers-spring), opacity 0.25s, visibility 0s 0.45s;
}
.careers-vp.is-paint .careers-swatches { transform: none; opacity: 1; visibility: visible; transition: transform 0.45s var(--careers-spring), opacity 0.25s, visibility 0s; }
.careers-swatch { appearance: none; border: 0; background: transparent; width: 40px; height: 40px; border-radius: 50%; display: grid; place-items: center; cursor: pointer; }
.careers-swatch i {
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: var(--c);
  box-shadow: 0 0 0 2px color-mix(in oklch, var(--bg-base) 90%, transparent), 0 0 0 3px color-mix(in oklch, var(--text-primary) 25%, transparent);
  transition: transform 0.35s var(--careers-spring), box-shadow 0.2s;
}
.careers-swatch[aria-pressed="true"] i { transform: scale(1.18); box-shadow: 0 0 0 2px color-mix(in oklch, var(--bg-base) 90%, transparent), 0 0 0 4px var(--text-primary); }
.careers-swatch:focus-visible { outline: 2px solid var(--accent-secondary); outline-offset: -2px; }

/* ---------- Narrow screens ---------- */
@media (max-width: 920px) {
  .careers-hero-grid { grid-template-columns: minmax(0, 1fr); }
  .careers-vp-stage { height: min(96vw, 460px); }
}
@media (max-width: 420px) {
  .careers-seg .icon { display: none; }
  .careers-seg button { padding: 0 0.75rem; }
}


/* ===== assets/css/components/careers-roles.css ===== */
/* =========================================================
   Careers: "Two crafts. Pick yours." The two roles as big side-by-side
   cards with poster-size words, an "or" between them. Markup:
   includes/partials/careers-roles.php. The Blender card's wireframe and
   the Texture card's layer stack: modules/careers-roles.js.

   Cost: hover lifts and dims with transform and opacity only; the
   wireframe redraws only while its card is hovered or focused.
   ========================================================= */
.careers-head--loud { max-width: none; }
.careers-head--loud h2 { font-size: clamp(2.6rem, 4.6vw + 1rem, 5rem); letter-spacing: -0.055em; line-height: 0.98; }

.careers-choices { position: relative; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1.25rem; align-items: stretch; }

.careers-choice {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  padding: clamp(1.4rem, 2.6vw, 2.25rem);
  border-radius: var(--radius-lg);
  border: 1px solid var(--border-subtle);
  transition: transform 0.6s var(--careers-ease), box-shadow 0.5s ease, opacity 0.4s ease, border-color 0.4s ease;
}
.careers-choice:hover,
.careers-choice:focus-within {
  transform: translateY(-6px);
  border-color: color-mix(in oklch, var(--accent-secondary) 50%, transparent);
  box-shadow: var(--shadow-glow-cyan);
}
.careers-choices:has(.careers-choice:hover) .careers-choice:not(:hover) { opacity: 0.6; }

/* Each side gets its own room: a Blender viewport floor, a texture
   editor's transparency checker. */
.careers-choice::before { content: ''; position: absolute; z-index: -1; pointer-events: none; }
.careers-choice--blender { background: radial-gradient(90% 50% at 50% 34%, color-mix(in oklch, var(--accent-secondary) 13%, transparent), transparent 70%), var(--bg-surface); }
.careers-choice--blender::before {
  left: -40%;
  right: -40%;
  top: 22%;
  height: 46%;
  background-image:
    linear-gradient(to right, color-mix(in oklch, var(--accent-secondary) 16%, transparent) 1px, transparent 1px),
    linear-gradient(to bottom, color-mix(in oklch, var(--accent-secondary) 16%, transparent) 1px, transparent 1px);
  background-size: 44px 44px;
  transform: perspective(520px) rotateX(66deg);
  transform-origin: 50% 100%;
  -webkit-mask-image: radial-gradient(60% 90% at 50% 100%, black, transparent 80%);
  mask-image: radial-gradient(60% 90% at 50% 100%, black, transparent 80%);
}
/* No floor while the wireframe is hidden (no JavaScript): it would land
   behind the card's text instead. */
.careers-choice--blender:has(.careers-choice-visual[hidden])::before { display: none; }
.careers-choice--texture { background: radial-gradient(90% 50% at 50% 34%, color-mix(in oklch, var(--accent-primary) 24%, transparent), transparent 70%), var(--bg-surface); }
.careers-choice--texture::before {
  inset: 0;
  background: conic-gradient(color-mix(in oklch, var(--text-primary) 5%, transparent) 25%, transparent 0 50%, color-mix(in oklch, var(--text-primary) 5%, transparent) 0 75%, transparent 0) 0 0 / 26px 26px;
  -webkit-mask-image: radial-gradient(70% 45% at 50% 32%, black, transparent 80%);
  mask-image: radial-gradient(70% 45% at 50% 32%, black, transparent 80%);
}

/* The word, poster-sized. Outlined at rest, solid when you lean in. */
.careers-choice-word {
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: clamp(3.6rem, 9.6vw, 9.9rem);
  line-height: 0.86;
  letter-spacing: -0.065em;
  white-space: nowrap;
  color: transparent;
  -webkit-text-stroke: 1.5px color-mix(in oklch, var(--accent-secondary) 45%, transparent);
  margin: -0.04em 0 0 -0.05em;
  user-select: none;
  transition: color 0.45s ease, -webkit-text-stroke-color 0.45s ease;
}
.careers-choice--texture .careers-choice-word { -webkit-text-stroke-color: color-mix(in oklch, var(--accent-primary) 75%, transparent); }
.careers-choice:hover .careers-choice-word,
.careers-choice:focus-within .careers-choice-word { color: var(--text-primary); -webkit-text-stroke-color: transparent; }

.careers-choice-visual { position: relative; height: clamp(14rem, 24vw, 18rem); margin-top: -1.6rem; display: grid; place-items: center; }
.careers-wire-box { position: relative; height: 100%; aspect-ratio: 1; max-width: 100%; }
.careers-wire-box canvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }

/* Three texture layers stacked like sheets in a file; they part when you
   lean in, so each one reads on its own. */
.careers-layers { position: relative; height: 82%; aspect-ratio: 1; max-width: 100%; perspective: 900px; }
.careers-layer {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  border-radius: var(--radius-sm);
  border: 1px solid color-mix(in oklch, var(--accent-secondary) 28%, transparent);
  background: color-mix(in oklch, var(--bg-surface) 30%, transparent);
  transform: translateY(14%) rotateX(58deg) rotateZ(-38deg) translateZ(calc(var(--i) * 20px));
  transition: transform 0.8s var(--careers-spring);
}
.careers-layer[data-choice-layer="base"] { background: transparent; }
.careers-choice:hover .careers-layer,
.careers-choice:focus-within .careers-layer { transform: translateY(14%) rotateX(58deg) rotateZ(-38deg) translateZ(calc(var(--i) * 64px)); }

.careers-choice-body { display: grid; gap: 1.1rem; margin-top: 0.75rem; flex: 1; align-content: start; }
.careers-choice-top { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem 0.9rem; }
.careers-choice h3 { font-size: clamp(1.5rem, 1.2vw + 1rem, 1.9rem); font-weight: 700; letter-spacing: -0.03em; }
.careers-role-new {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  font-weight: 600;
  color: var(--accent-gold);
  border: 1px solid color-mix(in oklch, var(--accent-gold) 40%, transparent);
  border-radius: var(--radius-pill);
  padding: 0.15rem 0.65rem;
}
.careers-choice-lead { color: var(--text-muted); font-size: 1.02rem; line-height: 1.7; }
.careers-choice-meta {
  padding-top: 1.1rem;
  border-top: 1px solid var(--border-subtle);
}
.careers-choice h4 { font-family: var(--font-body); font-size: 0.85rem; font-weight: 600; letter-spacing: 0; color: var(--text-muted); margin-bottom: 0.55rem; }
.careers-choice-meta p { font-size: 0.92rem; line-height: 1.55; }
.careers-ticks { display: grid; gap: 0.55rem; }
.careers-ticks li { display: grid; grid-template-columns: 1rem minmax(0, 1fr); gap: 0.6rem; font-size: 0.95rem; line-height: 1.45; }
.careers-ticks .icon { color: var(--accent-secondary); font-size: 0.8rem; margin-top: 0.3rem; }
.careers-choice .btn { justify-self: start; margin-top: 0.4rem; }

.careers-or {
  position: absolute;
  z-index: 2;
  left: 50%;
  top: clamp(3.4rem, 6.5vw, 5.6rem);
  translate: -50% -50%;
  width: 56px;
  height: 56px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--bg-base);
  border: 1px solid var(--border-strong);
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: 1rem;
  color: var(--text-primary);
  box-shadow: var(--shadow-glow-violet);
}

/* ---------- Narrow screens ---------- */
@media (max-width: 960px) {
  .careers-choices { grid-template-columns: minmax(0, 1fr); gap: 0; }
  .careers-or { position: relative; left: auto; top: auto; translate: none; justify-self: center; margin-block: -1.1rem; }
}


/* ===== assets/css/components/careers-sections.css ===== */
/* =========================================================
   Careers: "Your name, your pace." (four perk cells), "How hiring
   works." (the path) and the apply section around the shared postcard.
   Markup: includes/partials/careers-{perks,path,apply}.php. The postcard
   itself is contact.css.
   ========================================================= */

/* ---------- Perks: exactly four cells ---------- */
.careers-bento {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  grid-template-areas: "name name load" "paid remote load";
  gap: 1rem;
}
.careers-cell {
  position: relative;
  display: grid;
  align-content: start;
  gap: 0.75rem;
  padding: clamp(1.25rem, 2.4vw, 1.9rem);
  border-radius: var(--radius-lg);
  border: 1px solid var(--border-subtle);
  background: var(--bg-surface);
  overflow: hidden;
  min-width: 0;
}
.careers-cell h3 { font-size: clamp(1.3rem, 0.8vw + 1rem, 1.6rem); font-weight: 700; letter-spacing: -0.03em; }
.careers-cell > p,
.careers-cell-copy > p { color: var(--text-muted); line-height: 1.65; max-width: 34ch; }
.careers-cell--name {
  grid-area: name;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas: "copy card" "fields card";
  align-items: center;
  gap: 1.5rem 2rem;
  background: radial-gradient(80% 90% at 100% 0%, color-mix(in oklch, var(--accent-primary) 34%, transparent), transparent 65%), var(--bg-surface);
}
/* The fields stay hidden until their script runs. The cell is one row
   then, so the copy sits level with the card's middle. */
.careers-cell--name:not(:has(.careers-fields:not([hidden]))) { grid-template-areas: "copy card"; }
.careers-cell--load { grid-area: load; background: radial-gradient(90% 60% at 50% 100%, color-mix(in oklch, var(--accent-secondary) 10%, transparent), transparent 70%), var(--bg-surface); }
.careers-cell--paid { grid-area: paid; }
.careers-cell--remote { grid-area: remote; isolation: isolate; }
.careers-cell--remote::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background-image:
    linear-gradient(to right, color-mix(in oklch, var(--accent-secondary) 12%, transparent) 1px, transparent 1px),
    linear-gradient(to bottom, color-mix(in oklch, var(--accent-secondary) 12%, transparent) 1px, transparent 1px);
  background-size: 28px 28px;
  -webkit-mask-image: radial-gradient(90% 80% at 100% 100%, black, transparent 75%);
  mask-image: radial-gradient(90% 80% at 100% 100%, black, transparent 75%);
}
.careers-big { font-family: var(--font-heading); font-size: clamp(2.4rem, 3vw + 1rem, 3.4rem); font-weight: 700; letter-spacing: -0.05em; line-height: 1; margin-top: auto; }

/* Name cell: type a name and paste a picture link, see your card. */
.careers-cell-copy { grid-area: copy; display: grid; gap: 0.75rem; align-content: start; }
/* The two fields share a row where there is room for both and stack where
   there is not; a label that wraps keeps the two inputs level. */
.careers-fields { grid-area: fields; display: grid; grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr)); align-items: end; gap: 0.7rem 0.9rem; }
.careers-field { display: grid; gap: 0.4rem; }
.careers-field label { font-size: 0.8rem; font-weight: 500; color: var(--text-muted); }
/* Two lines are kept for the note, so the cell holds its height when the
   note changes. */
.careers-field-note { grid-column: 1 / -1; min-height: 3em; font-size: 0.75rem; line-height: 1.5; color: var(--text-muted); }
.careers-field-note.is-retry { color: var(--accent-gold); }
.careers-input {
  width: 100%;
  min-height: 44px;
  padding: 0.6rem 0.85rem;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-strong);
  background: var(--bg-base);
  color: var(--text-primary);
  font: inherit;
  font-size: 0.95rem;
}
.careers-input::placeholder { color: var(--text-faint); }
.careers-input:focus-visible { outline: 2px solid var(--accent-secondary); outline-offset: 1px; border-color: transparent; }
.careers-input[aria-invalid="true"] { border-color: color-mix(in oklch, var(--accent-gold) 65%, transparent); }

.careers-namecard {
  grid-area: card;
  width: 11.5rem;
  aspect-ratio: 4 / 5;
  position: relative;
  border-radius: var(--radius-md);
  overflow: hidden;
  box-shadow: var(--shadow-glow-violet);
  rotate: 3deg;
  transition: rotate 0.6s var(--careers-spring);
}
.careers-cell--name:hover .careers-namecard { rotate: 0deg; }
/* The same initials tile as the artist cards (artist-deck.css
   .artist-portrait-fallback), tinted by a hue taken from the name. */
.careers-namecard-art {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  background: radial-gradient(120% 120% at 50% 18%, oklch(0.62 0.14 var(--hue, 292) / 55%) 0%, var(--bg-surface-alt) 72%);
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: 2.6rem;
  letter-spacing: -0.02em;
}
.careers-namecard-scrim { position: absolute; inset: auto 0 0 0; height: 58%; background: linear-gradient(to top, color-mix(in oklch, var(--bg-base) 95%, transparent) 0%, transparent 100%); }
.careers-namecard-info { position: absolute; inset: auto 0.7rem 0.7rem 0.7rem; display: grid; gap: 0.35rem; }
.careers-namecard-name { font-family: var(--font-heading); font-weight: 700; font-size: 1.02rem; line-height: 1.2; overflow-wrap: anywhere; }
.careers-namecard-role { font-size: 0.72rem; color: var(--text-muted); }

/* A picture from a pasted link fills the card, the way a profile picture
   fills an artist's (artist-deck.css .artist-portrait img). The tile's
   glow stays behind a see-through picture; its letter steps aside. */
.careers-namecard-photo { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; animation: careers-photo-in 0.5s var(--careers-ease) both; }
@keyframes careers-photo-in { from { opacity: 0; scale: 1.06; } }
.careers-namecard.has-photo .careers-namecard-art { color: transparent; }
/* Any picture can arrive, a white one included. So the name stops relying
   on the scrim and gets a plate of its own, as tall as the name turns out
   to be, with a soft edge above it. */
.careers-namecard.has-photo .careers-namecard-scrim { display: none; }
.careers-namecard.has-photo .careers-namecard-info { inset: auto 0 0 0; padding: 0.55rem 0.7rem 0.7rem; background: color-mix(in oklch, var(--bg-base) 96%, transparent); }
.careers-namecard.has-photo .careers-namecard-info::before { content: ''; position: absolute; inset: auto 0 calc(100% - 1px) 0; height: 3.25rem; background: linear-gradient(to top, color-mix(in oklch, var(--bg-base) 96%, transparent), transparent); pointer-events: none; }

/* The status badge (artist-deck-leaf.css .artist-badge). */
.careers-badge {
  justify-self: start;
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  padding: 0.22rem 0.65rem;
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-pill);
  background: color-mix(in oklch, var(--bg-surface) 94%, transparent);
  font-size: 0.7rem;
  font-weight: 600;
  white-space: nowrap;
}
.careers-badge-dot { position: relative; width: 6px; height: 6px; border-radius: 50%; background: var(--text-faint); isolation: isolate; }
.careers-badge--available .careers-badge-dot { background: var(--accent-secondary); }
.careers-badge--at_capacity .careers-badge-dot { background: var(--accent-gold-alt); }
.careers-badge-dot .careers-ping { display: none; }
.careers-badge--available .careers-badge-dot .careers-ping { display: block; }

/* Load cell: slot stepper and on/off switch. */
.careers-load { display: grid; gap: 1.1rem; margin-top: 0.75rem; padding-top: 1.1rem; border-top: 1px solid var(--border-subtle); }
.careers-load-row { display: flex; align-items: center; justify-content: space-between; gap: 1rem; }
.careers-load-label { font-size: 0.85rem; color: var(--text-muted); }
.careers-stepper { display: inline-flex; align-items: center; gap: 0.35rem; }
.careers-stepper output { min-width: 2ch; text-align: center; font-family: var(--font-mono); font-weight: 600; font-size: 1.15rem; font-variant-numeric: tabular-nums; }
/* The plus and minus are drawn here, not taken from icons.svg. */
.careers-step {
  position: relative;
  width: 36px;
  height: 36px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-strong);
  background: var(--bg-base);
  color: var(--text-primary);
  transition: border-color 0.2s ease, color 0.2s ease;
}
.careers-step:hover:not(:disabled) { border-color: color-mix(in oklch, var(--accent-secondary) 60%, transparent); color: var(--accent-secondary); }
.careers-step:disabled { opacity: 0.4; cursor: not-allowed; }
.careers-step::before,
.careers-step[data-step="1"]::after { content: ''; position: absolute; left: 50%; top: 50%; width: 12px; height: 2px; border-radius: 1px; background: currentColor; translate: -50% -50%; }
.careers-step[data-step="1"]::after { width: 2px; height: 12px; }
.careers-slots { display: flex; flex-wrap: wrap; gap: 0.4rem; min-height: 22px; }
.careers-slot { width: 22px; height: 22px; border-radius: 6px; border: 1px dashed var(--border-strong); transition: background-color 0.3s ease, border-color 0.3s ease, scale 0.4s var(--careers-spring); }
.careers-slot.is-busy { background: var(--accent-primary); border: 1px solid color-mix(in oklch, var(--accent-secondary) 30%, transparent); }
.careers-slot.is-new { scale: 0.4; }
.careers-slot-key { font-size: 0.75rem; color: var(--text-faint); }
.careers-switch {
  position: relative;
  width: 48px;
  height: 28px;
  flex-shrink: 0;
  border-radius: var(--radius-pill);
  border: 1px solid var(--border-strong);
  background: var(--bg-base);
  transition: background-color 0.25s ease, border-color 0.25s ease;
}
.careers-switch::after { content: ''; position: absolute; top: 3px; left: 3px; width: 20px; height: 20px; border-radius: 50%; background: var(--text-muted); transition: translate 0.35s var(--careers-spring), background-color 0.25s ease; }
.careers-switch[aria-checked="true"] { background: color-mix(in oklch, var(--accent-secondary) 18%, transparent); border-color: color-mix(in oklch, var(--accent-secondary) 60%, transparent); }
.careers-switch[aria-checked="true"]::after { translate: 20px 0; background: var(--accent-secondary); }
.careers-switch:focus-visible,
.careers-step:focus-visible { outline: 2px solid var(--accent-secondary); outline-offset: 2px; }
.careers-load-result { display: grid; gap: 0.4rem; padding-top: 0.2rem; }

/* Paid cell: a commission's road from paid to paid out. */
.careers-road { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); margin-top: 0.75rem; position: relative; }
.careers-road::before { content: ''; position: absolute; top: 7px; left: 12.5%; right: 12.5%; height: 2px; background: var(--border-strong); }
.careers-road::after {
  content: '';
  position: absolute;
  top: 7px;
  left: 12.5%;
  right: 12.5%;
  height: 2px;
  background: var(--gradient-primary);
  transform-origin: left;
  transform: scaleX(var(--road, 1));
  transition: transform 1.6s var(--careers-ease) 0.2s;
}
.careers.is-armed .careers-cell--paid:not(.is-in) .careers-road { --road: 0; }
.careers-road li { position: relative; z-index: 1; display: grid; justify-items: center; gap: 0.45rem; text-align: center; font-size: 0.72rem; line-height: 1.3; color: var(--text-muted); }
.careers-road li::before { content: ''; width: 16px; height: 16px; border-radius: 50%; background: var(--bg-surface); border: 2px solid var(--accent-secondary); }
.careers-road li:last-child { color: var(--accent-gold); font-weight: 600; }
.careers-road li:last-child::before { background: var(--accent-gold); border-color: var(--accent-gold); }

/* ---------- How hiring works: the path ---------- */
.careers-path { --path-gap: 1.5rem; position: relative; display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--path-gap); }
/* The line runs from the first node's centre to the last one's. Each node
   (56px) sits at the start of its column, and the last column is
   (100% - 3 gaps) / 4 wide, so the line stops that width less half a node
   from the right edge, at any screen width. */
.careers-path::before,
.careers-path::after { content: ''; position: absolute; top: 27px; left: 28px; right: calc((100% - 3 * var(--path-gap)) / 4 - 28px); height: 2px; }
.careers-path::before { background: var(--border-subtle); }
.careers-path::after { background: var(--gradient-primary); transform-origin: left; transition: transform 1.8s var(--careers-ease) 0.15s; }
.careers.is-armed .careers-path:not(.is-in)::after { transform: scaleX(0); }
.careers-step-item { position: relative; display: grid; gap: 0.6rem; align-content: start; }
.careers-node {
  position: relative;
  z-index: 1;
  width: 56px;
  height: 56px;
  display: grid;
  place-items: center;
  margin-bottom: 0.6rem;
  border-radius: 50%;
  background: var(--bg-surface);
  border: 1px solid var(--border-strong);
  color: var(--accent-secondary);
  font-size: 1.15rem;
  box-shadow: var(--shadow-glow-violet);
}
.careers-step-item h3 { font-size: 1.2rem; font-weight: 600; letter-spacing: -0.02em; }
.careers-step-item p { color: var(--text-muted); font-size: 0.95rem; line-height: 1.6; max-width: 26ch; }

/* ---------- Apply: the section around the postcard ---------- */
/* Isolated, so the postmark's mix-blend-mode blends within this section. */
.careers-apply { isolation: isolate; }
.careers-apply::before { content: ''; position: absolute; inset: 0; background: var(--gradient-hero); transform: rotate(180deg); z-index: -1; pointer-events: none; }

/* A loose UV island floating over the section edge. */
.careers-sticker {
  position: absolute;
  z-index: 1;
  width: 74px;
  height: 74px;
  left: max(1rem, calc(50% - 37rem));
  top: -2.2rem;
  rotate: -12deg;
  pointer-events: none;
  filter: drop-shadow(0 16px 24px color-mix(in oklch, var(--accent-primary) 40%, transparent));
  animation: careers-drift 9s ease-in-out -4s infinite;
}
.careers-sticker canvas { display: block; width: 100%; height: 100%; }
@keyframes careers-drift { 0%, 100% { translate: 0 0; } 50% { translate: 5px -10px; } }

/* ---------- Narrow screens ---------- */
@media (max-width: 960px) {
  .careers-bento { grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-areas: "name name" "load load" "paid remote"; }
  .careers-path { grid-template-columns: repeat(2, minmax(0, 1fr)); row-gap: 2.5rem; }
  .careers-path::before,
  .careers-path::after { display: none; }
}
@media (max-width: 640px) {
  .careers-bento { grid-template-columns: minmax(0, 1fr); grid-template-areas: "name" "load" "paid" "remote"; }
  /* The card sits above the fields here, so it stays in view while typing. */
  .careers-cell--name { grid-template-columns: minmax(0, 1fr); grid-template-areas: "copy" "card" "fields"; }
  .careers-cell--name:not(:has(.careers-fields:not([hidden]))) { grid-template-areas: "copy" "card"; }
  .careers-namecard { justify-self: center; }
  .careers-path { grid-template-columns: minmax(0, 1fr); row-gap: 0; }
  .careers-step-item { grid-template-columns: 56px minmax(0, 1fr); column-gap: 1rem; padding-bottom: 1.75rem; }
  .careers-step-item::before { content: ''; position: absolute; left: 27px; top: 56px; bottom: 0; width: 2px; background: var(--border-subtle); }
  .careers-step-item:last-child::before { display: none; }
  .careers-node { grid-row: span 2; margin: 0; }
  .careers-sticker { display: none; }
}

