/* Generated by tools/build_css.py. Do not edit: edit the sources it
 * concatenates (static/css/tokens.css, static/css/base.css and the
 * sheets in static/css/regions/) and run the script again.
 */

/* ==== static/css/tokens.css ==== */
/* Artignitor design tokens.
 *
 * The single place a colour, spacing, radius or motion literal appears
 * (docs/PLAN.md 3.1 and 3.2). Every other stylesheet references these.
 *
 * Palette contrast is gated by tools/check_contrast.py, which parses this
 * file. Changing a hue here without re-running that gate is how the floor
 * gets lost.
 */

:root {
  /* --- Ground ------------------------------------------------------- */

  --canvas: #0B0B0C;
  --anchor: #0E1636;

  /* Colour a glass panel effectively presents to the text and accents on
   * top of it: --panel-fill composited over --canvas. Slightly lighter
   * than the arithmetic result (#181819), so every light-on-dark pairing
   * is measured against a fractionally harder ground than it will meet.
   * Read by tools/check_contrast.py as the second test ground. */
  --glass-composite: #1A1A1D;

  /* --- Accents ------------------------------------------------------ */
  /* One hue per region, held everywhere that region appears. Each carries
   * --text-on-accent when used as a fill. */

  --accent-ember: #FF8A1F;
  --accent-aqua: #35D6F5;
  --accent-mint: #3BE08A;
  --accent-magenta: #FF6FD0;
  --accent-sun: #FFD426;
  --accent-violet: #A981FF;

  /* --- Region identity ---------------------------------------------- */

  --region-discover: var(--accent-aqua);
  --region-project: var(--accent-violet);
  --region-campaign: var(--accent-ember);
  --region-profile: var(--accent-sun);
  --region-messages: var(--accent-magenta);
  --region-complete: var(--accent-mint);

  /* Ember doubles as the global primary action colour. */
  --accent-primary: var(--accent-ember);

  /* --- Text --------------------------------------------------------- */

  --text-primary: #F2F2F4;
  --text-secondary: #C9C9CF;
  --text-on-accent: #0B0B0C;

  /* --- Type --------------------------------------------------------- */

  --font-ui: Tahoma, Verdana, system-ui, -apple-system, "Segoe UI", sans-serif;

  /* Money columns, IDs, ledger views and countdown digits only. */
  --font-mono: ui-monospace, "Cascadia Mono", Consolas, monospace;

  /* --- Space -------------------------------------------------------- */
  /* Within a group: 1 or 2 units. Between panels: 4 or 6. Between page
   * regions: 12. Compress within a group, never between them. */

  --space-unit: 8px;
  --space-1: 8px;
  --space-2: 16px;
  --space-4: 32px;
  --space-6: 48px;
  --space-12: 96px;

  /* --- Form --------------------------------------------------------- */

  --radius-input: 8px;
  --radius-card: 12px;
  --radius-panel: 20px;
  --radius-pill: 999px;

  --border-width: 1px;
  --border-hairline: rgba(255, 255, 255, 0.14);

  --panel-fill: rgba(255, 255, 255, 0.055);
  --panel-border: rgba(255, 255, 255, 0.14);
  --panel-blur: 12px;
  --panel-lift: 0 8px 32px rgba(0, 0, 0, 0.55);

  /* --- Motion ------------------------------------------------------- */
  /* Collapsed wholesale under prefers-reduced-motion in base.css. */

  --motion-hover: 160ms;
  --motion-base: 260ms;
  --motion-entrance: 320ms;
  --motion-celebrate: 900ms;

  --ease-standard: cubic-bezier(0.2, 0.8, 0.3, 1);
  --ease-entrance: cubic-bezier(0.34, 1.56, 0.64, 1);
}

/* ==== static/css/base.css ==== */
/* Artignitor base stylesheet.
 *
 * The document shell, the glass recipe, the type scale and the two component
 * sets every screen is built from. Concatenated after tokens.css by
 * tools/build_css.py, so every colour, space, radius and motion value here is
 * a var() reference: no colour or spacing literal appears below.
 *
 * The values that are not colour, space, radius or motion (the type scale,
 * control heights, lift distances, stacking order) are declared once at the
 * top of this file and referenced the same way.
 */

/* ==================================================================== *
 *  Scale                                                               *
 * ==================================================================== */

:root {
  /* Type. Hierarchy is weight and size; colour carries region, not rank.
   * Display type is fluid so a hero fills a wide window and still fits a
   * narrow one without dropping below the body size anywhere. */
  --type-display: clamp(2.5rem, 6.5vw, 4.25rem);
  --type-h1: clamp(1.75rem, 3vw, 2.5rem);
  --type-h2: 1.5rem;
  --type-h3: 1.1875rem;
  --type-body: 1rem;
  --type-lede: 1.1875rem;
  --type-small: 0.875rem;

  --line-display: 0.94;
  --line-heading: 1.16;
  --line-body: 1.6;

  --tracking-display: -0.02em;
  --tracking-eyebrow: 0.14em;

  /* Form */
  --control-height: 44px;
  --measure-page: 1160px;
  --measure-text: 62ch;

  /* Depth. Hover lifts a control toward the cursor; a panel lifts further. */
  --lift-control: 2px;
  --lift-panel: 4px;
  --focus-ring-width: 2px;
  --focus-ring-offset: 3px;

  --layer-canvas: 0;
  --layer-content: 1;
  --layer-header: 20;
  --layer-notify: 40;

  /* Region identity. Every surface inherits an accent from its body class
   * (see Regions below) and components read it through --region, so the same
   * component carries the colour of wherever it is used. */
  --region: var(--accent-primary);

  /* The atmospheric wash behind the canvas. Held separate from --region
   * because aqua glow is the one pairing that reads weakly over the anchor
   * gradient (docs/PLAN.md 3.1), so the discover region changes its accent
   * without changing the wash behind it. */
  --glow: var(--accent-ember);

  /* Semantic state colours. The palette carries no red: ember is the alarm
   * hue, mint the success hue. Neither is ever the only channel; an error
   * always carries text, a success always carries a word. */
  --state-error: var(--accent-ember);
  --state-success: var(--accent-mint);
  --state-info: var(--accent-aqua);
  --state-warning: var(--accent-sun);
}

/* Regions. One hue per region, held everywhere that region appears. */
.region-home { --region: var(--accent-primary); --glow: var(--accent-ember); }
.region-discover { --region: var(--region-discover); --glow: var(--accent-violet); }
.region-project { --region: var(--region-project); --glow: var(--accent-violet); }
.region-campaign { --region: var(--region-campaign); --glow: var(--accent-ember); }
.region-profile { --region: var(--region-profile); --glow: var(--accent-sun); }
.region-messages { --region: var(--region-messages); --glow: var(--accent-magenta); }
.region-complete { --region: var(--region-complete); --glow: var(--accent-mint); }

/* ==================================================================== *
 *  Document                                                            *
 * ==================================================================== */

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

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

body {
  margin: 0;
  min-height: 100vh;
  background-color: var(--canvas);
  color: var(--text-primary);
  font-family: var(--font-ui);
  font-size: var(--type-body);
  line-height: var(--line-body);
}

/* The canvas itself: black running toward anchor, with one wide accent bloom
 * where a lesser design would put a grey header band. Fixed, so the field
 * stays put while panels scroll over it and the layering stays readable. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: var(--layer-canvas);
  pointer-events: none;
  background:
    radial-gradient(
      70% 48% at 50% 0%,
      color-mix(in srgb, var(--glow) 13%, transparent) 0%,
      transparent 72%
    ),
    linear-gradient(
      178deg,
      color-mix(in srgb, var(--anchor) 88%, transparent) 0%,
      color-mix(in srgb, var(--anchor) 26%, transparent) 42%,
      transparent 78%
    );
}

::selection {
  background: color-mix(in srgb, var(--region) 38%, transparent);
  color: var(--text-primary);
}

/* Traditional scrollbars: always visible, wide enough to grab. */
* {
  scrollbar-width: auto;
  scrollbar-color: color-mix(in srgb, var(--text-primary) 22%, transparent)
    transparent;
}

::-webkit-scrollbar {
  width: 14px;
  height: 14px;
}

::-webkit-scrollbar-track {
  background: color-mix(in srgb, var(--canvas) 70%, transparent);
}

::-webkit-scrollbar-thumb {
  background: color-mix(in srgb, var(--text-primary) 22%, transparent);
  background-clip: padding-box;
  border: 4px solid transparent;
  border-radius: var(--radius-pill);
  transition: background-color var(--motion-hover) var(--ease-standard);
}

::-webkit-scrollbar-thumb:hover {
  background: color-mix(in srgb, var(--region) 60%, transparent);
  background-clip: padding-box;
}

/* ==================================================================== *
 *  Typography                                                          *
 * ==================================================================== */

h1, h2, h3, h4 {
  margin: 0;
  line-height: var(--line-heading);
  font-weight: 700;
}

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

p {
  margin: 0;
  max-width: var(--measure-text);
}

.display {
  font-size: var(--type-display);
  line-height: var(--line-display);
  letter-spacing: var(--tracking-display);
  font-weight: 700;
  text-wrap: balance;
}

.lede {
  font-size: var(--type-lede);
  color: var(--text-secondary);
  max-width: var(--measure-text);
}

.eyebrow {
  font-size: var(--type-small);
  font-weight: 700;
  letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
  color: var(--region);
}

.caption {
  font-size: var(--type-small);
  color: var(--text-secondary);
}

/* Money columns, IDs, ledger values and countdown digits only. */
.mono {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}

a {
  color: var(--accent-aqua);
  text-decoration: underline;
  text-decoration-thickness: from-font;
  text-underline-offset: 0.18em;
  transition:
    color var(--motion-hover) var(--ease-standard),
    text-decoration-color var(--motion-hover) var(--ease-standard);
}

a:hover {
  color: color-mix(in srgb, var(--accent-aqua) 70%, var(--text-primary));
  text-decoration-thickness: 2px;
}

:focus-visible {
  outline: var(--focus-ring-width) solid var(--text-primary);
  outline-offset: var(--focus-ring-offset);
}

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

.shell {
  position: relative;
  z-index: var(--layer-content);
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}

.container {
  width: min(var(--measure-page), 100% - var(--space-4) - var(--space-4));
  margin-inline: auto;
}

.main {
  flex: 1 0 auto;
  padding-block: var(--space-6) var(--space-12);
}

/* Page regions are a vertical sequence with generous air between them.
 * Compress within a group; never between groups. */
.region + .region {
  margin-top: var(--space-12);
}

/* Children stretch to the stack's width, which is what a column of panels,
 * grids and tables needs: `align-items: flex-start` here instead would size
 * every one of them to its own content and leave the layout hugging the left
 * edge of the page with the canvas showing through where the content should
 * be. Elements that genuinely have to keep their own width (a pill, a
 * button) say so themselves, below, rather than the container saying it for
 * everything. */
.stack {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  align-items: stretch;
}

.stack--tight { gap: var(--space-1); }
.stack--loose { gap: var(--space-4); }

/* The hug list: a control whose width is its content, not its column. */
.stack > .btn,
.stack > .availability,
.stack > .status-pill,
.stack > .tag,
.stack > .role-chip {
  align-self: flex-start;
}

.row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}

.grid {
  display: grid;
  gap: var(--space-4);
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
}

.skip-link {
  position: absolute;
  left: var(--space-2);
  top: calc(var(--space-6) * -1);
  z-index: calc(var(--layer-notify) + 1);
  padding: var(--space-1) var(--space-2);
  background: var(--accent-primary);
  color: var(--text-on-accent);
  border-radius: var(--radius-input);
  font-weight: 700;
  text-decoration: none;
  transition: top var(--motion-base) var(--ease-standard);
}

.skip-link:focus {
  top: var(--space-2);
  color: var(--text-on-accent);
}

/* ==================================================================== *
 *  Glass                                                               *
 * ==================================================================== */

/* The panel recipe: translucent light fill, 1px hairline, 12px backdrop blur,
 * lift shadow, radius. Elevation stacks these rather than swapping them. */
.panel {
  position: relative;
  padding: var(--space-4);
  background: var(--panel-fill);
  border: var(--border-width) solid var(--panel-border);
  border-radius: var(--radius-panel);
  box-shadow: var(--panel-lift);
  backdrop-filter: blur(var(--panel-blur)) saturate(130%);
  -webkit-backdrop-filter: blur(var(--panel-blur)) saturate(130%);
}

.panel--card {
  padding: var(--space-2);
  border-radius: var(--radius-card);
}

/* Log in, join, password reset. One panel with nothing else on the screen,
 * so it is centred in the column and given the padding of a panel that is
 * the whole page rather than one card among several: a form pinned to the
 * top-left corner of a 1440px canvas reads as a fragment of a page that
 * failed to load. */
.auth-card {
  width: 100%;
  max-width: 30rem;
  margin-inline: auto;
  margin-block-start: var(--space-6);
  padding: var(--space-4);
  border-radius: var(--radius-panel);
}

.auth-card h1 {
  margin-bottom: var(--space-1);
}

/* One step up the stack: more fill, more blur, a heavier shadow. */
.panel--raised {
  background: color-mix(in srgb, var(--text-primary) 9%, transparent);
  backdrop-filter: blur(calc(var(--panel-blur) * 1.5)) saturate(140%);
  -webkit-backdrop-filter: blur(calc(var(--panel-blur) * 1.5)) saturate(140%);
  box-shadow:
    var(--panel-lift),
    0 2px 6px color-mix(in srgb, var(--canvas) 70%, transparent);
}

/* A panel that is itself a control: lifts toward the cursor and blooms its
 * region colour. The colour is not hover-only, since the accent hairline
 * across its top edge is present at rest. */
.panel--interactive {
  display: block;
  color: inherit;
  text-decoration: none;
  cursor: pointer;
  overflow: hidden;
  transition:
    transform var(--motion-hover) var(--ease-standard),
    border-color var(--motion-hover) var(--ease-standard),
    box-shadow var(--motion-hover) var(--ease-standard),
    background-color var(--motion-hover) var(--ease-standard);
}

.panel--interactive::before {
  content: "";
  position: absolute;
  left: var(--space-4);
  right: var(--space-4);
  top: 0;
  height: var(--border-width);
  background: var(--region);
  opacity: 0.6;
  transition:
    opacity var(--motion-hover) var(--ease-standard),
    box-shadow var(--motion-hover) var(--ease-standard);
}

.panel--interactive:hover {
  transform: translateY(calc(-1 * var(--lift-panel)));
  border-color: color-mix(in srgb, var(--region) 55%, var(--panel-border));
  background: color-mix(in srgb, var(--text-primary) 8%, transparent);
  box-shadow:
    var(--panel-lift),
    0 0 var(--space-6) color-mix(in srgb, var(--region) 22%, transparent);
}

.panel--interactive:hover::before {
  opacity: 1;
  box-shadow: 0 0 var(--space-2) var(--region);
}

.panel--interactive:active {
  transform: translateY(0);
}

.panel__title {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  font-size: var(--type-h3);
  font-weight: 700;
}

/* ==================================================================== *
 *  Header                                                              *
 * ==================================================================== */

/* Chrome stays weightless: glass and hairlines over the canvas, never a solid
 * bar. Pushed harder than a content panel, since this is the surface that
 * carries the identity. */
.site-header {
  position: sticky;
  top: 0;
  z-index: var(--layer-header);
  background: color-mix(in srgb, var(--canvas) 68%, transparent);
  border-bottom: var(--border-width) solid var(--panel-border);
  backdrop-filter: blur(calc(var(--panel-blur) * 1.5)) saturate(150%);
  -webkit-backdrop-filter: blur(calc(var(--panel-blur) * 1.5)) saturate(150%);
  box-shadow:
    inset 0 var(--border-width) 0
      color-mix(in srgb, var(--text-primary) 10%, transparent),
    0 var(--space-1) var(--space-4)
      color-mix(in srgb, var(--glow) 9%, transparent);
}

.site-header__inner {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  min-height: calc(var(--control-height) + var(--space-2));
  padding-block: var(--space-1);
}

.wordmark {
  display: inline-flex;
  align-items: baseline;
  font-size: var(--type-h3);
  font-weight: 700;
  letter-spacing: var(--tracking-display);
  color: var(--text-primary);
  text-decoration: none;
  white-space: nowrap;
}

.wordmark__spark {
  color: var(--accent-ember);
  text-shadow: 0 0 var(--space-2)
    color-mix(in srgb, var(--accent-ember) 55%, transparent);
}

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

/* Site state, carried in the chrome rather than announced in a banner. */
/* The account cluster: everything a signed-in person needs to reach their
 * own account, and the way back out of it. Sits opposite the wordmark with
 * the navigation between them, so the three groups read left to right as
 * identity, the site, you. Replaces the "Invite-only beta" pill that used
 * to sit here: the footer already says it, and a pill shaped like a button
 * that does nothing on click is a decorative element wearing an
 * interactive costume. */
.site-account {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  white-space: nowrap;
}

.site-account__me {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 2px var(--space-1) 2px 2px;
  border: var(--border-width) solid var(--panel-border);
  border-radius: var(--radius-pill);
  color: var(--text-primary);
  font-weight: 700;
  text-decoration: none;
  transition:
    border-color var(--motion-hover) var(--ease-standard),
    transform var(--motion-hover) var(--ease-standard),
    box-shadow var(--motion-hover) var(--ease-standard);
}

.site-account__me:hover {
  transform: translateY(calc(-1 * var(--lift-control)));
  border-color: color-mix(in srgb, var(--region-profile) 70%, transparent);
  box-shadow: 0 0 var(--space-4)
    color-mix(in srgb, var(--region-profile) 26%, transparent);
}

/* The monogram stands in for an avatar the header has no room to load. */
.site-account__mark {
  display: grid;
  place-items: center;
  width: calc(var(--space-4) - 2px);
  height: calc(var(--space-4) - 2px);
  border-radius: var(--radius-pill);
  background: color-mix(in srgb, var(--region-profile) 22%, transparent);
  color: var(--region-profile);
  font-size: var(--type-small);
  font-weight: 700;
}

.site-account__handle {
  font-size: var(--type-small);
}

/* Settings and Sign out: the same object, one of which happens to be a
 * button because signing out is a POST. Neither shouts. */
.site-account__link {
  padding: var(--space-1) 0;
  border: 0;
  background: none;
  color: var(--text-secondary);
  font-family: inherit;
  font-size: var(--type-small);
  font-weight: 700;
  text-decoration: none;
  cursor: pointer;
  transition: color var(--motion-hover) var(--ease-standard);
}

.site-account__link:hover {
  color: var(--text-primary);
}

.site-account__out {
  display: inline-flex;
}

.site-nav {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-1);
  /* Auto on both sides: the navigation centres between the wordmark and the
   * account cluster instead of being shoved against one of them. */
  margin-inline: auto;
}

.site-nav__link {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-pill);
  color: var(--text-secondary);
  font-weight: 700;
  text-decoration: none;
  transition:
    color var(--motion-hover) var(--ease-standard),
    background-color var(--motion-hover) var(--ease-standard),
    transform var(--motion-hover) var(--ease-standard);
}

/* Region identity at rest, not only under a cursor. */
.site-nav__link::before {
  content: "";
  width: var(--space-1);
  height: var(--space-1);
  border-radius: var(--radius-pill);
  background: var(--region);
  opacity: 0.75;
  transition:
    opacity var(--motion-hover) var(--ease-standard),
    box-shadow var(--motion-hover) var(--ease-standard);
}

.site-nav__link:hover {
  color: var(--text-primary);
  background: color-mix(in srgb, var(--region) 14%, transparent);
  transform: translateY(calc(-1 * var(--lift-control)));
}

.site-nav__link:hover::before,
.site-nav__link[aria-current="page"]::before {
  opacity: 1;
  box-shadow: 0 0 var(--space-2) var(--region);
}

.site-nav__link[aria-current="page"] {
  color: var(--text-primary);
  background: color-mix(in srgb, var(--region) 16%, transparent);
}

.site-nav__link:active {
  transform: translateY(0);
}

/* ==================================================================== *
 *  Footer                                                              *
 * ==================================================================== */

.site-footer {
  flex: 0 0 auto;
  border-top: var(--border-width) solid var(--panel-border);
  padding-block: var(--space-4);
  color: var(--text-secondary);
  font-size: var(--type-small);
}

.site-footer__inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-4);
}

.site-footer__links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-left: auto;
}

/* ==================================================================== *
 *  Notifications                                                       *
 * ==================================================================== */

.notifications {
  position: fixed;
  top: calc(var(--control-height) + var(--space-4));
  right: var(--space-4);
  z-index: var(--layer-notify);
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  width: min(380px, 100vw - var(--space-4) - var(--space-4));
  pointer-events: none;
}

.notice {
  --region: var(--state-info);
  pointer-events: auto;
  display: flex;
  align-items: baseline;
  gap: var(--space-1);
  padding: var(--space-2);
  background: color-mix(in srgb, var(--canvas) 82%, transparent);
  border: var(--border-width) solid
    color-mix(in srgb, var(--region) 45%, var(--panel-border));
  border-left-width: var(--space-1);
  border-left-color: var(--region);
  border-radius: var(--radius-card);
  box-shadow:
    var(--panel-lift),
    0 0 var(--space-6) color-mix(in srgb, var(--region) 18%, transparent);
  backdrop-filter: blur(var(--panel-blur)) saturate(140%);
  -webkit-backdrop-filter: blur(var(--panel-blur)) saturate(140%);
  animation: notice-enter var(--motion-entrance) var(--ease-entrance) both;
}

/* The level is carried by a word as well as by the hue. */
.notice__label {
  flex: 0 0 auto;
  color: var(--region);
  font-weight: 700;
}

.notice__body {
  color: var(--text-primary);
}

.notice--success { --region: var(--state-success); }
.notice--info { --region: var(--state-info); }
.notice--warning { --region: var(--state-warning); }
.notice--error { --region: var(--state-error); }

@keyframes notice-enter {
  from {
    opacity: 0;
    transform: translateY(calc(-1 * var(--space-2)));
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* ==================================================================== *
 *  Buttons                                                             *
 *  States: rest, hover, focus, active, disabled, loading, error.        *
 * ==================================================================== */

.btn {
  --btn-fill: color-mix(in srgb, var(--text-primary) 8%, transparent);
  --btn-edge: var(--panel-border);
  --btn-ink: var(--text-primary);
  --btn-glow: var(--region);

  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-1);
  min-height: var(--control-height);
  padding: var(--space-1) var(--space-4);
  background: var(--btn-fill);
  color: var(--btn-ink);
  border: var(--border-width) solid var(--btn-edge);
  border-radius: var(--radius-pill);
  font-family: var(--font-ui);
  font-size: var(--type-body);
  font-weight: 700;
  line-height: 1;
  text-decoration: none;
  cursor: pointer;
  transition:
    transform var(--motion-hover) var(--ease-standard),
    background-color var(--motion-hover) var(--ease-standard),
    border-color var(--motion-hover) var(--ease-standard),
    box-shadow var(--motion-hover) var(--ease-standard),
    color var(--motion-hover) var(--ease-standard);
}

/* rest ---------------------------------------------------------------- */

.btn--primary {
  --btn-fill: var(--accent-primary);
  --btn-edge: var(--accent-primary);
  --btn-ink: var(--text-on-accent);
  --btn-glow: var(--accent-primary);
  box-shadow: 0 var(--space-1) var(--space-4)
    color-mix(in srgb, var(--accent-primary) 26%, transparent);
}

/* The in-region action: carries the colour of wherever it sits. */
.btn--region {
  --btn-fill: var(--region);
  --btn-edge: var(--region);
  --btn-ink: var(--text-on-accent);
}

.btn--ghost {
  --btn-fill: transparent;
}

/* The panel holding an irreversible action, set apart from the routine ones
 * above it by an ember hairline and a wider gap rather than by a red slab:
 * the warning is that this panel is different, not that the page is on
 * fire. */
.account-danger {
  margin-top: var(--space-4);
  border-color: color-mix(in srgb, var(--state-error) 34%, var(--panel-border));
}

/* The irreversible one. Outlined rather than filled: a solid ember button
 * is this site's primary action, and account deletion must not wear the
 * same clothes as "post an opening". The edge and the label carry the
 * error hue so the difference is colour and weight rather than size. */
.btn--danger {
  --btn-fill: transparent;
  --btn-edge: var(--state-error);
  --btn-ink: var(--state-error);
  --btn-glow: var(--state-error);
  border-color: color-mix(in srgb, var(--state-error) 60%, transparent);
}

/* hover: lifts toward the cursor, and the glow comes with it ----------- */

.btn:hover {
  transform: translateY(calc(-1 * var(--lift-control)));
  border-color: color-mix(in srgb, var(--btn-glow) 70%, var(--btn-edge));
  box-shadow:
    0 var(--space-1) var(--space-4)
      color-mix(in srgb, var(--btn-glow) 34%, transparent),
    0 0 var(--space-6) color-mix(in srgb, var(--btn-glow) 22%, transparent);
}

.btn:not(.btn--primary):not(.btn--region):hover {
  background: color-mix(in srgb, var(--btn-glow) 16%, transparent);
}

.btn--primary:hover {
  --btn-fill: color-mix(in srgb, var(--accent-primary) 88%, var(--text-primary));
}

.btn--region:hover {
  --btn-fill: color-mix(in srgb, var(--region) 88%, var(--text-primary));
}

/* focus: keyboard-visible, never suppressed, legible over any fill ----- */

.btn:focus-visible {
  outline: var(--focus-ring-width) solid var(--text-primary);
  outline-offset: var(--focus-ring-offset);
  box-shadow: 0 0 var(--space-6)
    color-mix(in srgb, var(--btn-glow) 34%, transparent);
}

/* active: gives under the press ---------------------------------------- */

.btn:active {
  transform: translateY(0) scale(0.985);
  box-shadow: 0 0 0 color-mix(in srgb, var(--btn-glow) 20%, transparent);
}

/* disabled: reads as inert, and stops responding ------------------------ */

.btn:disabled,
.btn[aria-disabled="true"],
.btn.is-disabled {
  filter: saturate(0.2);
  opacity: 0.55;
  cursor: not-allowed;
  transform: none;
  box-shadow: none;
}

.btn:disabled:hover,
.btn[aria-disabled="true"]:hover,
.btn.is-disabled:hover {
  transform: none;
  background: var(--btn-fill);
  border-color: var(--btn-edge);
  box-shadow: none;
}

/* loading: visible process, and the label stays readable ---------------- */

.btn[aria-busy="true"],
.btn.is-loading {
  cursor: progress;
}

.btn[aria-busy="true"]::before,
.btn.is-loading::before {
  content: "";
  flex: 0 0 auto;
  width: var(--space-2);
  height: var(--space-2);
  border: var(--border-width) solid
    color-mix(in srgb, var(--btn-ink) 30%, transparent);
  border-top-color: var(--btn-ink);
  border-radius: var(--radius-pill);
  animation: btn-spin var(--motion-celebrate) linear infinite;
}

@keyframes btn-spin {
  to { transform: rotate(1turn); }
}

/* error: the action failed. The hue is never the only channel; the message
 * beside the button carries the words. --------------------------------- */

.btn.is-error {
  --btn-edge: var(--state-error);
  --btn-glow: var(--state-error);
  box-shadow: 0 0 var(--space-4)
    color-mix(in srgb, var(--state-error) 30%, transparent);
}

.btn--small {
  min-height: calc(var(--control-height) - var(--space-2));
  padding: var(--space-1) var(--space-2);
  font-size: var(--type-small);
}

/* ==================================================================== *
 *  Icons                                                               *
 *                                                                      *
 *  The hand-drawn set in static/icons/ (docs/PLAN.md 3.5), served as   *
 *  images so each drawing arrives whole: full colour, gradients and    *
 *  highlights, drawn once per size rather than scaled down. Sizes are  *
 *  literal here because 16 and 24 are what the drawings are, not a     *
 *  step on the spacing scale. tools/check_icons.py reads the url()     *
 *  values below as well as the {% static %} calls in the templates.    *
 * ==================================================================== */

.btn__icon {
  flex: 0 0 auto;
  transition: filter var(--motion-hover) var(--ease-standard);
}

/* The icon brightens with the button it sits in. A hover that lifted the
 * control and left the drawing flat reads as two objects, not one. */
.btn:hover .btn__icon,
.btn:focus-visible .btn__icon {
  filter: drop-shadow(
    0 0 var(--space-1) color-mix(in srgb, var(--btn-glow) 65%, transparent)
  );
}

/* The medium a project is in. Which medium is only known at render time and
 * {% static %} cannot build a path, so the file is bound by modifier here.
 * One drawing per top-level Medium (apps/taxonomy/fixtures/taxonomy.json);
 * the tree is editable in admin, so a medium with no drawing collapses to
 * its words rather than showing an empty box. */
.icon-medium {
  display: none;
}

.icon-medium--book,
.icon-medium--comic,
.icon-medium--film,
.icon-medium--music,
.icon-medium--game,
.icon-medium--other {
  display: inline-block;
  flex: 0 0 auto;
  width: 16px;
  height: 16px;
  background-repeat: no-repeat;
  background-position: center;
  background-size: 16px 16px;
}

.icon-medium--book {
  background-image: url("../icons/16/medium-book.svg");
}

.icon-medium--comic {
  background-image: url("../icons/16/medium-comic.svg");
}

.icon-medium--film {
  background-image: url("../icons/16/medium-film.svg");
}

.icon-medium--music {
  background-image: url("../icons/16/medium-music.svg");
}

.icon-medium--game {
  background-image: url("../icons/16/medium-game.svg");
}

.icon-medium--other {
  background-image: url("../icons/16/medium-other.svg");
}

/* The role a person states or a project is looking for, drawn the same way
 * and for the same reason as the mediums above: bound by modifier, because
 * the slug is only known at render time. Twenty-four of the fifty-four roles
 * in the taxonomy have a drawing; a role without one collapses to its words
 * rather than leaving a gap, which is what `.icon-role`'s `display: none`
 * does by default. The templates ask for one with
 * `<span class="icon-role icon-role--{{ role|slugify }}">`, so a role added
 * in admin after launch needs only a file and a rule here. */
.icon-role {
  display: none;
}

.icon-role--actor,
.icon-role--animator,
.icon-role--camera-operator,
.icon-role--cinematographer,
.icon-role--colourist,
.icon-role--composer,
.icon-role--costume-designer,
.icon-role--director,
.icon-role--editor,
.icon-role--game-designer,
.icon-role--illustrator,
.icon-role--inker,
.icon-role--letterer,
.icon-role--lighting-designer,
.icon-role--mixing-engineer,
.icon-role--penciller,
.icon-role--producer,
.icon-role--programmer,
.icon-role--screenwriter,
.icon-role--sculptor,
.icon-role--sound-designer,
.icon-role--translator,
.icon-role--vfx-artist,
.icon-role--vocalist,
.icon-role--writer {
  display: inline-block;
  flex: 0 0 auto;
  width: 16px;
  height: 16px;
  background-repeat: no-repeat;
  background-position: center;
  background-size: 16px 16px;
}

.icon-role--actor {
  background-image: url("../icons/16/role-actor.svg");
}

.icon-role--animator {
  background-image: url("../icons/16/role-animator.svg");
}

.icon-role--camera-operator {
  background-image: url("../icons/16/role-camera-operator.svg");
}

.icon-role--cinematographer {
  background-image: url("../icons/16/role-cinematographer.svg");
}

.icon-role--colourist {
  background-image: url("../icons/16/role-colourist.svg");
}

.icon-role--composer {
  background-image: url("../icons/16/role-composer.svg");
}

.icon-role--costume-designer {
  background-image: url("../icons/16/role-costume-designer.svg");
}

.icon-role--director {
  background-image: url("../icons/16/role-director.svg");
}

.icon-role--editor {
  background-image: url("../icons/16/role-editor.svg");
}

.icon-role--game-designer {
  background-image: url("../icons/16/role-game-designer.svg");
}

.icon-role--illustrator {
  background-image: url("../icons/16/role-illustrator.svg");
}

.icon-role--inker {
  background-image: url("../icons/16/role-inker.svg");
}

.icon-role--letterer {
  background-image: url("../icons/16/role-letterer.svg");
}

.icon-role--lighting-designer {
  background-image: url("../icons/16/role-lighting-designer.svg");
}

.icon-role--mixing-engineer {
  background-image: url("../icons/16/role-mixing-engineer.svg");
}

.icon-role--penciller {
  background-image: url("../icons/16/role-penciller.svg");
}

.icon-role--producer {
  background-image: url("../icons/16/role-producer.svg");
}

.icon-role--programmer {
  background-image: url("../icons/16/role-programmer.svg");
}

.icon-role--screenwriter {
  background-image: url("../icons/16/role-screenwriter.svg");
}

.icon-role--sculptor {
  background-image: url("../icons/16/role-sculptor.svg");
}

.icon-role--sound-designer {
  background-image: url("../icons/16/role-sound-designer.svg");
}

.icon-role--translator {
  background-image: url("../icons/16/role-translator.svg");
}

.icon-role--vfx-artist {
  background-image: url("../icons/16/role-vfx-artist.svg");
}

.icon-role--vocalist {
  background-image: url("../icons/16/role-vocalist.svg");
}

.icon-role--writer {
  background-image: url("../icons/16/role-writer.svg");
}

/* ==================================================================== *
 *  Fields                                                              *
 *  States: rest, hover, focus, active, disabled, loading, error.        *
 * ==================================================================== */

.field {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  width: 100%;
  max-width: var(--measure-text);
}

.field__label {
  font-weight: 700;
  font-size: var(--type-small);
  color: var(--text-primary);
}

.field__hint {
  font-size: var(--type-small);
  color: var(--text-secondary);
}

/* Hidden until the field is actually invalid, and shown with a glyph so the
 * error never depends on colour vision alone. */
.field__error {
  display: none;
  align-items: center;
  gap: var(--space-1);
  font-size: var(--type-small);
  font-weight: 700;
  color: var(--state-error);
}

.field__error::before {
  content: "!";
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--space-2);
  height: var(--space-2);
  border: var(--border-width) solid var(--state-error);
  border-radius: var(--radius-pill);
}

/* rest: recessed, with more of the canvas showing through than the panel
 * around it, so the field reads as cut into the glass. ------------------ */

.input,
.textarea,
.select {
  width: 100%;
  min-height: var(--control-height);
  padding: var(--space-1) var(--space-2);
  background: color-mix(in srgb, var(--canvas) 76%, transparent);
  color: var(--text-primary);
  border: var(--border-width) solid var(--panel-border);
  border-radius: var(--radius-input);
  box-shadow: inset 0 var(--border-width) var(--space-1)
    color-mix(in srgb, var(--canvas) 80%, transparent);
  font-family: var(--font-ui);
  font-size: var(--type-body);
  line-height: var(--line-body);
  transition:
    border-color var(--motion-hover) var(--ease-standard),
    box-shadow var(--motion-hover) var(--ease-standard),
    background-color var(--motion-hover) var(--ease-standard);
}

.textarea {
  min-height: calc(var(--control-height) * 3);
  resize: vertical;
}

.input::placeholder,
.textarea::placeholder {
  color: var(--text-secondary);
  opacity: 1;
}

/* hover ---------------------------------------------------------------- */

.input:hover,
.textarea:hover,
.select:hover {
  border-color: color-mix(in srgb, var(--text-primary) 32%, var(--panel-border));
  background: color-mix(in srgb, var(--canvas) 68%, transparent);
}

/* focus: region colour on the edge, a soft ring, and a keyboard outline - */

.input:focus,
.textarea:focus,
.select:focus {
  border-color: var(--region);
  background: color-mix(in srgb, var(--canvas) 60%, transparent);
  box-shadow:
    inset 0 var(--border-width) var(--space-1)
      color-mix(in srgb, var(--canvas) 80%, transparent),
    0 0 var(--space-4) color-mix(in srgb, var(--region) 24%, transparent);
}

.input:focus-visible,
.textarea:focus-visible,
.select:focus-visible {
  outline: var(--focus-ring-width) solid var(--text-primary);
  outline-offset: var(--focus-ring-offset);
}

/* active: the press registers before the caret arrives ------------------ */

.input:active,
.textarea:active,
.select:active {
  border-color: color-mix(in srgb, var(--region) 80%, var(--text-primary));
}

/* disabled -------------------------------------------------------------- */

.input:disabled,
.textarea:disabled,
.select:disabled {
  filter: saturate(0.2);
  opacity: 0.55;
  cursor: not-allowed;
  background: color-mix(in srgb, var(--canvas) 88%, transparent);
}

/* loading: the field is waiting on the server. A hairline travels under it
 * so the wait is visible rather than implied. --------------------------- */

.input[aria-busy="true"],
.textarea[aria-busy="true"],
.select[aria-busy="true"],
.field.is-loading .input,
.field.is-loading .textarea {
  cursor: progress;
  background-image: linear-gradient(
    90deg,
    transparent 0%,
    color-mix(in srgb, var(--region) 60%, transparent) 50%,
    transparent 100%
  );
  background-repeat: no-repeat;
  background-size: 40% var(--border-width);
  background-position: -40% 100%;
  animation: field-wait calc(var(--motion-celebrate) * 1.6) linear infinite;
}

@keyframes field-wait {
  to { background-position: 140% 100%; }
}

/* error ----------------------------------------------------------------- */

.input[aria-invalid="true"],
.textarea[aria-invalid="true"],
.select[aria-invalid="true"],
.field.is-error .input,
.field.is-error .textarea {
  border-color: var(--state-error);
  box-shadow:
    inset 0 var(--border-width) var(--space-1)
      color-mix(in srgb, var(--canvas) 80%, transparent),
    0 0 var(--space-4) color-mix(in srgb, var(--state-error) 26%, transparent);
}

.field.is-error .field__error,
.field:has([aria-invalid="true"]) .field__error {
  display: flex;
}

/* ==================================================================== *
 *  Reduced motion                                                      *
 *                                                                      *
 *  Every transition collapses to an instant state change, and nothing  *
 *  is left carried by motion alone: the hover lift is replaced by the  *
 *  same border and glow change, the spinner by a static ring, the      *
 *  travelling field hairline by a full-width one.                      *
 * ==================================================================== */

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    /* 1ms rather than 0 so anything sequencing on transitionend still
     * receives the event. */
    transition-duration: 1ms !important;
    transition-delay: 0ms !important;
    animation-duration: 1ms !important;
    animation-delay: 0ms !important;
    animation-iteration-count: 1 !important;
    scroll-behavior: auto !important;
  }

  .btn:hover,
  .btn:active,
  .site-nav__link:hover,
  .panel--interactive:hover,
  .panel--interactive:active {
    transform: none;
  }

  /* A static three-quarter ring still reads as busy. */
  .btn[aria-busy="true"]::before,
  .btn.is-loading::before {
    animation: none;
    border-top-color: var(--btn-ink);
    border-right-color: var(--btn-ink);
  }

  .input[aria-busy="true"],
  .textarea[aria-busy="true"],
  .select[aria-busy="true"],
  .field.is-loading .input,
  .field.is-loading .textarea {
    animation: none;
    background-size: 100% var(--border-width);
    background-position: 0 100%;
  }

  .notice {
    animation: none;
  }
}

/* ==== static/css/regions/discovery.css ==== */
/* Discovery: the two browse surfaces at /discover/ and /discover/crew/.
 *
 * Region accent is aqua for projects (`.region-discover` in base.css) and
 * violet for crew (`.region-crew`, bound below because base.css has no crew
 * region and this sheet is where the surface lands). Everything here reads
 * var(--region), so the same rail, the same cards and the same pager carry
 * whichever hue the surface owns and no rule below names a colour.
 *
 * Concatenated after base.css and before regions/home.css, openings.css and
 * profile.css (tools/build_css.py sorts the region sheets alphabetically).
 * Two consequences worth knowing: `.status-pill`, `.visually-hidden` and
 * `.checkbox` are defined in sheets that land *after* this one and are reused
 * here rather than redefined, and any modifier this sheet adds to them must
 * not collide with a name those sheets already use — which is why the
 * availability pills below are `--availability-open` rather than `--open`.
 *
 * The layout is two islands with black between them: a sticky filter rail and
 * a results panel, each floating on the canvas, with the grid gap wide enough
 * that the field reads as continuous behind them.
 */

/* Crew is violet (docs/PLAN.md 3.3: /discover/crew/ [violet]). The glow
 * stays violet on both surfaces: aqua glow over the anchor gradient is the
 * one weak pairing in this palette (docs/PLAN.md 3.1). */
.region-crew {
  --region: var(--region-project);
  --glow: var(--accent-violet);
}

/* ==================================================================== *
 *  Head and surface switch                                             *
 * ==================================================================== */

.discover-head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-4);
}

.discover-head__title {
  max-width: 16ch;
}

/* Two surfaces, one control. Each tab carries the colour of the surface it
 * leads to, so the switch itself says the crew page is the violet one before
 * you have been there. */
.surface-switch {
  display: inline-flex;
  gap: var(--space-1);
  padding: var(--space-1);
  background: var(--panel-fill);
  border: var(--border-width) solid var(--panel-border);
  border-radius: var(--radius-pill);
  backdrop-filter: blur(var(--panel-blur)) saturate(130%);
  -webkit-backdrop-filter: blur(var(--panel-blur)) saturate(130%);
}

.surface-switch__tab {
  --tab: var(--region);
  display: inline-flex;
  align-items: center;
  min-height: calc(var(--control-height) - var(--space-2));
  padding: var(--space-1) var(--space-4);
  border-radius: var(--radius-pill);
  color: var(--text-secondary);
  font-weight: 700;
  text-decoration: none;
  transition:
    color var(--motion-hover) var(--ease-standard),
    background-color var(--motion-hover) var(--ease-standard),
    transform var(--motion-hover) var(--ease-standard),
    box-shadow var(--motion-hover) var(--ease-standard);
}

.surface-switch__tab--crew { --tab: var(--accent-violet); }
.surface-switch__tab--projects { --tab: var(--accent-aqua); }

.surface-switch__tab:hover {
  color: var(--tab);
  background: color-mix(in srgb, var(--tab) 16%, transparent);
  transform: translateY(calc(-1 * var(--lift-control)));
  box-shadow: 0 0 var(--space-4) color-mix(in srgb, var(--tab) 22%, transparent);
}

.surface-switch__tab:active {
  transform: translateY(0);
}

.surface-switch__tab[aria-current="page"] {
  background: var(--region);
  color: var(--text-on-accent);
  box-shadow: 0 var(--space-1) var(--space-4)
    color-mix(in srgb, var(--region) 30%, transparent);
}

.surface-switch__tab[aria-current="page"]:hover {
  color: var(--text-on-accent);
  background: color-mix(in srgb, var(--region) 88%, var(--text-primary));
}

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

.discover-layout {
  display: grid;
  grid-template-columns: minmax(0, 300px) minmax(0, 1fr);
  gap: var(--space-6);
  align-items: start;
}

/* ==================================================================== *
 *  Filter rail                                                         *
 * ==================================================================== */

/* Sticks under the header so the filters stay reachable however far the
 * results run, and scrolls internally rather than growing past the window. */
.discover-rail {
  position: sticky;
  top: calc(var(--control-height) + var(--space-4));
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-2);
  max-height: calc(100vh - var(--control-height) - var(--space-6));
  overflow-y: auto;
}

.discover-rail__search .field__label {
  font-size: var(--type-small);
  letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
  color: var(--region);
}

/* Stays on screen while the facets scroll under it, on the same glass
 * recipe as the rest of the chrome rather than a solid bar. */
.discover-rail__actions {
  position: sticky;
  bottom: calc(-1 * var(--space-2));
  gap: var(--space-1);
  padding-block: var(--space-2);
  margin-top: auto;
  border-top: var(--border-width) solid var(--panel-border);
  background: color-mix(in srgb, var(--canvas) 80%, transparent);
  backdrop-filter: blur(var(--panel-blur)) saturate(140%);
  -webkit-backdrop-filter: blur(var(--panel-blur)) saturate(140%);
}

.rail-facet {
  margin: 0;
  padding: 0;
  border: 0;
  border-top: var(--border-width) solid
    color-mix(in srgb, var(--panel-border) 70%, transparent);
}

/* Region colour identifies the group at rest, not under a cursor. */
.rail-facet__legend {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  padding-block: var(--space-1);
  font-size: var(--type-small);
  font-weight: 700;
  letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
  color: var(--region);
}

.rail-facet__count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: var(--space-2);
  padding-inline: 5px;
  border-radius: var(--radius-pill);
  background: var(--region);
  color: var(--text-on-accent);
  font-size: var(--type-small);
  letter-spacing: normal;
}

.rail-facet__options {
  display: flex;
  flex-direction: column;
  padding-right: var(--space-1);
}

/* Long vocabularies (54 roles, 77 genres) scroll inside the group rather
 * than pushing everything below them off the rail. The cap applies only to
 * groups that actually are long: applied to every group it clipped the six
 * mediums halfway through their last row, which reads as a rendering fault
 * rather than as "there is more below". Nine is the first count where the
 * list is longer than the cap allows. */
.rail-facet__options:has(.facet-option:nth-child(9)) {
  max-height: calc(var(--space-12) * 2.2);
  overflow-y: auto;
}

.facet-option {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  padding: var(--space-1);
  border-radius: var(--radius-input);
  cursor: pointer;
  transition:
    background-color var(--motion-hover) var(--ease-standard),
    color var(--motion-hover) var(--ease-standard);
}

.facet-option:hover {
  background: color-mix(in srgb, var(--region) 14%, transparent);
}

.facet-option__label {
  font-size: var(--type-small);
  color: var(--text-secondary);
  line-height: var(--line-heading);
}

.facet-option:hover .facet-option__label {
  color: var(--text-primary);
}

/* A ticked value reads as ticked from across the rail, not only by its box. */
.facet-option:has(:checked) .facet-option__label {
  color: var(--text-primary);
  font-weight: 700;
}

.facet-option:has(:focus-visible) {
  background: color-mix(in srgb, var(--region) 18%, transparent);
}

/* ==================================================================== *
 *  Results                                                             *
 * ==================================================================== */

.discover-results {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.discover-count {
  margin: 0;
  color: var(--text-secondary);
  font-size: var(--type-small);
}

.discover-count__figure {
  font-size: var(--type-h3);
  color: var(--region);
}

/* Waiting on the server. The panel dims and a hairline travels across the top
 * of it, so the wait is visible process rather than a frozen list. */
.discover-results.htmx-request {
  opacity: 0.6;
  transition: opacity var(--motion-base) var(--ease-standard);
}

.discover-results.htmx-request .discover-count {
  background-image: linear-gradient(
    90deg,
    transparent 0%,
    color-mix(in srgb, var(--region) 70%, transparent) 50%,
    transparent 100%
  );
  background-repeat: no-repeat;
  background-size: 40% var(--border-width);
  background-position: -40% 100%;
  animation: discover-wait calc(var(--motion-celebrate) * 1.4) linear infinite;
}

@keyframes discover-wait {
  to { background-position: 140% 100%; }
}

.card-grid {
  display: grid;
  gap: var(--space-4);
  grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
}

/* ==================================================================== *
 *  Cards                                                               *
 * ==================================================================== */

.project-card,
.crew-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-4);
}

/* The title's link covers the card, so the whole panel is the target the
 * hover lift already promises. Nothing else inside either card is a link,
 * which is what makes this safe. */
.project-card__link::after,
.crew-card__link::after {
  content: "";
  position: absolute;
  inset: 0;
}

/* Keyboard parity with the pointer: the same lift and bloom on focus. */
.project-card:focus-within,
.crew-card:focus-within {
  transform: translateY(calc(-1 * var(--lift-panel)));
  border-color: color-mix(in srgb, var(--region) 55%, var(--panel-border));
  box-shadow:
    var(--panel-lift),
    0 0 var(--space-6) color-mix(in srgb, var(--region) 22%, transparent);
}

.project-card__head,
.crew-card__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-2);
}

.project-card__title,
.crew-card__name {
  font-size: var(--type-h3);
  margin: 0;
}

.project-card__link,
.crew-card__link {
  color: var(--text-primary);
  text-decoration: none;
  transition: color var(--motion-hover) var(--ease-standard);
}

.project-card:hover .project-card__link,
.crew-card:hover .crew-card__link,
.project-card__link:focus-visible,
.crew-card__link:focus-visible {
  color: var(--region);
}

.project-card__logline {
  margin: 0;
  color: var(--text-secondary);
}

.project-card__tags,
.project-card__roles-list,
.crew-card__roles {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
  margin: 0;
}

.tag {
  padding: 2px var(--space-1);
  border: var(--border-width) solid var(--panel-border);
  border-radius: var(--radius-pill);
  color: var(--text-secondary);
  font-size: var(--type-small);
  white-space: nowrap;
}

.tag--medium {
  display: inline-flex;
  align-items: center;
  /* Half a unit: the drawing and its name are one object. */
  gap: calc(var(--space-1) / 2);
  border-color: color-mix(in srgb, var(--region) 45%, transparent);
  color: var(--region);
  font-weight: 700;
}

/* The AI policy is a decision, so it gets a hue as well as its words: mint
 * for human-only, sun for assisted, violet for generative. The words are
 * always printed, so the hue is never the only channel. */
.tag--policy { --policy: var(--text-secondary); }
.tag--policy-human_only { --policy: var(--accent-mint); }
.tag--policy-assisted { --policy: var(--accent-sun); }
.tag--policy-generative_ok { --policy: var(--accent-violet); }

.tag--policy {
  border-color: color-mix(in srgb, var(--policy) 45%, transparent);
  color: var(--policy);
}

.project-card__roles {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.project-card__roles-label,
.crew-card__roles-empty {
  margin: 0;
  font-size: var(--type-small);
  font-weight: 700;
  letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
  color: var(--text-secondary);
}

.role-chip {
  --chip: var(--region);
  /* Inline flex, half a unit of gap: the drawing and the role name are one
   * object, exactly as in `.tag--medium` above. */
  display: inline-flex;
  align-items: center;
  gap: calc(var(--space-1) / 2);
  padding: 2px var(--space-1);
  border: var(--border-width) solid
    color-mix(in srgb, var(--chip) 40%, transparent);
  border-radius: var(--radius-pill);
  background: color-mix(in srgb, var(--chip) 14%, transparent);
  color: var(--text-primary);
  font-size: var(--type-small);
  font-weight: 700;
  white-space: nowrap;
}

/* Money is ember everywhere on this platform (docs/PLAN.md 3.1). */
.role-chip--pay {
  --chip: var(--accent-ember);
  color: var(--accent-ember);
}

.project-card__foot,
.crew-card__foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-1) var(--space-2);
  margin-top: auto;
  padding-top: var(--space-2);
  border-top: var(--border-width) solid
    color-mix(in srgb, var(--panel-border) 70%, transparent);
  font-size: var(--type-small);
  color: var(--text-secondary);
}

.project-card__owner {
  font-weight: 700;
  color: var(--text-primary);
}

/* Owner responsiveness. The band is a word first and a colour second, and
 * the arithmetic behind it is printed beside it rather than hidden in a
 * tooltip: a badge nobody can check is a badge nobody should believe. */
.responsiveness {
  --resp: var(--text-secondary);
  display: inline-flex;
  align-items: center;
  color: var(--resp);
  font-weight: 700;
}

/* The gap belongs to the dot rather than to the flex container: as a
 * container gap it also opened a space between the label and the comma that
 * starts the detail clause, printing "No reply record yet , 0 applications
 * so far". */
.responsiveness::before {
  content: "";
  margin-right: var(--space-1);
  width: 6px;
  height: 6px;
  border-radius: var(--radius-pill);
  background: var(--resp);
  box-shadow: 0 0 var(--space-1) var(--resp);
}

.responsiveness--replies { --resp: var(--state-success); }
.responsiveness--mixed { --resp: var(--state-warning); }
.responsiveness--quiet { --resp: var(--state-error); }

.responsiveness__detail {
  color: var(--text-secondary);
  font-weight: 400;
}

/* ==================================================================== *
 *  Crew card                                                           *
 * ==================================================================== */

.crew-card__avatar {
  flex: 0 0 auto;
  width: calc(var(--space-6) + var(--space-1));
  height: calc(var(--space-6) + var(--space-1));
  border-radius: var(--radius-pill);
  border: var(--border-width) solid var(--panel-border);
  object-fit: cover;
}

.crew-card__avatar--empty {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: color-mix(in srgb, var(--region) 20%, transparent);
  color: var(--text-primary);
  font-size: var(--type-h2);
  font-weight: 700;
}

.crew-card__identity {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin-right: auto;
}

.crew-card__handle {
  font-size: var(--type-small);
  color: var(--text-secondary);
}

.crew-card__bio {
  margin: 0;
  color: var(--text-secondary);
}

/* Availability, as its own modifier set: `.status-pill--open` and
 * `--closed` already mean an opening's state in regions/openings.css, and an
 * unavailable person is not an error. */
.status-pill--availability-open { --pill: var(--state-success); }
.status-pill--availability-limited { --pill: var(--state-warning); }
.status-pill--availability-closed { --pill: var(--text-secondary); }

/* Project lifecycle states. No overlap with the opening and application
 * states in regions/openings.css. */
.status-pill--recruiting { --pill: var(--state-info); }
.status-pill--crewed,
.status-pill--in_production { --pill: var(--accent-violet); }
.status-pill--funding { --pill: var(--accent-ember); }
.status-pill--funded,
.status-pill--shipped { --pill: var(--state-success); }
.status-pill--unfunded,
.status-pill--abandoned { --pill: var(--text-secondary); }

/* ==================================================================== *
 *  Pager                                                               *
 * ==================================================================== */

.pager {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
}

.pager__position {
  font-size: var(--type-small);
  color: var(--text-secondary);
}

/* ==================================================================== *
 *  Empty state                                                         *
 * ==================================================================== */

/* No data to protect here, so this is where the atmosphere goes: a wash of
 * region colour rising from the floor of the panel, one characterful line,
 * and one obvious action. Most of the beta will meet this screen. */
.discover-empty {
  position: relative;
  overflow: hidden;
  padding: var(--space-6);
  gap: var(--space-2);
}

.discover-empty::before {
  content: "";
  position: absolute;
  inset: 40% -20% -40% -20%;
  pointer-events: none;
  background: radial-gradient(
    50% 100% at 50% 100%,
    color-mix(in srgb, var(--region) 22%, transparent) 0%,
    transparent 70%
  );
}

.discover-empty > * {
  position: relative;
}

.discover-empty__title {
  font-size: var(--type-h1);
  max-width: 18ch;
}

/* ==================================================================== *
 *  Narrow viewports                                                    *
 * ==================================================================== */

@media (max-width: 900px) {
  .discover-layout {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-4);
  }

  /* Sticking a full-height rail to the top of a phone screen would take the
   * whole viewport; it scrolls with the page instead. */
  .discover-rail {
    position: static;
    max-height: none;
  }

  .rail-facet__options {
    max-height: calc(var(--space-12) * 1.6);
  }
}

/* ==================================================================== *
 *  Reduced motion                                                      *
 * ==================================================================== */

@media (prefers-reduced-motion: reduce) {
  .surface-switch__tab:hover,
  .project-card:focus-within,
  .crew-card:focus-within {
    transform: none;
  }

  /* The travelling hairline becomes a full-width static one: the panel is
   * still visibly waiting, without anything moving. */
  .discover-results.htmx-request .discover-count {
    animation: none;
    background-size: 100% var(--border-width);
    background-position: 0 100%;
  }
}

/* ==== static/css/regions/home.css ==== */
/* Landing surface.
 *
 * Region sheets are concatenated after base.css by tools/build_css.py, in
 * alphabetical order. They hold what belongs to one surface only; anything a
 * second screen needs moves down into base.css.
 */

.hero {
  padding-block: var(--space-6) 0;
}

/* A unit of air above the actions so they read as the answer to the lede
 * rather than as its last line. */
.hero-actions {
  margin-top: var(--space-2);
}

.steps {
  margin: 0;
  padding: 0;
  list-style: none;
}

.step {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-4);
}

/* The step number reads as a label rather than a value, so it stays in the
 * UI face and takes the step's region colour. */
.step__index {
  color: var(--region);
  font-size: var(--type-small);
  font-weight: 700;
  letter-spacing: var(--tracking-eyebrow);
}

.step .panel__title {
  margin-bottom: var(--space-1);
}

/* ==== static/css/regions/messaging.css ==== */
/* Messages and notifications: the inbox, one conversation, the notification
 * list and its email preferences.
 *
 * Region accent is magenta (`.region-messages` in base.css, --accent-magenta
 * #FF6FD0), read here only as var(--region) so this sheet never names a hue.
 * Concatenated after base.css by tools/build_css.py; it sorts between
 * regions/home.css and regions/openings.css.
 *
 * `.visually-hidden` is borrowed from regions/openings.css, which is where
 * that utility currently lives. It belongs in base.css once a third surface
 * wants it; nothing here redeclares it, so there is one copy in the built
 * sheet either way.
 *
 * Two colours do semantic work on these surfaces and they are deliberately
 * different from each other:
 *
 *   --region       (magenta)  unread. New, and yours to read.
 *   --state-warning (sun)     waiting on you. Somebody is on the other end.
 *
 * The second is the whole point of the app (`tasks/todo.md` 4.2: unanswered
 * messages are how a collaboration site dies), so it is the louder of the
 * two and it always carries a word as well as a colour. Neither is ever the
 * only channel.
 */

/* ==================================================================== *
 *  Shared                                                              *
 * ==================================================================== */

/* The inbox and the notification list share one head. Display type against
 * a lot of black is what makes a list page feel like a place rather than a
 * table with a caption. */
.messages-title {
  margin: 0;
}

/* No conversations, no notifications. The one surface on these pages with
 * room to be atmospheric, and the one every new user sees first. */
/* The wash is layered into the panel's own background rather than added as a
 * positioned pseudo-element, so it cannot end up behind the glass fill it is
 * meant to sit inside. Wide, soft, region-coloured, and nowhere near a
 * surface carrying data. */
.messages-empty {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--space-4);
  align-items: start;
  padding: var(--space-6);
  overflow: hidden;
  background:
    radial-gradient(
      58% 78% at 12% 8%,
      color-mix(in srgb, var(--region) 18%, transparent) 0%,
      transparent 70%
    ),
    var(--panel-fill);
}

.messages-empty__mark {
  margin: 0;
  color: var(--region);
  opacity: 0.55;
  line-height: 1;
  text-shadow: 0 0 var(--space-6) var(--region);
}

.messages-empty h2 {
  margin: 0;
}

.messages-empty p {
  margin: 0;
  max-width: var(--measure-text);
  color: var(--text-secondary);
}

@media (max-width: 640px) {
  .messages-empty {
    grid-template-columns: 1fr;
    padding: var(--space-4);
  }
}

/* ==================================================================== *
 *  The inbox                                                           *
 * ==================================================================== */

/* Islands, not rows: each conversation is its own panel with real black
 * between it and the next. A conversation is a relationship, and stacking
 * them into a hairline-separated table would read as a log. */
.thread-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin: 0;
  padding: 0;
  list-style: none;
}

.thread-card {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--space-1) var(--space-2);
  padding: var(--space-2) var(--space-4);
  align-items: baseline;
}

.thread-card__who {
  grid-column: 1;
  font-weight: 700;
  font-size: var(--type-h3);
  color: var(--text-primary);
}

.thread-card__marks {
  grid-column: 2;
  grid-row: 1 / span 2;
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  align-items: center;
  gap: var(--space-1);
}

.thread-card__subject {
  grid-column: 1;
  color: var(--text-primary);
}

.thread-card__context,
.thread-card__time {
  grid-column: 1;
  color: var(--text-secondary);
  font-size: var(--type-small);
}

.thread-card__context {
  color: var(--region);
  font-weight: 700;
}

/* A thread waiting on this reader carries a sun hairline down its leading
 * edge, present at rest rather than on hover: the whole point is that it is
 * visible without being looked for. */
.thread-card.is-awaiting {
  border-left: calc(var(--border-width) * 3) solid var(--state-warning);
}

.unread-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: calc(var(--space-4) - var(--space-1));
  padding: 0 var(--space-1);
  height: var(--space-4);
  border-radius: var(--radius-pill);
  background: var(--region);
  color: var(--text-on-accent);
  font-weight: 700;
  font-size: var(--type-small);
  box-shadow: 0 0 var(--space-2) color-mix(in srgb, var(--region) 55%, transparent);
}

.await-flag {
  display: inline-flex;
  align-items: center;
  padding: 0 var(--space-1);
  height: var(--space-4);
  border-radius: var(--radius-pill);
  border: var(--border-width) solid var(--state-warning);
  background: color-mix(in srgb, var(--state-warning) 16%, transparent);
  color: var(--state-warning);
  font-size: var(--type-small);
  font-weight: 700;
  white-space: nowrap;
}

@media (max-width: 640px) {
  .thread-card {
    grid-template-columns: minmax(0, 1fr);
  }

  .thread-card__marks {
    grid-column: 1;
    grid-row: auto;
    justify-content: flex-start;
  }
}

/* ==================================================================== *
 *  One conversation                                                    *
 * ==================================================================== */

.thread-title {
  margin: 0;
  font-size: var(--type-h1);
  line-height: var(--line-heading);
}

.thread-who {
  gap: var(--space-1);
}

.thread-who__person {
  font-weight: 700;
}

.thread-context {
  margin: 0;
  color: var(--text-secondary);
  font-size: var(--type-small);
}

.thread-panel {
  padding: var(--space-4);
}

.thread-live {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.thread-log {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin: 0;
  padding: 0;
  list-style: none;
}

/* A message is a recessed surface inside the panel holding it: darker than
 * its host, tighter radius, no lift. The reader's own messages invert that
 * and take the region tint, so which side said what is legible before a
 * single word is read — and the sender is named in text as well, since
 * alignment and colour must never be the only channel. */
.message {
  max-width: 46rem;
  padding: var(--space-2);
  border: var(--border-width) solid var(--border-hairline);
  border-radius: var(--radius-card);
  background: color-mix(in srgb, var(--canvas) 55%, transparent);
}

.message--own {
  margin-left: auto;
  border-color: color-mix(in srgb, var(--region) 45%, var(--panel-border));
  background: color-mix(in srgb, var(--region) 10%, transparent);
}

.message__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-1) var(--space-2);
  margin: 0 0 var(--space-1);
}

.message__sender {
  font-weight: 700;
  font-size: var(--type-small);
  color: var(--text-primary);
}

.message__read {
  font-size: var(--type-small);
  font-weight: 700;
  color: var(--state-success);
}

.message__read--pending {
  color: var(--text-secondary);
  font-weight: 400;
}

.message__body {
  color: var(--text-primary);
}

.message__body p {
  margin: 0 0 var(--space-1);
}

.message__body p:last-child {
  margin-bottom: 0;
}

/* The composer sits below a hairline rather than inside its own panel: it is
 * the same conversation, one step further down. */
.composer {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding-top: var(--space-4);
  border-top: var(--border-width) solid var(--panel-border);
}

.composer .field {
  width: 100%;
}

.composer__actions {
  justify-content: space-between;
}

.composer__note {
  margin: 0;
}

/* htmx marks the form while a send is in flight. The composer dims rather
 * than disappearing, so the words being sent stay on screen; under reduced
 * motion the transition collapses and it simply changes state. */
.composer.htmx-request {
  opacity: 0.6;
  transition: opacity var(--motion-hover) var(--ease-standard);
}

/* ==================================================================== *
 *  Notifications                                                       *
 * ==================================================================== */

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

.notification-panel__head {
  justify-content: space-between;
}

.notification-panel__head h2 {
  margin: 0;
}

.notification-list {
  display: flex;
  flex-direction: column;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Dense inside the group, generous around it: the panel this list sits in
 * still floats in the same black as everything else. */
.notification {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto;
  align-items: baseline;
  gap: var(--space-2);
  padding: var(--space-1) 0;
  border-bottom: var(--border-width) solid
    color-mix(in srgb, var(--panel-border) 60%, transparent);
}

.notification:last-child {
  border-bottom: 0;
}

.notification__verb {
  color: var(--text-secondary);
}

/* Unread carries weight and a leading marker, not only colour. */
.notification.is-unread .notification__verb {
  color: var(--text-primary);
  font-weight: 700;
}

.notification.is-unread .notification__verb::before {
  content: "";
  display: inline-block;
  width: var(--space-1);
  height: var(--space-1);
  margin-right: var(--space-1);
  border-radius: var(--radius-pill);
  background: var(--region);
  box-shadow: 0 0 var(--space-1) var(--region);
  vertical-align: middle;
}

.notification__time {
  white-space: nowrap;
}

.notification__link {
  white-space: nowrap;
}

@media (max-width: 640px) {
  .notification {
    grid-template-columns: minmax(0, 1fr);
  }
}

.prefs-panel {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.prefs-panel__head h2 {
  margin: 0;
}

.prefs-panel__head p {
  margin: 0;
  max-width: var(--measure-text);
}

.prefs-field .select {
  max-width: 28rem;
}

/* ==== static/css/regions/openings.css ==== */
/* Openings and applications: the project's open positions block, the opening
 * page, the creator's applications inbox, and the external funding handoff
 * the same project page carries once a team has formed.
 *
 * Region accent is violet (`.region-project` in base.css, --accent-violet
 * #A981FF), read here only as var(--region) so this sheet never names a hue.
 * Concatenated after base.css by tools/build_css.py; it sorts before
 * regions/profile.css, which is where the shared form classes (.form-panel,
 * .form-row, .form-actions, .checkbox) live.
 *
 * The inbox is the one genuinely dense surface in the product so far, and it
 * is where the two halves of this design have to be held apart on purpose:
 * rows compress as far as the data wants, and the panel around them still
 * floats in the same generous black as everything else. Nothing between
 * groups is compressed to fit more rows on screen.
 *
 * Three utilities land here rather than in base.css because this is the first
 * surface to need them: `.visually-hidden`, `.fact-list` and `.status-pill`.
 * They move down into base.css when a second region wants them, the same way
 * `.checkbox` is waiting in regions/profile.css.
 */

/* ==================================================================== *
 *  Shared utilities                                                    *
 * ==================================================================== */

/* Present to a screen reader, absent from the picture. Used where an icon
 * carries the meaning visually and the control still needs a real name. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* A labelled column of facts: terms on an opening, an applicant's answers in
 * an expanded row. The label is small and the value is body-weight, so the
 * eye runs down the values and only reads the labels when it needs them. */
.fact-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  margin: 0;
}

.fact {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-1) var(--space-2);
  padding-block: var(--space-1);
  border-bottom: var(--border-width) solid
    color-mix(in srgb, var(--panel-border) 60%, transparent);
}

.fact:last-child {
  border-bottom: 0;
}

.fact dt {
  flex: 0 0 auto;
  min-width: 8ch;
  font-size: var(--type-small);
  font-weight: 700;
  color: var(--text-secondary);
}

.fact dd {
  flex: 1 1 auto;
  margin: 0;
  color: var(--text-primary);
}

/* A lifecycle state, as a hairline pill. The hue is never the only channel:
 * the pill always prints the state's own word, so the same information lands
 * for a reader who cannot separate mint from aqua. Tinted fill rather than a
 * solid one, because a table forty rows deep of solid accent blocks would
 * shout over the data it is annotating. */
.status-pill {
  --pill: var(--text-secondary);
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 2px var(--space-1);
  border: var(--border-width) solid
    color-mix(in srgb, var(--pill) 50%, transparent);
  border-radius: var(--radius-pill);
  background: color-mix(in srgb, var(--pill) 12%, transparent);
  color: var(--pill);
  font-size: var(--type-small);
  font-weight: 700;
  line-height: 1.4;
  white-space: nowrap;
}

.status-pill::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: var(--radius-pill);
  background: var(--pill);
  box-shadow: 0 0 var(--space-1) var(--pill);
}

/* Live and waiting on someone. */
.status-pill--open,
.status-pill--submitted { --pill: var(--state-info); }

/* Held, deliberately: the owner has read it and not decided. */
.status-pill--shortlisted { --pill: var(--state-warning); }

/* The two that mean the mechanic worked. */
.status-pill--filled,
.status-pill--accepted { --pill: var(--state-success); }

/* Ember is the alarm hue in this palette; there is no red. */
.status-pill--closed,
.status-pill--declined { --pill: var(--state-error); }

/* Nobody's fault and nothing to act on: no colour spent. */
.status-pill--withdrawn { --pill: var(--text-secondary); }

/* The external funding handoff's outcomes (docs/PLAN.md 4.2). A campaign
 * that did not fund is a fact about someone else's platform rather than an
 * alarm on this one, so it takes the warning hue and not the error one. */
.status-pill--running { --pill: var(--state-info); }
.status-pill--funded { --pill: var(--state-success); }
.status-pill--unfunded { --pill: var(--state-warning); }

/* ==================================================================== *
 *  Open positions, on the project page                                 *
 * ==================================================================== */

.openings-panel {
  /* The element the project page is arranged around (docs/PLAN.md 3.4), so
   * it carries the region's colour along its top edge rather than sitting as
   * one more identical panel in the column. */
  position: relative;
  overflow: hidden;
  border-color: color-mix(in srgb, var(--region) 32%, var(--panel-border));
  box-shadow:
    var(--panel-lift),
    0 0 var(--space-12) color-mix(in srgb, var(--region) 10%, transparent);
}

.openings-panel::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 2px;
  background: linear-gradient(
    90deg,
    var(--region) 0%,
    color-mix(in srgb, var(--region) 20%, transparent) 100%
  );
}

.openings-panel__head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  width: 100%;
}

.openings-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  width: 100%;
  margin: 0;
  padding: 0;
  list-style: none;
}

.opening-card {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: baseline;
  gap: var(--space-1) var(--space-2);
  padding: var(--space-2);
  border: var(--border-width) solid var(--panel-border);
  border-radius: var(--radius-card);
}

/* `.panel--interactive` insets its accent hairline by the padding of a full
 * panel; this card is padded one step tighter, so the hairline follows it. */
.opening-card::before {
  left: var(--space-2);
  right: var(--space-2);
}

.opening-card__role {
  grid-column: 1;
  font-size: var(--type-h3);
  font-weight: 700;
  color: var(--text-primary);
}

.opening-card__terms {
  grid-column: 1;
  grid-row: 2;
  color: var(--text-secondary);
  font-size: var(--type-small);
}

.opening-card__count {
  grid-column: 2;
  grid-row: 1 / span 2;
  align-self: center;
  white-space: nowrap;
}

/* ==================================================================== *
 *  Opening page                                                        *
 * ==================================================================== */

.opening-title {
  font-size: var(--type-h1);
}

.opening-status {
  gap: var(--space-2);
  margin-top: var(--space-1);
}

/* Brief and terms side by side on a wide window, stacked on a narrow one.
 * The terms panel is one step up the elevation stack: it is the part someone
 * is actually deciding on. */
.opening-layout {
  display: grid;
  grid-template-columns: minmax(0, 2fr) minmax(260px, 1fr);
  gap: var(--space-4);
  align-items: start;
}

.opening-brief > * + * {
  margin-top: var(--space-2);
}

.opening-terms {
  position: sticky;
  top: calc(var(--control-height) + var(--space-4));
}

.apply-panel {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  border-color: color-mix(in srgb, var(--region) 28%, var(--panel-border));
}

.apply-panel__head {
  gap: var(--space-1);
}

/* The settled variants (already applied, closed, signed out) say one thing
 * and offer one action, so they get the panel's air without its form width. */
.apply-panel--settled {
  gap: var(--space-2);
  align-items: flex-start;
}

/* A checkbox group is a list of controls, not one control, so it must not
 * inherit the single-field `.select` box (see `ApplicationForm.__init__`). */
.checkbox-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  margin: 0;
  padding: 0;
  list-style: none;
}

.checkbox-list label {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  padding: var(--space-1);
  border-radius: var(--radius-input);
  cursor: pointer;
  transition: background-color var(--motion-hover) var(--ease-standard);
}

.checkbox-list label:hover {
  background: color-mix(in srgb, var(--region) 12%, transparent);
}

.checkbox-list input {
  width: var(--space-2);
  height: var(--space-2);
  accent-color: var(--region);
  cursor: pointer;
}

/* ==================================================================== *
 *  Applications inbox                                                  *
 * ==================================================================== */

.inbox-head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-2) var(--space-4);
  width: 100%;
}

/* The panel keeps its full air on the outside and gives the table its edges
 * on the inside: a dense grid pushed hard against a 32px inset reads as a
 * screenshot pasted into a card. */
.inbox-panel {
  padding: var(--space-1);
  overflow-x: auto;
}

.inbox-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--type-small);
}

/* --- Header -------------------------------------------------------- */

.inbox-table thead th {
  position: sticky;
  top: 0;
  z-index: var(--layer-content);
  padding: var(--space-1) var(--space-2);
  text-align: left;
  white-space: nowrap;
  background: color-mix(in srgb, var(--canvas) 88%, transparent);
  border-bottom: var(--border-width) solid var(--panel-border);
  backdrop-filter: blur(var(--panel-blur));
  -webkit-backdrop-filter: blur(var(--panel-blur));
}

.inbox-th.is-sorted {
  border-bottom-color: var(--region);
}

.inbox-sort {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  color: var(--text-secondary);
  font-weight: 700;
  text-decoration: none;
  transition: color var(--motion-hover) var(--ease-standard);
}

.inbox-sort:hover {
  color: var(--text-primary);
}

.is-sorted .inbox-sort {
  color: var(--region);
}

/* The direction is a shape as well as a colour, so the sorted column is
 * readable without seeing the accent. */
.inbox-sort__arrow {
  width: 0;
  height: 0;
  border-left: 4px solid transparent;
  border-right: 4px solid transparent;
  opacity: 0;
  transition: opacity var(--motion-hover) var(--ease-standard);
}

.inbox-sort__arrow.is-first {
  border-top: 5px solid currentColor;
  opacity: 1;
}

.inbox-sort__arrow.is-second {
  border-bottom: 5px solid currentColor;
  opacity: 1;
}

.inbox-sort:hover .inbox-sort__arrow {
  opacity: 0.5;
}

/* A column with nothing behind it yet reads as inert rather than as a control
 * that is failing to respond. */
.inbox-th--inert {
  color: var(--text-secondary);
  font-weight: 400;
}

/* --- Rows ---------------------------------------------------------- */

.inbox-group {
  border-bottom: var(--border-width) solid
    color-mix(in srgb, var(--panel-border) 55%, transparent);
}

.inbox-cell {
  padding: var(--space-1) var(--space-2);
  vertical-align: middle;
  white-space: nowrap;
}

/* A row cannot lift toward the cursor without pulling the grid apart, so the
 * hover payload here is the region colour blooming across the row and a bar
 * appearing at its leading edge. It is the same idea as .panel--interactive,
 * expressed in a form a table can hold. */
.inbox-row {
  position: relative;
  transition: background-color var(--motion-hover) var(--ease-standard);
}

.inbox-row:hover {
  background: color-mix(in srgb, var(--region) 10%, transparent);
}

.inbox-cell--toggle {
  width: 1%;
  padding-left: var(--space-1);
  border-left: 3px solid transparent;
  transition: border-color var(--motion-hover) var(--ease-standard);
}

.inbox-row:hover .inbox-cell--toggle {
  border-left-color: color-mix(in srgb, var(--region) 60%, transparent);
}

.inbox-group.is-expanded .inbox-cell--toggle {
  border-left-color: var(--region);
}

.inbox-group.is-expanded .inbox-row {
  background: color-mix(in srgb, var(--region) 14%, transparent);
}

/* The flag is the owner's own mark, so it colours the whole row's edge: it is
 * meant to be findable while scanning, not only while reading. */
.inbox-group.is-flagged .inbox-cell--toggle {
  border-left-color: var(--state-warning);
}

.inbox-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: calc(var(--control-height) - var(--space-2));
  height: calc(var(--control-height) - var(--space-2));
  border-radius: var(--radius-input);
  color: var(--text-secondary);
  text-decoration: none;
  transition:
    background-color var(--motion-hover) var(--ease-standard),
    color var(--motion-hover) var(--ease-standard),
    transform var(--motion-hover) var(--ease-standard);
}

.inbox-toggle:hover {
  background: color-mix(in srgb, var(--region) 20%, transparent);
  color: var(--text-primary);
  transform: translateY(calc(-1 * var(--lift-control)));
}

.inbox-toggle__chevron {
  width: 0;
  height: 0;
  border-top: 5px solid transparent;
  border-bottom: 5px solid transparent;
  border-left: 7px solid currentColor;
  transition: transform var(--motion-base) var(--ease-standard);
}

.is-expanded .inbox-toggle {
  color: var(--region);
}

.is-expanded .inbox-toggle__chevron {
  transform: rotate(90deg);
}

/* Waiting on the server: the chevron dims rather than being replaced, so the
 * row does not change shape while the swap is in flight. */
.inbox-toggle.htmx-request {
  cursor: progress;
}

.inbox-toggle.htmx-request .inbox-toggle__chevron {
  opacity: 0.4;
}

.inbox-cell--applicant {
  width: 26ch;
  font-weight: 400;
  text-align: left;
  white-space: normal;
}

.inbox-applicant {
  display: block;
  color: var(--text-primary);
  font-weight: 700;
  text-decoration: none;
}

.inbox-applicant:hover {
  color: var(--region);
  text-decoration: underline;
}

.inbox-applicant__role {
  display: block;
  color: var(--text-secondary);
  font-size: var(--type-small);
}

/* Money and counts tabulate, so they align right and take the mono face. */
.inbox-cell--figure {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* A value the site does not have, drawn as an absence rather than a zero: a
 * blank rate means "open to discussion" and a blank rating means the
 * reputation system does not exist yet. Neither is nothing. */
.inbox-blank {
  color: var(--text-secondary);
  cursor: help;
}

.inbox-cell--flag,
.inbox-cell--when {
  text-align: center;
}

.inbox-cell--flag form {
  display: inline-flex;
}

.inbox-flag {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: calc(var(--control-height) - var(--space-2));
  height: calc(var(--control-height) - var(--space-2));
  padding: 0;
  background: transparent;
  border: 0;
  border-radius: var(--radius-input);
  cursor: pointer;
  transition:
    background-color var(--motion-hover) var(--ease-standard),
    transform var(--motion-hover) var(--ease-standard);
}

/* Hollow at rest, filled and glowing when set: the state is a shape, not only
 * a colour. */
.inbox-flag__mark {
  width: 0;
  height: 0;
  border-left: 6px solid transparent;
  border-right: 6px solid transparent;
  border-bottom: 10px solid
    color-mix(in srgb, var(--text-secondary) 55%, transparent);
  transition:
    border-bottom-color var(--motion-hover) var(--ease-standard),
    filter var(--motion-hover) var(--ease-standard);
}

.inbox-flag:hover {
  background: color-mix(in srgb, var(--state-warning) 18%, transparent);
  transform: translateY(calc(-1 * var(--lift-control)));
}

.inbox-flag:hover .inbox-flag__mark {
  border-bottom-color: var(--state-warning);
}

.inbox-flag[aria-pressed="true"] .inbox-flag__mark {
  border-bottom-color: var(--state-warning);
  filter: drop-shadow(0 0 var(--space-1) var(--state-warning));
}

.inbox-cell--when {
  color: var(--text-secondary);
}

/* --- The expanded row ---------------------------------------------- */

.inbox-detail {
  padding: 0 var(--space-2) var(--space-2)
    calc(var(--space-2) + 3px);
  background: color-mix(in srgb, var(--region) 7%, transparent);
}

.application-detail {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(240px, 320px);
  gap: var(--space-4);
  align-items: start;
  padding: var(--space-2);
  border: var(--border-width) solid
    color-mix(in srgb, var(--region) 30%, var(--panel-border));
  border-radius: var(--radius-card);
  background: color-mix(in srgb, var(--canvas) 55%, transparent);
  font-size: var(--type-body);
  animation: application-detail-enter var(--motion-entrance) var(--ease-entrance)
    both;
}

@keyframes application-detail-enter {
  from {
    opacity: 0;
    transform: translateY(calc(-1 * var(--space-1)));
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.application-detail__body > * + * {
  margin-top: var(--space-2);
}

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

.application-works {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  margin: 0;
  padding: 0;
  list-style: none;
}

.application-work {
  padding: var(--space-1) var(--space-2);
  border-left: 2px solid color-mix(in srgb, var(--region) 55%, transparent);
  border-radius: 0 var(--radius-input) var(--radius-input) 0;
  background: color-mix(in srgb, var(--text-primary) 4%, transparent);
}

.application-work .caption {
  display: block;
}

.application-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
}

/* --- Empty state ---------------------------------------------------- */

/* Most of this site is empty for its first six months, so the empty inbox is
 * a designed surface rather than a sentence: region glow, one mark, one line
 * with a point of view, one action. */
.inbox-empty {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-6) var(--space-4);
  background:
    radial-gradient(
      70% 120% at 12% 50%,
      color-mix(in srgb, var(--region) 16%, transparent) 0%,
      transparent 70%
    ),
    var(--panel-fill);
}

.inbox-empty__mark {
  margin: 0;
  color: color-mix(in srgb, var(--region) 60%, transparent);
  text-shadow: 0 0 var(--space-6) var(--region);
  line-height: 1;
}

/* --- The openings strip under the table ----------------------------- */

.opening-strip {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
  margin: 0;
  padding: 0;
  list-style: none;
}

.opening-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: var(--space-1) var(--space-2);
  border: var(--border-width) solid var(--panel-border);
  border-radius: var(--radius-pill);
  color: var(--text-primary);
  font-size: var(--type-small);
  font-weight: 700;
  text-decoration: none;
  transition:
    border-color var(--motion-hover) var(--ease-standard),
    background-color var(--motion-hover) var(--ease-standard),
    transform var(--motion-hover) var(--ease-standard);
}

.opening-chip:hover {
  color: var(--text-primary);
  border-color: color-mix(in srgb, var(--region) 60%, var(--panel-border));
  background: color-mix(in srgb, var(--region) 14%, transparent);
  transform: translateY(calc(-1 * var(--lift-control)));
}

.opening-chip__state {
  color: var(--text-secondary);
  font-weight: 400;
}

.opening-chip__count {
  padding-left: var(--space-1);
  border-left: var(--border-width) solid var(--panel-border);
  color: var(--text-secondary);
}

/* ==================================================================== *
 *  External funding handoff                                            *
 * ==================================================================== */

/* What a crewed project's owner meets in place of a funding surface this
 * site does not have yet (docs/PLAN.md 4.2).
 *
 * It runs on the campaign region's ember rather than the project page's
 * violet: funding is its own place in this product, and it keeps that hue
 * when the native campaign arrives in Stage 7. The override on --region is
 * the only line here that names an accent; everything else reads it, so the
 * panel re-skins with the token. */
.handoff-panel {
  --region: var(--region-campaign);
  position: relative;
  overflow: hidden;
  border-color: color-mix(in srgb, var(--region) 32%, var(--panel-border));
  box-shadow:
    var(--panel-lift),
    0 0 var(--space-12) color-mix(in srgb, var(--region) 10%, transparent);
}

.handoff-panel::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 2px;
  background: linear-gradient(
    90deg,
    var(--region) 0%,
    color-mix(in srgb, var(--region) 20%, transparent) 100%
  );
}

.handoff-panel__head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  width: 100%;
}

/* The recorded campaign: the one part of this panel a visitor sees. The
 * label is what keeps a bare URL from reading as loose text. */
.handoff-record {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-1) var(--space-2);
  width: 100%;
  margin: 0;
}

.handoff-record__label {
  font-size: var(--type-small);
  font-weight: 700;
  color: var(--text-secondary);
}

/* Three platforms, one honest line each. A column rather than three cards:
 * they are not equivalent choices, and a row of identical tiles would say
 * they were. Name and note are one unit at one space step; the platforms are
 * separated at two. */
.handoff-platforms {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  width: 100%;
  margin: 0;
  padding: 0;
  list-style: none;
}

.handoff-platform {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.handoff-platform__name {
  font-weight: 700;
}

.handoff-platform__note {
  max-width: var(--measure-text);
  color: var(--text-secondary);
}

.handoff-form {
  width: 100%;
}

/* The lifecycle step, held off the recording form by a real divider: one is
 * a note about somebody else's platform, the other changes what this project
 * is and, at `shipped`, cannot be taken back. */
.handoff-step {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  width: 100%;
  padding-top: var(--space-2);
  border-top: var(--border-width) solid var(--panel-border);
}

.handoff-step__note {
  max-width: var(--measure-text);
}

/* ==================================================================== *
 *  Narrow viewports                                                    *
 * ==================================================================== */

@media (max-width: 900px) {
  .opening-layout,
  .application-detail {
    grid-template-columns: minmax(0, 1fr);
  }

  .opening-terms {
    position: static;
  }
}

/* ==================================================================== *
 *  Reduced motion                                                      *
 *                                                                      *
 *  base.css collapses every duration; the lifts and the rotation need  *
 *  their own reset, and the expanded row's entrance is replaced by an  *
 *  instant appearance rather than a fast one.                          *
 * ==================================================================== */

@media (prefers-reduced-motion: reduce) {
  .inbox-toggle:hover,
  .inbox-flag:hover,
  .opening-chip:hover {
    transform: none;
  }

  .application-detail {
    animation: none;
  }

  /* The chevron still has to say open or closed without turning, so the
   * expanded state swaps the triangle's direction outright. */
  .is-expanded .inbox-toggle__chevron {
    transform: none;
    border-left: 5px solid transparent;
    border-right: 5px solid transparent;
    border-top: 7px solid currentColor;
    border-bottom: 0;
  }
}

/* ==== static/css/regions/profile.css ==== */
/* Profile and portfolio surfaces: /p/<handle> and its edit forms.
 *
 * Region accent is sun (`.region-profile` in base.css, --accent-sun #FFD426),
 * read here only as var(--region) so the sheet never names a hue. Concatenated
 * after base.css by tools/build_css.py.
 *
 * Two component classes land here rather than in base.css because this is the
 * first surface to need them: `.checkbox` and `.input--file`. They move down
 * into base.css when a second region wants them.
 */

/* ==================================================================== *
 *  Identity card                                                       *
 * ==================================================================== */

.profile-hero {
  padding-block: var(--space-4) 0;
}

/* The banner bleeds to the panel's own edges, so the glass reads as a single
 * object with an image set into its top rather than as an image with a panel
 * stuck underneath it. */
.profile-card {
  padding: 0;
  overflow: hidden;
}

.profile-card__banner {
  display: block;
  width: 100%;
  height: clamp(120px, 22vw, 240px);
  object-fit: cover;
  border-bottom: var(--border-width) solid var(--panel-border);
}

/* No banner uploaded: the canvas gradient and a wash of region colour, which
 * is a deliberate surface rather than a grey placeholder box. */
.profile-card__banner--empty {
  background:
    radial-gradient(
      120% 180% at 18% 0%,
      color-mix(in srgb, var(--region) 22%, transparent) 0%,
      transparent 62%
    ),
    linear-gradient(
      160deg,
      color-mix(in srgb, var(--anchor) 92%, transparent) 0%,
      transparent 88%
    );
}

.profile-card__body {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  grid-template-areas:
    "portrait identity facts"
    "portrait actions  actions"
    "links    links    links";
  gap: var(--space-2) var(--space-4);
  align-items: start;
  padding: var(--space-4);
}

.profile-card__portrait {
  grid-area: portrait;
  /* Pulls the avatar up over the banner's lower edge, which is what makes the
   * two read as one card instead of two stacked rectangles. */
  margin-top: calc(var(--space-6) * -1.6);
}

.profile-card__avatar {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 132px;
  height: 132px;
  object-fit: cover;
  border-radius: var(--radius-pill);
  border: 2px solid color-mix(in srgb, var(--region) 55%, var(--panel-border));
  box-shadow:
    var(--panel-lift),
    0 0 var(--space-6) color-mix(in srgb, var(--region) 26%, transparent);
  background: var(--canvas);
}

/* No avatar: the initial, in region colour, at display weight. Reads as a
 * designed mark rather than a broken image. */
.profile-card__avatar--empty {
  font-size: var(--type-h1);
  font-weight: 700;
  line-height: 1;
  color: var(--region);
  background:
    radial-gradient(
      100% 100% at 50% 0%,
      color-mix(in srgb, var(--region) 20%, transparent) 0%,
      transparent 70%
    ),
    var(--canvas);
}

.profile-card__identity {
  grid-area: identity;
  padding-top: var(--space-1);
}

.profile-card__name {
  font-size: var(--type-h1);
  line-height: var(--line-heading);
  letter-spacing: var(--tracking-display);
}

.profile-card__handle {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-2);
  color: var(--text-secondary);
}

.profile-card__pronouns {
  padding: 0 var(--space-1);
  border-left: var(--border-width) solid var(--panel-border);
}

.profile-card__actions {
  grid-area: actions;
  margin-top: var(--space-1);
}

/* --- Availability ---------------------------------------------------- */

/* The hue is never the only channel: every state prints its own words, and
 * the dot differs in fill as well as in colour. */
.availability {
  --availability: var(--text-secondary);
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: var(--space-1) var(--space-2);
  border: var(--border-width) solid
    color-mix(in srgb, var(--availability) 45%, var(--panel-border));
  border-radius: var(--radius-pill);
  color: var(--availability);
  font-size: var(--type-small);
  font-weight: 700;
}

.availability__dot {
  width: var(--space-1);
  height: var(--space-1);
  border-radius: var(--radius-pill);
  background: var(--availability);
  box-shadow: 0 0 var(--space-2) var(--availability);
}

.availability--open { --availability: var(--state-success); }
.availability--limited { --availability: var(--accent-ember); }

.availability--closed {
  --availability: var(--text-secondary);
}

.availability--closed .availability__dot {
  background: transparent;
  border: var(--border-width) solid var(--text-secondary);
  box-shadow: none;
}

/* --- Facts and links -------------------------------------------------- */

.profile-facts {
  grid-area: facts;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-4);
  margin: 0;
  justify-content: flex-end;
}

.profile-facts__item {
  display: flex;
  flex-direction: column;
  gap: 0;
}

.profile-facts dt {
  font-size: var(--type-small);
  font-weight: 700;
  letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
  color: var(--region);
}

.profile-facts dd {
  margin: 0;
  color: var(--text-primary);
}

.profile-links {
  grid-area: links;
  margin: 0;
  padding: 0;
  list-style: none;
  gap: var(--space-1) var(--space-2);
}

.profile-links__link {
  display: inline-block;
  max-width: 34ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  vertical-align: bottom;
}

/* ==================================================================== *
 *  Roles                                                               *
 * ==================================================================== */

.role-list {
  margin: 0;
  padding: 0;
  list-style: none;
}

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

/* The primary role is the one line a project owner reads in a list of
 * applicants, so it carries the accent edge on this page too. */
.role--primary {
  border-color: color-mix(in srgb, var(--region) 45%, var(--panel-border));
  box-shadow:
    var(--panel-lift),
    0 0 var(--space-6) color-mix(in srgb, var(--region) 14%, transparent);
}

.role__flag {
  font-size: var(--type-small);
  font-weight: 700;
  letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
  color: var(--region);
}

/* ==================================================================== *
 *  About                                                               *
 * ==================================================================== */

.profile-about {
  display: grid;
  gap: var(--space-4);
  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
  align-items: start;
}

.ai-stance {
  border-color: color-mix(in srgb, var(--state-info) 32%, var(--panel-border));
}

.ai-stance .eyebrow {
  color: var(--state-info);
}

/* --- Rendered markdown ------------------------------------------------ */

/* Everything here has been through apps.core.markdown, so the tag set is the
 * allow-list in that module and nothing else can appear. */
.prose {
  max-width: var(--measure-text);
}

.prose > * + * {
  margin-top: var(--space-2);
}

.prose p,
.prose li {
  margin: 0;
  line-height: var(--line-body);
}

.prose h2,
.prose h3,
.prose h4 {
  font-size: var(--type-h3);
}

.prose ul,
.prose ol {
  margin: 0;
  padding-left: var(--space-4);
}

.prose li + li {
  margin-top: var(--space-1);
}

.prose blockquote {
  margin: 0;
  padding-left: var(--space-2);
  border-left: var(--space-1) solid
    color-mix(in srgb, var(--region) 50%, transparent);
  color: var(--text-secondary);
}

.prose code {
  font-family: var(--font-mono);
  font-size: var(--type-small);
  padding: 0 calc(var(--space-1) / 2);
  border-radius: calc(var(--radius-input) / 2);
  background: color-mix(in srgb, var(--canvas) 70%, transparent);
}

.prose pre {
  margin: 0;
  padding: var(--space-2);
  overflow-x: auto;
  border: var(--border-width) solid var(--panel-border);
  border-radius: var(--radius-input);
  background: color-mix(in srgb, var(--canvas) 76%, transparent);
}

.prose hr {
  border: 0;
  border-top: var(--border-width) solid var(--panel-border);
}

.prose--tight {
  font-size: var(--type-small);
  color: var(--text-secondary);
}

.prose--tight > * + * {
  margin-top: var(--space-1);
}

/* ==================================================================== *
 *  Tabs: Sparks / In production / Shipped                              *
 * ==================================================================== */

/* Links rather than a scripted widget, so each tab is a shareable URL. No
 * container fill: the tabs float on the canvas like everything else. */
.tabs {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
}

.tab {
  display: inline-flex;
  align-items: baseline;
  gap: var(--space-1);
  padding: var(--space-1) var(--space-2);
  min-height: calc(var(--control-height) - var(--space-2));
  border: var(--border-width) solid var(--panel-border);
  border-radius: var(--radius-pill);
  background: var(--panel-fill);
  color: var(--text-secondary);
  font-weight: 700;
  text-decoration: none;
  backdrop-filter: blur(var(--panel-blur));
  -webkit-backdrop-filter: blur(var(--panel-blur));
  transition:
    transform var(--motion-hover) var(--ease-standard),
    color var(--motion-hover) var(--ease-standard),
    border-color var(--motion-hover) var(--ease-standard),
    background-color var(--motion-hover) var(--ease-standard),
    box-shadow var(--motion-hover) var(--ease-standard);
}

.tab__count {
  padding: 0 var(--space-1);
  border-radius: var(--radius-pill);
  background: color-mix(in srgb, var(--text-primary) 10%, transparent);
  font-size: var(--type-small);
}

.tab:hover {
  transform: translateY(calc(-1 * var(--lift-control)));
  color: var(--text-primary);
  border-color: color-mix(in srgb, var(--region) 60%, var(--panel-border));
  background: color-mix(in srgb, var(--region) 14%, transparent);
  box-shadow: 0 0 var(--space-6)
    color-mix(in srgb, var(--region) 20%, transparent);
}

.tab:active {
  transform: translateY(0);
}

/* The active tab takes the region fill and the glow sits under it. */
.tab[aria-current="page"] {
  background: var(--region);
  border-color: var(--region);
  color: var(--text-on-accent);
  box-shadow: 0 var(--space-1) var(--space-4)
    color-mix(in srgb, var(--region) 34%, transparent);
}

.tab[aria-current="page"] .tab__count {
  background: color-mix(in srgb, var(--canvas) 22%, transparent);
}

/* ==================================================================== *
 *  Credits                                                             *
 * ==================================================================== */

.portfolio {
  margin: 0;
  padding: 0;
  list-style: none;
}

.credit {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-2);
  transition:
    transform var(--motion-hover) var(--ease-standard),
    border-color var(--motion-hover) var(--ease-standard),
    box-shadow var(--motion-hover) var(--ease-standard);
}

/* The card lifts as one object when its title link is under the cursor or
 * keyboard focus, rather than only the link changing colour. */
.credit:has(.credit__link:hover),
.credit:has(.credit__link:focus-visible) {
  transform: translateY(calc(-1 * var(--lift-panel)));
  border-color: color-mix(in srgb, var(--region) 55%, var(--panel-border));
  box-shadow:
    var(--panel-lift),
    0 0 var(--space-6) color-mix(in srgb, var(--region) 22%, transparent);
}

.credit__image {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 10;
  object-fit: cover;
  border-radius: var(--radius-input);
  border: var(--border-width) solid var(--panel-border);
  background: var(--canvas);
}

.credit__link {
  color: var(--text-primary);
  text-decoration: none;
}

.credit__link:hover {
  color: var(--region);
  text-decoration: underline;
  text-underline-offset: 0.18em;
}

.credit__meta {
  color: var(--text-secondary);
}

.credit__owner-actions {
  margin-top: auto;
  padding-top: var(--space-1);
  border-top: var(--border-width) solid var(--panel-border);
  width: 100%;
}

/* --- The provenance marker -------------------------------------------- */

/* Shown only on a credit whose source is `artignitor`. Its whole value is
 * that it cannot be self-awarded, so it is built to look like a stamp rather
 * than like a decoration: colour icon, accent edge, ember glow, and its own
 * words beside it. */
.flame-mark {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  align-self: flex-start;
  padding: calc(var(--space-1) / 2) var(--space-1);
  border: var(--border-width) solid
    color-mix(in srgb, var(--accent-ember) 50%, var(--panel-border));
  border-radius: var(--radius-pill);
  background: color-mix(in srgb, var(--accent-ember) 12%, transparent);
  box-shadow: 0 0 var(--space-4)
    color-mix(in srgb, var(--accent-ember) 20%, transparent);
}

.flame-mark__icon {
  flex: 0 0 auto;
  filter: drop-shadow(
    0 0 var(--space-1) color-mix(in srgb, var(--accent-ember) 60%, transparent)
  );
}

.flame-mark__label {
  font-size: var(--type-small);
  font-weight: 700;
  letter-spacing: 0.04em;
  color: var(--text-primary);
  white-space: nowrap;
}

/* ==================================================================== *
 *  Empty states                                                        *
 * ==================================================================== */

.empty-state {
  align-items: flex-start;
  gap: var(--space-2);
  text-align: left;
  background:
    radial-gradient(
      90% 140% at 12% 0%,
      color-mix(in srgb, var(--region) 10%, transparent) 0%,
      transparent 64%
    ),
    var(--panel-fill);
}

.empty-state__glyph {
  color: var(--region);
  opacity: 0.7;
}

.empty-state .caption {
  max-width: var(--measure-text);
}

.empty-note {
  color: var(--text-secondary);
}

/* ==================================================================== *
 *  Profile and portfolio forms                                         *
 * ==================================================================== */

.form-panel {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  border: var(--border-width) solid var(--panel-border);
  min-width: 0;
}

.form-panel__legend {
  padding: 0 var(--space-1);
  font-size: var(--type-h3);
  font-weight: 700;
  color: var(--text-primary);
}

.form-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-4);
  align-items: flex-start;
}

.form-row .field {
  flex: 1 1 220px;
  max-width: none;
}

.form-row--flags {
  align-items: center;
  gap: var(--space-4);
}

.field--inline {
  flex-direction: row;
  align-items: center;
  gap: var(--space-1);
  width: auto;
}

.field--inline .field__label {
  order: 2;
}

.field--danger .field__label {
  color: var(--state-error);
}

.role-rows {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin: 0;
  padding: 0;
  list-style: none;
}

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

.form-actions {
  gap: var(--space-2);
}

.portfolio-remove {
  margin-top: var(--space-4);
}

/* A destructive action reads as one at rest, not only once it is pressed. */
.btn.is-danger {
  --btn-edge: color-mix(in srgb, var(--state-error) 50%, var(--panel-border));
  --btn-glow: var(--state-error);
  color: var(--state-error);
}

/* Controls base.css has not needed until now. */
.checkbox {
  width: var(--space-2);
  height: var(--space-2);
  accent-color: var(--region);
  cursor: pointer;
}

.input--file {
  padding: var(--space-1);
  cursor: pointer;
}

.select--multiple {
  min-height: calc(var(--control-height) * 3);
  padding: var(--space-1);
}

/* ==================================================================== *
 *  Narrow viewports                                                    *
 * ==================================================================== */

@media (max-width: 760px) {
  .profile-card__body {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas:
      "portrait"
      "identity"
      "facts"
      "actions"
      "links";
  }

  .profile-facts {
    justify-content: flex-start;
  }

  .profile-card__portrait {
    margin-top: calc(var(--space-6) * -1.2);
  }
}

@media (prefers-reduced-motion: reduce) {
  .tab:hover,
  .tab:active,
  .credit:has(.credit__link:hover),
  .credit:has(.credit__link:focus-visible) {
    transform: none;
  }
}
