/* mujo Hub design system.

   Two canvases, one rule: a surface spends colour on its ground OR on its
   content, never both.

   The hub is a tool. Its ground is flat and near-neutral, its cards are white
   with a hairline, and the colour budget is spent on the one row that is late
   and the one button that is the move. No glass, no blur, no shadow on a card:
   on a neutral ground a border already says "separate object", and the lift
   was spending the contrast that urgency needs.

   The creator portal is the brand. It keeps the lavender canvas under
   .portal-page, because she meets mujo there once and there is no queue on
   that page for the colour to drown out.

   One ink (forest), one accent, three status colours that mean something and
   are never decoration.

   The accent is rationed by rank, not by role: lime marks the single most
   urgent action on a screen, and every other action, however important, is
   the dark pill (.btn-forest). Sixteen lime buttons down one page is the same
   as none - the eye takes the first and reads the rest as texture. That is
   also why there is no lime chip: a chip is a label, and a label is never the
   thing you should do next. */

/* Licence note. The installed cut's name table reads "Indarung Personal":
   86 glyphs, no umlauts, no ß, no ō, no OpenType features. The commercial
   licence for an internal product is still to be confirmed with the foundry.
   Until it is, the face sets exactly one display title per screen, in
   English, never under 28px, and dynamic text passes through display_text
   (app/helpers/display_helper.rb), which falls back to Quicksand for any
   string the cut cannot set. */
@font-face {
  font-family: "Indarung";
  src: url("/assets/Indarung-ae08f7af.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Quicksand";
  src: url("/assets/Quicksand-836c8f42.woff2") format("woff2");
  font-weight: 300 700;
  font-style: normal;
  font-display: swap;
}

/* Inter Variable (SIL OFL 1.1, licence next to the file in fonts/inter/),
   subset to Latin with tnum, zero, cv11 and both axes kept. It sets numbers
   only: Quicksand has no tabular figures, so columns of money and day counts
   wobbled by digit width. Never a heading, never a button. */
@font-face {
  font-family: "Inter";
  src: url("/assets/inter/InterVariable-45e1aeca.woff2") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

/* Tokens. Every component below reads from here, so a change to a duration,
   a curve or an ink step lands everywhere at once. Existing names are kept;
   the new ones are motion, layers, control and shadow tokens that the
   components used to invent for themselves. */
:root {
  color-scheme: light;

  /* Ink: one forest, four steps, now read from the hue-159 neutral ramp in
     wave5-tokens.css instead of forest alphas, so secondary text is a solid
     colour that measures the same on every surface. --ink-faint is for the
     11px caps labels and nothing longer: neutral-500 is 3.5:1 on cream. The
     fourth step is for disabled and placeholder. */
  --forest: #0C4129;
  --ink: var(--neutral-900);
  --ink-soft: var(--neutral-600);
  --ink-faint: var(--neutral-500);
  --ink-mute: var(--neutral-400);

  /* Accent: primary actions and nothing else. */
  --lime: #EFF860;
  --lime-deep: #E4EE3C;
  --lime-ink: #000000;
  --lime-glow: rgba(239, 248, 96, .32);

  /* Lilac for the portal canvas and the portal-flavoured surfaces inside the
     hub. The gradient is defined once here and used on .portal-page. */
  --lavender: #C3B1E5;
  --lavender-soft: #DFC8E7;
  --lavender-tint: #F3ECF6;
  /* Flat since wave 5: a lilac gradient is the first thing every list of
     generated-UI tells names, and the canvas only reads as a brand colour
     while it is a plain sheet. */
  --portal-ground: #D2BFFB;

  /* State. Used for what is late, what is fine, what broke. Never decoration.
     The 600 step is the UI colour (dots, fills, text on cream), the 700 and
     800 steps are the ink; the portal re-points these at the darker steps
     because the lilac canvas is lighter than it looks. */
  --ok: var(--success-600);
  --ok-ink: var(--success-700);
  --ok-soft: var(--success-tint);
  --warn: var(--warning-600);
  --warn-ink: var(--warning-800);
  --warn-soft: var(--warning-tint);
  --bad: var(--danger-600);
  --bad-ink: var(--danger-700);
  --bad-soft: var(--danger-tint);

  /* Canvas and surfaces, lowest to highest.

     One rule decides this block: a surface spends colour on its ground OR on
     its content, never both. The hub is where work happens, so its ground is
     flat and near-neutral (a faint green bias off the forest ink, so it reads
     as chosen rather than as default grey) and the colour budget goes to the
     one late row and the one lime button.

     The lavender is not gone, it moved. It lives on .portal-page, where a
     creator meets the brand once and there is no operational signal to drown. */
  --ground: var(--surface-0);
  --surface-soft: var(--neutral-150);
  --surface-solid: #FFFFFF;
  --surface-raised: #FFFFFF;
  --surface-sheet: #FBF9FF;
  --glass: var(--surface-solid);
  --glass-border: var(--line);

  /* Lines, by weight, from the neutral ramp. --line separates objects,
     --line-soft separates rows inside one object. The ring is the half-pixel
     edge that light draws around anything that lifts; its colour and the
     shadow stacks are set in wave5-tokens.css. */
  --line-soft: var(--neutral-150);
  --line: var(--neutral-200);
  --line-strong: var(--neutral-400);
  --hairline: var(--neutral-200);
  --ring: 0 0 0 .5px rgba(12, 65, 41, .18);

  /* Controls */
  --control-bg: var(--surface-soft);
  --control-bg-focus: var(--surface-solid);
  --control-line: var(--line);
  --control-line-focus: var(--ink);
  --focus-ring: 0 0 0 3px rgba(12, 65, 41, .22);

  /* Shadow stacks: hairline ring, then layers with doubling offsets, every
     layer in --shadow-color (the forest at seven percent on cream, the lilac
     on the portal; set in wave5-tokens.css). One light source, straight
     above. Cards stay flat (hub rule). These are for the things that lift
     off the page: menus, popovers, sheets, toasts. */
  --shadow-rest: var(--ring), 0 1px 1px var(--shadow-color);
  --shadow-hover: var(--ring), 0 1px 1px var(--shadow-color), 0 2px 2px var(--shadow-color), 0 4px 4px var(--shadow-color);
  --shadow-menu: var(--ring), 0 1px 1px var(--shadow-color), 0 2px 2px var(--shadow-color), 0 4px 4px var(--shadow-color), 0 8px 8px var(--shadow-color);
  --shadow-sheet: var(--ring), 0 1px 1px var(--shadow-color), 0 2px 2px var(--shadow-color), 0 4px 4px var(--shadow-color), 0 8px 8px var(--shadow-color), 0 16px 16px var(--shadow-color);
  --shadow: var(--shadow-sheet);

  /* Type scale and line heights */
  --fs-display: 34px; --lh-display: 1.1;
  --fs-stat: 40px;    --lh-stat: 1;
  --fs-h1: 27px;      --lh-h1: 1.2;
  --fs-title: 17px;   --lh-title: 1.3;
  --fs-h3: 15px;
  --fs-body: 14px;    --lh-body: 1.55;
  --fs-sm: 12.5px;    --lh-sm: 1.5;
  --fs-label: 11px;   --lh-label: 1.2;
  --fs-input-mobile: 16px;
  /* Lining, tabular figures for any column of numbers, so AED amounts and
     day counts line up instead of wobbling by digit width. */
  --num: "tnum" 1, "lnum" 1;

  /* Spacing: 4px base */
  --s1: 4px; --s2: 8px; --s3: 12px; --s4: 16px; --s5: 20px; --s6: 24px; --s7: 28px; --s8: 32px; --s10: 40px; --s12: 48px;

  /* Radius */
  --r-xs: 8px;
  --r-sm: 12px;
  --r: 18px;
  --r-lg: 24px;
  --r-pill: 50px;

  /* Motion. One house curve for everything that settles, an in curve for
     exits only, in-out for things that slide between two places, a pop for
     ticks and counts, and the iOS sheet curve for the bottom sheet. */
  --dur-fast: 120ms;
  --dur-base: 200ms;
  --dur-enter: 280ms;
  --dur-slow: 420ms;
  --ease-out: cubic-bezier(0.22, 1, 0.36, 1);
  --ease-in: cubic-bezier(0.55, 0, 1, 0.45);
  --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
  --ease-pop: cubic-bezier(0.35, 1.55, 0.65, 1);
  --ease-sheet: cubic-bezier(0.32, 0.72, 0, 1);

  /* Layers. Dialogs use the browser's top layer and need no z-index. */
  --z-sticky: 10;
  --z-menu: 20;
  --z-toast: 30;
  --z-nav: 40;

  /* Touch */
  --tap: 44px;
  --safe-b: env(safe-area-inset-bottom, 0px);
  --safe-t: env(safe-area-inset-top, 0px);
}

html {
  min-height: 100%;
}

body {
  margin: 0;
  min-height: 100vh;
  font-family: "Quicksand", ui-rounded, sans-serif;
  font-weight: 500;
  font-size: var(--fs-body);
  color: var(--ink);
  background: var(--ground);
}

/* The creator portal keeps the brand canvas: she meets mujō here once, there
   is no queue to read, and nothing on this page competes with it. */
body.portal-page {
  background: var(--portal-ground) fixed;
}

/* Page titles inside the hub are body type at weight. Indarung sets wide caps,
   which reads as a banner rather than a heading and fights the sentence under
   it: on a tool, the title should name the page and get out of the way.
   The display face still opens the portal and the sign-in page, where a
   headline is the whole point, and still sets the big numbers. */
h1 {
  font-family: "Quicksand", sans-serif;
  font-weight: 700;
  font-size: 27px;
  margin: 0 0 6px;
}

.portal-hello,
.hub-auth h1 {
  font-family: "Indarung", "Quicksand", sans-serif;
  font-weight: 400;
  font-size: var(--fs-display);
}

h2, h3 {
  font-family: "Quicksand", sans-serif;
  font-weight: 700;
  margin: 0 0 10px;
}

h2 { font-size: var(--fs-title); }
h3 { font-size: 15px; }

/* A card holding more than one block: the second heading needs air above it,
   or it sits flush on the table it follows. Only when something precedes it,
   so the first heading in a card keeps sitting on the card's own padding. */
.card :is(h2, h3):not(:first-child) { margin-top: 26px; }

/* The note under a table is about the table, so it keeps its own spacing and
   just needs to clear the last row. */
.card .hub-table + p { margin-top: 12px; }

p { line-height: 1.55; }

a { color: var(--ink); }

/* Keyboard users could not see where they were. One ring, everywhere. */
:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 2px;
  border-radius: var(--r-sm);
}

/* Reduced motion: everything lands at once. The iteration count matters as
   much as the duration, because a zero-second loop (the submit shimmer, a
   pulse) would still run forever. The toast keeps its timer and loses only
   the countdown bar. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }

  .hub-dialog,
  .hub-dialog::backdrop {
    transition: none;
    filter: none;
    transform: none;
  }

  .flash-stack .flash::after {
    animation: none;
  }
}

/* Shell */

.hub-shell {
  display: flex;
  min-height: 100vh;
}

.hub-sidebar {
  width: 236px;
  flex-shrink: 0;
  box-sizing: border-box;
  position: sticky;
  top: 0;
  height: 100vh;
  padding: 22px 14px;
  display: flex;
  flex-direction: column;
  gap: 18px;
  background: var(--surface-soft);
  border-right: 1px solid var(--line);
}

.hub-brand {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 4px 12px;
}

.hub-logo svg {
  height: 20px;
  width: auto;
  display: block;
}

.hub-nav {
  display: flex;
  flex-direction: column;
  gap: 2px;
  flex: 1;
  overflow-y: auto;
}

.hub-nav-link {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 9px 14px;
  border-radius: var(--r-pill);
  color: var(--ink);
  text-decoration: none;
  font-weight: 600;
  font-size: var(--fs-body);
  border: 1px solid transparent;
  transition: background var(--dur-fast) var(--ease-out), transform var(--dur-fast) var(--ease-out);
}

.hub-nav-link:hover {
  background: rgba(12, 65, 41, .06);
}

/* The page you are on is a solid white pill against the recessed sidebar. */
.hub-nav-link.is-active {
  background: var(--surface-solid);
  border-color: var(--line);
  font-weight: 700;
}

.hub-nav-link:active {
  transform: scale(.98);
}

.hub-nav-link:focus-visible {
  border-radius: var(--r-pill);
}

/* The number that tells you whether to open the page at all. */
.hub-nav-badge {
  min-width: 20px;
  height: 20px;
  padding: 0 6px;
  box-sizing: border-box;
  border-radius: var(--r-pill);
  background: var(--ink);
  color: var(--lime);
  font-size: var(--fs-label);
  font-weight: 700;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-variant-numeric: tabular-nums;
}

.hub-nav-badge.is-bad {
  background: var(--bad);
  color: #fff;
}

.hub-sidebar-footer {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.hub-sidebar-footer .hub-nav-link {
  font-size: var(--fs-sm);
  font-weight: 600;
  padding: 7px 14px;
  color: var(--ink-soft);
}

.hub-user-chip {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 12px;
  border-radius: var(--r-pill);
  background: var(--glass);
  border: 1px solid var(--glass-border);
  text-decoration: none;
  color: inherit;
  transition: background var(--dur-fast) var(--ease-out), transform var(--dur-fast) var(--ease-out);
}

.hub-user-chip:hover {
  background: var(--surface-soft);
}

.hub-user-chip:active {
  transform: scale(.98);
}

.hub-avatar {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: var(--ink);
  color: var(--lime);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  font-size: 12px;
  flex-shrink: 0;
}

.hub-user-meta {
  display: flex;
  flex-direction: column;
  line-height: 1.25;
  min-width: 0;
}

.hub-user-meta strong {
  font-size: 13px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.hub-user-meta small {
  font-size: var(--fs-label);
  color: var(--ink-soft);
}

.hub-main {
  flex: 1;
  min-width: 0;
  padding: 34px 38px 60px;
}

.hub-auth {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
}

.auth-column {
  width: 100%;
  max-width: 400px;
}

.auth-brand {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 22px;
}

.auth-brand svg {
  height: 22px;
  width: auto;
  display: block;
}

/* Cards */

/* White on a flat ground, separated by a hairline. No blur, no shadow: on a
   neutral canvas a border is enough to say "separate object", and the lift
   was spending contrast that the late rows need. */
.card {
  background: var(--glass);
  border: 1px solid var(--line);
  border-radius: var(--r);
  padding: 22px 24px;
}

/* A container that holds a table or a number. Part of the page rather than
   something sitting on it, so it drops to the recessed surface. */
.card-plain {
  background: var(--surface-soft);
  border-color: var(--line-soft);
}

.card-flush {
  padding: 10px 16px;
}

.card-narrow {
  max-width: 560px;
}

/* A form somebody composes in rather than fills in. 560px is right for a
   column of one-line fields and cramped for three text areas holding the
   sentences a creator will read out. */
.card-form {
  max-width: 720px;
}

.card-head-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  margin-bottom: 12px;
}

.card-head-row h2 {
  margin-bottom: 0;
}

.grid-cards {
  display: grid;
  gap: var(--s6);
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
}

/* Two grids in a row were touching. Every card-to-card distance is one value. */
.grid-cards + .grid-cards,
.grid-cards + .card,
.card + .grid-cards {
  margin-top: var(--s6);
}

.stack > * + * { margin-top: var(--s6); }
.stack-sm > * + * { margin-top: 14px; }
.mt-gap { margin-top: var(--s6); }
.mt-sm { margin-top: var(--s3); }
.mb-sm { margin-bottom: var(--s3); }
.text-center { text-align: center; }
.text-left { text-align: left; }

/* Buttons or links that sit side by side. */
.actions-row {
  display: flex;
  align-items: center;
  gap: var(--s2);
  flex-wrap: wrap;
}

.stat-row {
  display: grid;
  gap: var(--s3);
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
}

.stat-card {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  padding: 14px 18px;
  border-radius: var(--r);
  text-decoration: none;
  color: inherit;
  transition: background var(--dur-fast) var(--ease-out), transform var(--dur-fast) var(--ease-out);
}

a.stat-card:hover {
  background: var(--surface-soft);
}

a.stat-card:active {
  transform: scale(.98);
}

.stat-card .stat-value {
  font-size: 28px;
}

.stat-label {
  font-size: var(--fs-label);
  font-weight: 700;
  text-transform: uppercase;
  color: var(--ink-soft);
}

.stat-value {
  font-family: "Indarung", "Quicksand", sans-serif;
  font-size: var(--fs-stat);
  line-height: 1;
  font-variant-numeric: tabular-nums;
}

/* The display face is drawn for digits. A stat whose value is words - two
   creator names, "none yet", a currency code - came out as wide caps you have
   to decode rather than read, so those set in the body face at weight. */
.stat-value-words {
  font-family: "Quicksand", sans-serif;
  font-weight: 700;
  font-size: 22px;
  line-height: 1.2;
}

.stat-value-sm {
  font-size: 28px;
}

/* Buttons: always pill */

.btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  border-radius: var(--r-pill);
  text-transform: uppercase;
  font-family: "Quicksand", sans-serif;
  font-weight: 700;
  font-size: 13px;
  line-height: 1;
  padding: 12px 22px;
  cursor: pointer;
  text-decoration: none;
  white-space: nowrap;
  transition: background var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              transform var(--dur-fast) var(--ease-out),
              box-shadow var(--dur-base) var(--ease-out);
  will-change: transform;
}

/* A firm press, not a collapse: the button shrinks two percent under the
   finger and comes back on the house curve. */
.btn:active {
  transform: scale(.98);
}

.btn:focus-visible {
  outline-offset: 2px;
  border-radius: var(--r-pill);
}

.btn-primary {
  background: linear-gradient(180deg, var(--lime), var(--lime-deep));
  border: 1.5px solid var(--lime-ink);
  color: var(--lime-ink);
  box-shadow: 0 1px 0 rgba(0, 0, 0, .12), 0 2px 8px var(--lime-glow);
}

.btn-primary:hover {
  background: var(--ink);
  color: var(--lime);
  box-shadow: 0 1px 0 rgba(0, 0, 0, .12), 0 4px 14px var(--lime-glow);
}

.btn-primary:active {
  box-shadow: 0 0 0 rgba(0, 0, 0, 0);
}

.btn-secondary {
  background: var(--surface-soft);
  border: 1px solid var(--hairline);
  color: var(--ink);
}

.btn-secondary:hover {
  background: var(--surface-solid);
}

/* Dark pill: a second strong action that must not compete with the lime one. */
.btn-forest {
  background: var(--ink);
  border: 1.5px solid var(--ink);
  color: var(--lime);
}

.btn-forest:hover {
  background: #145a3a;
  border-color: #145a3a;
}

/* Removes, closes, deactivates. Quiet until hovered, so it is findable but
   never the thing your eye lands on first. */
.btn-danger {
  background: transparent;
  border: 1px solid rgba(199, 69, 61, .45);
  color: var(--bad);
}

.btn-danger:hover {
  background: var(--bad);
  border-color: var(--bad);
  color: #fff;
}

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

/* Loading: while the form is busy Turbo disables the button and swaps its
   label through data-turbo-submits-with, and the label gets a slow sheen
   instead of a spinner. Scoped to the busy form so a button that is merely
   disabled (the sign button before the end is read) does not shimmer. The
   same look is available by hand through .is-loading. */
form[aria-busy="true"] .btn[disabled][data-turbo-submits-with],
.btn.is-loading {
  opacity: 1;
  cursor: progress;
  pointer-events: none;
  background-image: linear-gradient(100deg, transparent 30%, rgba(255, 255, 255, .45) 50%, transparent 70%);
  background-size: 220% 100%;
  animation: btn-shimmer 1.8s var(--ease-in-out) infinite;
}

@keyframes btn-shimmer {
  from { background-position: 120% 0; }
  to { background-position: -120% 0; }
}

/* Success: the label has already changed (Copied); the colour flips and a
   tick pops in so the eye catches it even without reading. */
.btn.is-success {
  background: var(--ink);
  color: var(--lime);
  border-color: var(--ink);
}

.btn.is-success::before {
  content: "\2713";
  animation: pop-in var(--dur-enter) var(--ease-pop);
}

@keyframes pop-in {
  from { transform: scale(.6); opacity: 0; filter: blur(2px); }
  to { transform: none; opacity: 1; filter: none; }
}

/* A way out, sitting next to the button that saves your work. A second pill
   there is a mis-click: the escape has to be visibly lighter than the commit. */
.form-actions-quiet {
  margin-left: auto;
  font-size: var(--fs-sm);
  color: var(--ink-soft);
}

/* Guidance under a form field, for example the upload size limit. */
.field-hint {
  margin: 6px 0 0;
  font-size: var(--fs-sm);
  color: var(--ink-soft);
}

/* A sentence of context, set against the canvas rather than boxed. */
.note {
  margin: 0 0 var(--s6);
  padding: 10px 16px;
  border-left: 3px solid var(--line);
  font-size: var(--fs-sm);
  color: var(--ink-soft);
}

/* Operational trouble the team must not scroll past. */
.ops-warning {
  border-left: 3px solid var(--bad);
  margin-bottom: 16px;
  font-size: var(--fs-body);
}

details.ops-warning {
  padding: 14px 20px;
}

details.ops-warning summary {
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}

.ops-list {
  margin: 12px 0 0;
  padding-left: 18px;
  line-height: 1.6;
}

.ops-list li + li {
  margin-top: 6px;
}

/* Dashboard: one line per thing. Name, what to do, how long, one button. */
.needs-list {
  display: flex;
  flex-direction: column;
}

.needs-row {
  display: grid;
  grid-template-columns: minmax(110px, 150px) 1fr auto auto;
  align-items: center;
  gap: var(--s4);
  padding: 11px 0;
  border-top: 1px solid var(--line-soft);
  font-size: var(--fs-body);
}

.needs-row:first-child {
  border-top: none;
}

.needs-what {
  color: var(--ink);
}

.needs-age {
  font-size: var(--fs-sm);
  color: var(--ink-soft);
  white-space: nowrap;
}

.needs-row .actions-row {
  justify-content: flex-end;
  flex-wrap: nowrap;
}

/* A folded card: the summary line is the whole story until opened. */
.fold {
  padding: 14px 20px;
}

.fold summary {
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

.fold .plain-list {
  margin-top: 10px;
}

/* Small text actions that must not compete with the one real button. */
.btn-quiet {
  background: transparent;
  border: 1px solid transparent;
  color: var(--ink-soft);
  padding: 6px 10px;
  font-size: 11px;
}

.btn-quiet:hover {
  background: var(--surface-soft);
  border-color: var(--line);
  color: var(--ink);
}

.btn-quiet:disabled {
  cursor: default;
  opacity: 1;
  transform: none;
}

/* Name on the left, one button on the right. */
.plain-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.plain-list li {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 9px 0;
  border-bottom: 1px solid var(--line-soft);
}

.plain-list li:last-child {
  border-bottom: none;
}

.plain-list li > span {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}

.btn-sm {
  padding: 6px 12px;
  font-size: 12px;
}

/* Her portal link, the thing the team pastes into WhatsApp all day. */
.portal-link {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}

.portal-link-url {
  font-size: 12px;
  color: var(--ink-soft);
  overflow-wrap: anywhere;
}

.btn-block {
  width: 100%;
}

form.button_to {
  display: contents;
}

/* Chips. A chip is a state, and its colour says which kind: fine, waiting on
   us, broken, or over. Plain chips are just labels. */

.chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  border-radius: var(--r-pill);
  padding: 4px 11px;
  font-size: var(--fs-label);
  font-weight: 700;
  text-transform: uppercase;
  background: var(--surface-soft);
  border: 1px solid var(--hairline);
  color: var(--ink);
  white-space: nowrap;
  text-decoration: none;
  font-variant-numeric: tabular-nums;
  transition: background var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              transform var(--dur-fast) var(--ease-out);
}

.chip-forest {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--lime);
}

.chip-ok {
  background: var(--ok-soft);
  border-color: rgba(62, 142, 90, .35);
  color: var(--ok-ink);
}

.chip-warn {
  background: var(--warn-soft);
  border-color: rgba(201, 138, 43, .4);
  color: var(--warn-ink);
}

.chip-bad {
  background: var(--bad-soft);
  border-color: rgba(199, 69, 61, .35);
  color: var(--bad-ink);
}

.chip-faint {
  background: transparent;
  border-color: var(--line);
  color: var(--ink-faint);
}

/* Page structure */

.page-head {
  margin-bottom: 24px;
}

.page-head-row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
}

.page-sub {
  margin: 2px 0 0;
  color: var(--ink-soft);
  font-size: var(--fs-body);
}

.filter-bar {
  margin-bottom: 16px;
  display: flex;
  align-items: center;
  gap: var(--s2);
  flex-wrap: wrap;
}

/* Tables */

.hub-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--fs-body);
}

.hub-table th {
  text-align: left;
  font-size: var(--fs-sm);
  font-weight: 700;
  color: var(--ink-soft);
  padding: 10px 12px;
  border-bottom: 1px solid var(--line);
}

.hub-table td {
  padding: 12px;
  border-bottom: 1px solid var(--line-soft);
  vertical-align: middle;
  font-variant-numeric: tabular-nums;
}

/* A column of amounts or counts: right-aligned so the digits stack. */
.hub-table td.num,
.hub-table th.num {
  text-align: right;
}

.hub-table tr:last-child td {
  border-bottom: none;
}

.hub-table .actions-row {
  justify-content: flex-end;
}

.table-link {
  font-weight: 700;
  text-decoration: none;
}

.table-link:hover {
  text-decoration: underline;
}

/* A button that has to sit in a row of links and read as one. Used where the
   action changes something and must be a POST, so an anchor would be wrong,
   but the weight in the row is the same as its neighbours. */
button.table-link {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  font-weight: 700;
  color: var(--ink);
  cursor: pointer;
}

.empty-state {
  padding: 36px 20px;
  text-align: center;
  color: var(--ink-soft);
}

.empty-state p {
  margin: 0;
}

/* Forms */

/* One field to the next. Without this the label of the next field sits six
   pixels under the hint of the last one, and a form reads as one wall rather
   than a sequence of questions. */
.field + .field,
.field + .form-fold,
.field + .field-row,
.field-row + .field {
  margin-top: var(--s5);
}

.field label {
  display: block;
  font-size: 13px;
  font-weight: 700;
  margin-bottom: 6px;
}

.field-row {
  display: grid;
  gap: 14px;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
}

/* The field-to-field spacing above is for stacked fields. Inside a row the
   fields sit side by side, and the same rule would push every field after
   the first 20px down, so the row's labels no longer line up. */
.field-row > .field {
  margin-top: 0;
}

/* A field on one line with the button that submits it: the field takes the
   room, the button takes what it needs. */
.field-grow {
  flex: 1;
  min-width: 180px;
}

/* A set of fields that belong to one purpose, so the form says why it asks. */
.field-group {
  display: grid;
  gap: 14px;
  margin: 0;
  padding: 18px;
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--surface-soft);
}

.field-group legend {
  padding: 0 8px;
  font-size: 13px;
  font-weight: 700;
  color: var(--ink);
}

.field-group > .field-hint {
  margin: 0;
}

.input {
  width: 100%;
  box-sizing: border-box;
  padding: 11px 15px;
  border-radius: 14px;
  border: 1px solid var(--control-line);
  background: var(--control-bg);
  font-family: "Quicksand", sans-serif;
  font-weight: 500;
  font-size: var(--fs-body);
  color: var(--ink);
  transition: background var(--dur-base) var(--ease-out),
              border-color var(--dur-base) var(--ease-out),
              box-shadow var(--dur-base) var(--ease-out);
}

.input::placeholder {
  color: var(--ink-mute);
  opacity: 1;
}

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

/* The field lifts to white and grows a soft ring on focus. The ring replaces
   the two-pixel outline the global rule would draw, so the outline is moved
   to the ring colour at zero width rather than switched off. */
.input:focus {
  outline: 2px solid transparent;
  outline-offset: 1px;
  background: var(--control-bg-focus);
  border-color: var(--control-line-focus);
  box-shadow: var(--focus-ring);
  border-radius: 14px;
}

/* Native selects keep the OS menu, which is the right thing on a phone, and
   get the hub's chevron and ring so they read as the same control. */
select.input {
  appearance: none;
  -webkit-appearance: none;
  padding-right: 38px;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'><path d='M1 1l5 5 5-5' fill='none' stroke='%230C4129' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/></svg>");
  background-repeat: no-repeat;
  background-position: right 14px center;
  cursor: pointer;
}

select.input.input-sm {
  padding-right: 30px;
  background-position: right 10px center;
}

.input-inline {
  width: auto;
  min-width: 200px;
}

.form-actions {
  display: flex;
  align-items: center;
  gap: 12px;
  padding-top: 6px;
  flex-wrap: wrap;
}

/* The record on a creator's page: who she is, what was agreed, what has
   happened. It used to be three bordered cards, which made one person read as
   seven stacked containers. A border says "separate object you act on"; this
   is reference, so it sits on the page under a rule and spends nothing. */
.facts {
  display: grid;
  gap: var(--s6) var(--s8);
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  margin-top: var(--s5);
}

.facts-head {
  font-size: var(--fs-sm);
  font-weight: 700;
  color: var(--ink-soft);
  text-transform: uppercase;
  margin: 0 0 12px;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--line);
}

.facts-block .facts-head {
  margin-bottom: 0;
}

/* Detail lists */

.detail-list {
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.detail-list > div {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 14px;
  border-bottom: 1px solid var(--line-soft);
  padding-bottom: 10px;
}

.detail-list > div:last-child {
  border-bottom: none;
  padding-bottom: 0;
}

.detail-list dt {
  font-size: var(--fs-sm);
  font-weight: 700;
  color: var(--ink-soft);
  flex-shrink: 0;
}

.detail-list dd {
  margin: 0;
  font-size: var(--fs-body);
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* Timeline */

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

.timeline li {
  padding: 11px 0;
  border-bottom: 1px solid var(--line-soft);
  font-size: 13px;
}

.timeline li:last-child {
  border-bottom: none;
}

.timeline-time {
  float: right;
  color: var(--ink-soft);
  font-size: 12px;
  font-variant-numeric: tabular-nums;
}

/* What a creator actually wrote, set apart from the machine's own entries so it
   reads as a person talking rather than another logged event. */
.timeline-quote {
  margin: 8px 0 2px;
  padding: 10px 14px;
  border-left: 3px solid var(--ink);
  background: var(--surface-soft);
  border-radius: 0 10px 10px 0;
  font-size: 13.5px;
  line-height: 1.7;
  white-space: pre-wrap;
}

/* Flash */

.flash {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 12px 14px 12px 18px;
  border-radius: var(--r);
  margin: 0 0 18px;
  font-size: var(--fs-body);
  font-weight: 600;
  background: var(--surface-soft);
  border: 1px solid var(--glass-border);
}

.flash-body {
  flex: 1;
  line-height: 1.5;
}

.flash-dismiss {
  flex-shrink: 0;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  border: none;
  background: rgba(0, 0, 0, .06);
  color: inherit;
  font-size: 16px;
  line-height: 1;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out), transform var(--dur-fast) var(--ease-out);
}

.flash-dismiss:hover {
  background: rgba(0, 0, 0, .12);
}

.flash-dismiss:active {
  transform: scale(.98);
}

.flash-alert {
  background: rgba(255, 226, 226, .75);
  border-color: rgba(199, 69, 61, .25);
  color: #7C2222;
}

.flash-notice {
  background: rgba(232, 250, 220, .75);
  border-color: rgba(12, 65, 41, .2);
}

/* The flash as a toast. The layout renders one fixed stack, top right on a
   laptop and above the home indicator on a phone, and each notice rises into
   it, counts down along a hairline, and drops out. Inline .flash blocks in
   forms (validation errors) are not in the stack and stay static. */
.flash-stack {
  position: fixed;
  top: var(--s4);
  right: var(--s4);
  z-index: var(--z-toast);
  display: grid;
  gap: var(--s2);
  width: min(420px, calc(100vw - 32px));
  pointer-events: none;
}

.flash-stack .flash {
  position: relative;
  pointer-events: auto;
  margin: 0;
  border: 0;
  overflow: hidden;
  background: var(--surface-raised);
  box-shadow: var(--shadow-menu);
  animation: fade-rise var(--dur-enter) var(--ease-out);
}

.flash-stack .flash-notice {
  background: var(--surface-raised);
}

.flash-stack .flash-alert {
  background: #FFF4F4;
}

.flash-stack .flash.is-leaving {
  animation: fade-drop var(--dur-base) var(--ease-in) forwards;
}

/* The countdown: a hairline that shrinks from the left over the toast's
   life. The controller sets --toast-life (longer when there is an Undo) and
   pauses it under the pointer or keyboard focus. */
.flash-stack .flash::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: 0;
  height: 2px;
  width: 100%;
  background: var(--ink);
  transform-origin: left;
  animation: toast-life var(--toast-life, 6s) linear forwards;
}

.flash-stack .flash.is-paused::after,
.flash-stack .flash:hover::after {
  animation-play-state: paused;
}

.flash-stack .flash-alert::after,
.flash-stack .flash.is-sticky::after {
  display: none;
}

.flash-stack .flash form .btn {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--lime);
}

@keyframes fade-rise {
  from { opacity: 0; transform: translateY(6px); filter: blur(2px); }
  to { opacity: 1; transform: none; filter: none; }
}

@keyframes fade-drop {
  to { opacity: 0; transform: translateY(-4px); filter: blur(2px); }
}

@keyframes toast-life {
  to { transform: scaleX(0); }
}

@media (max-width: 760px) {
  .flash-stack {
    top: auto;
    bottom: calc(var(--s4) + var(--safe-b));
    left: var(--s4);
    right: var(--s4);
    width: auto;
  }

  @keyframes fade-drop {
    to { opacity: 0; transform: translateY(4px); filter: blur(2px); }
  }
}

/* Analytics: plain CSS bars rather than a charting library. */

.funnel {
  display: grid;
  gap: 8px;
  margin-bottom: 14px;
}

.funnel-row {
  display: grid;
  grid-template-columns: 78px 1fr 84px;
  align-items: center;
  gap: 12px;
}

.funnel-label {
  font-size: var(--fs-sm);
  font-weight: 700;
}

.funnel-track {
  height: 22px;
  border-radius: var(--r-pill);
  background: rgba(12, 65, 41, .08);
  overflow: hidden;
}

.funnel-bar {
  display: block;
  height: 100%;
  min-width: 2px;
  border-radius: var(--r-pill);
  background: var(--ink);
}

.funnel-value {
  font-size: var(--fs-body);
  font-weight: 700;
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.funnel-value small {
  font-weight: 500;
  color: var(--ink-soft);
}

.bars {
  display: flex;
  align-items: flex-end;
  gap: 10px;
  height: 160px;
  padding-top: 8px;
}

.bar-col {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-end;
  height: 100%;
  gap: 6px;
}

.bar {
  width: 100%;
  min-height: 3px;
  border-radius: 8px 8px 0 0;
  background: var(--ink);
}

.bar-value {
  font-size: 13px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.bar-label {
  font-size: var(--fs-label);
  font-weight: 700;
  color: var(--ink-soft);
}

/* Pipeline board */

.pipe-board {
  display: flex;
  gap: 12px;
  overflow-x: auto;
  padding-bottom: 8px;
  align-items: flex-start;
}

.pipe-col {
  background: var(--surface-soft);
  border: 1px solid var(--line);
  border-radius: var(--r);
  padding: 12px 10px;
  min-width: 190px;
  flex: 1;
}

.pipe-col h3 {
  font-size: var(--fs-sm);
  font-weight: 700;
  color: var(--ink-soft);
  display: flex;
  justify-content: space-between;
  padding: 0 4px 10px;
  margin: 0;
}

.pipe-card {
  display: block;
  background: var(--surface-solid);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  padding: 10px 12px;
  margin-bottom: 8px;
}

.pipe-card-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 8px;
}

.pipe-card-name {
  font-weight: 700;
  font-size: 13px;
  text-decoration: none;
}

.pipe-card-name:hover {
  text-decoration: underline;
}

.pipe-card .days {
  font-size: var(--fs-label);
  color: var(--ink-soft);
  flex-shrink: 0;
}

.pipe-card .days.hot {
  color: var(--bad);
  font-weight: 700;
}

.pipe-card-next {
  margin-top: 4px;
  font-size: var(--fs-sm);
  color: var(--ink-soft);
}

.pipe-card-action {
  margin-top: 8px;
}

.pipe-card-action:empty {
  display: none;
}

.pipe-card-action .btn-sm {
  padding: 5px 11px;
  font-size: 11px;
}

.pipe-closed {
  margin-top: 16px;
}

.pipe-closed summary {
  cursor: pointer;
  font-size: var(--fs-sm);
  font-weight: 700;
  color: var(--ink-soft);
}

.filter-check {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: var(--fs-sm);
  font-weight: 600;
}

.pipe-empty {
  font-size: var(--fs-label);
  color: var(--ink-soft);
  padding: 4px;
  margin: 0;
}

/* Creator portal. The public page has no sidebar and one column; it shares
   every token above so a change here and a change in the hub stay in step. */

.portal-main {
  max-width: 640px;
  margin: 0 auto;
  padding: 40px 18px 80px;
}

.portal-logo svg {
  width: 92px;
  height: auto;
  display: block;
  margin: 0 auto 8px;
}

.portal-hello {
  text-align: center;
  margin: 10px 0 4px;
}

/* Five steps across the top of her page. A flex row with no wrap fitted at
   400px and pushed the whole page sideways below about 360px, which is an
   ordinary Android and an iPhone SE. It wraps now, and the gap comes from the
   same scale as everything else rather than being tuned to just fit. */
.portal-steps {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--s3) var(--s4);
  padding: 18px 0 22px;
  margin: 0;
}

.portal-steps li {
  font-size: var(--fs-label);
  font-weight: 700;
  text-transform: uppercase;
  color: rgba(12, 65, 41, .85);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
}

.portal-steps li span {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(255, 255, 255, .75);
  border: 1.5px solid rgba(12, 65, 41, .15);
  font-size: 13px;
}

.portal-steps li.done span {
  background: var(--ink);
  color: var(--lime);
  border-color: var(--ink);
}

.portal-steps li.current span {
  background: var(--lime);
  border-color: var(--ink);
  color: #000;
}

.portal-steps li.done,
.portal-steps li.current {
  color: var(--ink);
}

.portal-card {
  margin-top: 16px;
}

.portal-contract {
  background: rgba(255, 255, 255, .8);
  border: 1px solid var(--line);
  border-radius: 14px;
  padding: 16px 18px;
  font-size: 13px;
  line-height: 1.7;
  margin: 14px 0;
  max-height: 60vh;
  overflow-y: auto;
}

.portal-contract-full {
  max-height: none;
}

.portal-script {
  background: rgba(255, 255, 255, .8);
  border: 1px solid var(--line);
  border-radius: 14px;
  padding: 16px 18px;
  font-size: 13.5px;
  line-height: 1.75;
}

/* The signing block, set apart from the reading above it. */
.portal-sign {
  margin-top: 20px;
  padding: 20px;
  border: 1.5px solid rgba(12, 65, 41, .18);
  border-radius: var(--r);
  background: rgba(255, 255, 255, .55);
}

.portal-sign h3 {
  margin: 0 0 12px;
  font-size: 17px;
}

.portal-sign-terms {
  margin-bottom: 16px;
}

.portal-sign-note {
  margin: 14px 0 0;
  font-size: 12px;
  line-height: 1.7;
  color: var(--ink-soft);
}

/* Signing area. Height in CSS pixels; the canvas is scaled to the device
   pixel ratio in JS so the ink is not blurry on a phone. */
.sign-pad {
  position: relative;
  height: clamp(140px, 32vw, 200px);
  border: 1px dashed rgba(12, 65, 41, .35);
  border-radius: 14px;
  background: #FFFFFF;
  overflow: hidden;
}

.sign-pad canvas {
  display: block;
  width: 100%;
  height: 100%;
  touch-action: none;
  cursor: crosshair;
}

.sign-pad-hint {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  font-size: 13px;
  color: var(--ink-mute);
}

.sign-pad-actions {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  margin-top: 8px;
}

.portal-check {
  margin: 0 0 16px;
  padding-left: 20px;
  font-size: 13.5px;
  line-height: 1.9;
  color: var(--ink-soft);
}

.portal-choice {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.portal-choice label {
  font-weight: 500;
  font-size: var(--fs-body);
}

/* Both fieldsets in the portal: the upload choice and the signature pad. They
   group controls so a screen reader announces the question with each option,
   and they arrive with a border and padding that belong to 1997. */
.sign-pad-group,
.portal-choice-group {
  border: 0;
  padding: 0;
  margin: 0;
  min-width: 0;
}

.sign-pad-legend,
.portal-choice-label {
  display: block;
  font-size: 13px;
  font-weight: 700;
  margin-bottom: 8px;
  padding: 0;
}

.portal-step-note {
  margin: -8px 0 4px;
  font-size: var(--fs-sm);
}

.portal-details {
  margin-top: 14px;
}

.portal-details summary {
  cursor: pointer;
  font-size: 13px;
  font-weight: 700;
  padding: 8px 0;
}

.portal-upload-status {
  font-size: 13px;
  line-height: 1.6;
  margin: 10px 0 0;
  color: var(--ink-soft);
}

.portal-upload-status.upload-problem {
  color: #8A1C1C;
  font-weight: 600;
}

.portal-progress {
  height: 10px;
  border-radius: var(--r-pill);
  background: rgba(12, 65, 41, .12);
  overflow: hidden;
  margin-top: 12px;
}

.portal-progress span {
  display: block;
  height: 100%;
  width: 0;
  background: var(--ink);
  transition: width var(--dur-base) var(--ease-out);
}

/* Who we are, our trade licence, and what happens to her details. This is
   what somebody reads when deciding whether a page asking for an ID number is
   real, so it is set at the body size rather than two steps below it. */
.portal-foot {
  max-width: 640px;
  margin: 0 auto;
  padding: 0 18px 56px;
  text-align: center;
  font-size: var(--fs-sm);
  line-height: 1.8;
  color: var(--ink-soft);
}

.portal-foot a {
  color: var(--ink);
}

/* Secondary text on the lavender gradient needs more ink than on white:
   .78 of forest lands just under AA there, .85 clears it. */
.portal-page .page-sub,
.portal-page .field-hint,
.portal-page .portal-foot,
.portal-page .portal-sign-note,
.portal-page .portal-timeline-when,
.portal-page .portal-upload-status {
  color: rgba(12, 65, 41, .85);
}

/* 16px stops iOS Safari zooming the page the moment she taps a field. */
.portal-main .input {
  font-size: 16px;
}

.portal-main .btn {
  min-height: 48px;
}

/* Modals: native dialog, styled as a card on a laptop and a bottom sheet on a
   phone. It blurs in rather than appearing, and the backdrop fades with it.
   @starting-style gives the entrance; the discrete transitions on display and
   overlay keep the box on screen while the exit plays. */

.hub-dialog {
  border: 0;
  border-radius: var(--r-lg);
  padding: 0;
  margin: auto;
  width: min(520px, calc(100vw - 32px));
  background: var(--surface-sheet);
  color: var(--ink);
  box-shadow: var(--shadow-sheet);
  overscroll-behavior: contain;
  will-change: transform;
  opacity: 0;
  transform: translateY(8px) scale(.98);
  filter: blur(2px);
  transition: opacity var(--dur-enter) var(--ease-out),
              transform var(--dur-enter) var(--ease-out),
              filter var(--dur-enter) var(--ease-out),
              overlay var(--dur-enter) allow-discrete,
              display var(--dur-enter) allow-discrete;
}

.hub-dialog[open] {
  opacity: 1;
  transform: none;
  filter: none;
}

@starting-style {
  .hub-dialog[open] {
    opacity: 0;
    transform: translateY(8px) scale(.98);
    filter: blur(2px);
  }
}

.hub-dialog::backdrop {
  background: rgba(12, 65, 41, 0);
  -webkit-backdrop-filter: blur(0);
  backdrop-filter: blur(0);
  transition: background var(--dur-enter) var(--ease-out),
              backdrop-filter var(--dur-enter) var(--ease-out),
              overlay var(--dur-enter) allow-discrete,
              display var(--dur-enter) allow-discrete;
}

.hub-dialog[open]::backdrop {
  background: rgba(12, 65, 41, .35);
  -webkit-backdrop-filter: blur(4px);
  backdrop-filter: blur(4px);
}

@starting-style {
  .hub-dialog[open]::backdrop {
    background: rgba(12, 65, 41, 0);
    -webkit-backdrop-filter: blur(0);
    backdrop-filter: blur(0);
  }
}

/* iOS Safari scrolls the page behind a modal dialog unless told not to. */
html.has-dialog {
  overflow: hidden;
}

.hub-dialog-body {
  padding: 22px 24px 24px;
}

.hub-dialog .card-head-row {
  margin-bottom: 8px;
}

/* The close control is a 44px target with a 28px circle drawn inside it, so
   the thumb has room and the eye still sees a small quiet button. */
.sheet-close {
  flex-shrink: 0;
  width: var(--tap);
  height: var(--tap);
  margin: -8px -10px -8px 0;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: transparent;
  color: inherit;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: transform var(--dur-fast) var(--ease-out);
}

.sheet-close span {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: rgba(0, 0, 0, .06);
  font-size: 16px;
  line-height: 28px;
  text-align: center;
  transition: background var(--dur-fast) var(--ease-out);
}

.sheet-close:hover span {
  background: rgba(0, 0, 0, .12);
}

.sheet-close:active {
  transform: scale(.98);
}

/* The grab bar: drawn on every size, listened to on a phone. */
.sheet-handle {
  display: none;
}

/* A way out next to the way in, for the phone where the X is far from the
   thumb. The forms inside the sheet are their own; this sits under them. */
.sheet-cancel {
  display: none;
}

/* Collab cards on the creator page. */

.collab-card {
  padding: 20px 24px;
}

.collab-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
}

.collab-next {
  margin: 8px 0 0;
  font-size: var(--fs-body);
}

.collab-section {
  margin-top: 16px;
  padding-top: 14px;
  border-top: 1px solid var(--line-soft);
}

.collab-terms summary {
  cursor: pointer;
  display: flex;
  align-items: baseline;
  gap: 10px;
  flex-wrap: wrap;
}

.collab-terms summary .page-sub {
  font-size: var(--fs-sm);
}

/* Rare actions behind one button. */
.more-menu {
  position: relative;
}

.more-menu summary {
  list-style: none;
}

.more-menu summary::-webkit-details-marker {
  display: none;
}

.more-menu-body {
  position: absolute;
  right: 0;
  top: calc(100% + 6px);
  z-index: var(--z-menu);
  min-width: 200px;
  padding: 6px;
  border-radius: var(--r-sm);
  background: var(--surface-raised);
  border: 0;
  box-shadow: var(--shadow-menu);
  display: flex;
  flex-direction: column;
  gap: 2px;
  animation: fade-rise var(--dur-base) var(--ease-out);
}

.more-menu-item {
  display: block;
  width: 100%;
  text-align: left;
  padding: 9px 12px;
  border: none;
  border-radius: 8px;
  background: transparent;
  font-family: "Quicksand", sans-serif;
  font-weight: 600;
  font-size: 13px;
  color: var(--ink);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out), transform var(--dur-fast) var(--ease-out);
}

.more-menu-item:hover {
  background: rgba(12, 65, 41, .06);
}

.more-menu-item:active {
  transform: scale(.98);
}

.more-menu-item-danger {
  color: var(--bad);
}

.more-menu-item-danger:hover {
  background: var(--bad-soft);
}

/* The video where the decision is made. */
.video-review-player {
  display: block;
  width: 100%;
  max-width: 420px;
  max-height: 480px;
  border-radius: var(--r-sm);
  background: #000;
  margin-bottom: 8px;
}

.input-sm {
  padding: 6px 12px;
  font-size: var(--fs-sm);
  min-width: 0;
}

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

.content-children {
  margin-top: 8px;
}

.content-children summary {
  cursor: pointer;
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--ink-soft);
}

.content-children .plain-list {
  margin-top: 6px;
  font-size: var(--fs-sm);
}

.bank-details summary {
  cursor: pointer;
  list-style: none;
}

.bank-details summary::-webkit-details-marker {
  display: none;
}

.bank-details-body {
  margin-top: 8px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  align-items: flex-start;
  font-size: var(--fs-sm);
}

.bank-iban {
  font-size: 13px;
  background: var(--surface-soft);
  padding: 4px 8px;
  border-radius: 8px;
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

/* Bulk: the checkbox column and the bar that appears once something is ticked. */
.th-check {
  width: 28px;
  padding-right: 0;
}

.th-check input {
  width: 16px;
  height: 16px;
  accent-color: var(--ink);
  cursor: pointer;
}

.bulk-bar[hidden] {
  display: none;
}

.bulk-bar {
  display: flex;
  align-items: center;
  gap: var(--s3);
  flex-wrap: wrap;
  padding: 10px 14px;
  margin: 4px 0 8px;
  border-radius: var(--r-sm);
  background: rgba(239, 248, 96, .35);
  border: 1px solid rgba(0, 0, 0, .12);
  font-size: var(--fs-sm);
}

/* Mobile */

/* Phone only. Without a rule outside the breakpoint the Menu button renders
   as a bare browser button next to the logo on every desktop page. */
.hub-nav-toggle {
  display: none;
}

@media (max-width: 840px) {
  .hub-shell {
    flex-direction: column;
  }

  .hub-sidebar {
    width: 100%;
    height: auto;
    position: static;
    border-right: none;
    border-bottom: 1px solid var(--glass-border);
  }

  .hub-main {
    padding: 20px 16px 48px;
  }

  /* Eighteen tables, the widest nine columns across, and on a phone every one
     of them scrolled sideways with nothing to say so: no shadow at the edge,
     no sticky name, so you swiped right and lost track of whose row you were
     reading. The weekly report is the worst case - the creative is column two
     and Remove is column nine.

     Turning all eighteen into stacked cards would mean a data-label on every
     cell in the app. This does the two things that actually hurt: the row
     keeps its name while you scroll, and the edge says there is more. */
  .hub-table {
    display: block;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    /* A fade at the right edge, painted only while there is somewhere to
       scroll to, so a table that fits looks like a table that fits. */
    background:
      linear-gradient(to right, var(--surface-solid) 30%, rgba(255, 255, 255, 0)) left center / 28px 100% no-repeat,
      linear-gradient(to left, var(--surface-solid) 30%, rgba(255, 255, 255, 0)) right center / 28px 100% no-repeat,
      radial-gradient(farthest-side at 0 50%, rgba(12, 65, 41, .12), rgba(12, 65, 41, 0)) left center / 14px 100% no-repeat,
      radial-gradient(farthest-side at 100% 50%, rgba(12, 65, 41, .12), rgba(12, 65, 41, 0)) right center / 14px 100% no-repeat;
    background-attachment: local, local, scroll, scroll;
  }

  /* The first cell carries the name of the row on almost every table here, so
     it stays put while the numbers scroll under your thumb. Where a checkbox
     column comes first, the name is the second cell and that one sticks
     instead - pinning a tick box would keep the useless half. */
  .hub-table th:first-child,
  .hub-table td:first-child,
  .hub-table .th-check + th,
  .hub-table .th-check + td {
    position: sticky;
    left: 0;
    background: var(--surface-solid);
  }

  .hub-table .th-check + th,
  .hub-table .th-check + td {
    left: 40px;
  }

  .card-plain .hub-table th:first-child,
  .card-plain .hub-table td:first-child,
  .card-plain .hub-table .th-check + th,
  .card-plain .hub-table .th-check + td {
    background: var(--surface-soft);
  }

  /* The nav is fourteen stacked pills on a phone, roughly 750px of it, and it
     sits above the content on every single page load. Collapsed behind the
     brand row instead, so the page starts with the page. */
  .hub-nav {
    display: none;
  }

  .hub-shell.nav-open .hub-nav {
    display: flex;
    margin-top: 12px;
  }

  .hub-nav-toggle {
    margin-left: auto;
    display: inline-flex;
    align-items: center;
    gap: 8px;
    min-height: 44px;
    padding: 10px 18px;
    border: 1px solid var(--glass-border);
    border-radius: var(--r-pill);
    background: var(--glass);
    font-family: "Quicksand", sans-serif;
    font-weight: 700;
    font-size: 14px;
    color: var(--ink);
    cursor: pointer;
  }

  .hub-sidebar-footer {
    margin-top: 12px;
  }

  /* Thumbs, not cursors. 44px is the iOS minimum. */
  .btn,
  .btn-sm {
    min-height: 44px;
    padding: 12px 20px;
    font-size: 14px;
  }

  /* Anything under 16px makes iOS Safari zoom the page on focus. */
  .input {
    font-size: 16px;
  }

  /* On a phone the contract reads inline with the page. A scroll box inside
     a scrolling page is where a thumb gets stuck. */
  .portal-contract {
    max-height: none;
  }

  .portal-steps {
    gap: 10px;
  }

  .portal-steps li {
    font-size: 10px;
  }
}

/* Folds open by growing rather than snapping. The markup wraps the content
   after <summary> in .reveal > .reveal-inner, and the grid row grows from
   nothing to its content height on the house curve. Browsers that have the
   details-content pseudo also animate the close; the others close at once,
   which is the lesser of the two snaps. */
details.fold,
details.form-fold,
details.collab-terms,
details.bank-details,
details.content-children,
details.portal-details,
details.hub-nav-more,
details.ops-warning {
  interpolate-size: allow-keywords;
}

.reveal {
  display: grid;
  grid-template-rows: 0fr;
  opacity: 0;
  transition: grid-template-rows var(--dur-enter) var(--ease-out), opacity var(--dur-base) var(--ease-out);
}

.reveal-inner {
  min-height: 0;
  overflow: hidden;
}

details[open] > .reveal {
  grid-template-rows: 1fr;
  opacity: 1;
}

@supports selector(details::details-content) {
  :is(details.fold, details.form-fold, details.collab-terms, details.bank-details, details.content-children, details.portal-details, details.hub-nav-more, details.ops-warning)::details-content {
    display: block;
    height: 0;
    overflow: clip;
    transition: content-visibility var(--dur-enter) allow-discrete, height var(--dur-enter) var(--ease-out);
  }

  :is(details.fold, details.form-fold, details.collab-terms, details.bank-details, details.content-children, details.portal-details, details.hub-nav-more, details.ops-warning)[open]::details-content {
    height: auto;
  }
}

/* Everything that is not the daily five lives behind one "More". */
.hub-nav-more {
  margin-top: 6px;
}

.hub-nav-more summary {
  cursor: pointer;
  list-style: none;
  color: var(--ink-soft);
}

.hub-nav-more summary::-webkit-details-marker {
  display: none;
}

.hub-nav-more summary::after {
  content: "›";
  font-size: 18px;
  line-height: 1;
  transition: transform var(--dur-base) var(--ease-out);
}

.hub-nav-more[open] summary::after {
  transform: rotate(90deg);
}

.hub-nav-more[open] summary {
  margin-bottom: 2px;
}

.hub-nav-more .hub-nav-link {
  padding-left: 22px;
  font-weight: 600;
}

/* Undo in place of "Are you sure?": the row says what happened and offers the way back. */
.needs-row.is-undone {
  color: var(--ink-soft);
  background: var(--surface-soft);
}

.flash form {
  display: inline;
  margin-left: 10px;
}

/* Portal forms: room between questions, so a phone screen reads as a list
   of four things rather than one block of labels and boxes. */
.portal-form {
  display: grid;
  gap: 18px;
  margin-top: 18px;
}

.portal-form .field-hint {
  margin: 6px 0 0;
}

.portal-form textarea.input {
  resize: vertical;
  min-height: 64px;
}

/* Add creator: the four that matter, the rest folded. */
.form-fold {
  margin-top: 6px;
}

.form-fold summary {
  cursor: pointer;
  font-size: 13px;
  font-weight: 700;
  color: var(--ink-soft);
  padding: 8px 0;
}

.form-fold[open] summary {
  margin-bottom: 10px;
}

.form-fold > .stack-sm,
.form-fold .reveal-inner > .stack-sm {
  display: grid;
  gap: 14px;
}

/* The error next to the field it is about. */
.field-error {
  margin: 6px 0 0;
  font-size: 13px;
  font-weight: 600;
  color: var(--bad);
  animation: fade-rise var(--dur-enter) var(--ease-out);
}

.input.is-invalid,
.input[aria-invalid="true"] {
  border-color: var(--bad);
  box-shadow: 0 0 0 3px var(--bad-soft);
}

/* A short sideways nudge when the broken field takes focus: the same gesture
   as a head shake, over before it is annoying. */
.input.is-invalid:focus,
.input[aria-invalid="true"]:focus {
  animation: nudge var(--dur-slow) var(--ease-out);
}

@keyframes nudge {
  20% { transform: translateX(-3px); }
  40% { transform: translateX(3px); }
  60% { transform: translateX(-2px); }
  80% { transform: translateX(2px); }
}

/* A sheet with a video in it needs the room. */
.hub-dialog-wide {
  width: min(760px, 94vw);
}

.hub-dialog-wide .video-review-player {
  width: 100%;
  max-height: 52vh;
}

/* Nothing to do is a state worth a sentence, not a blank. */
.all-caught-up p:first-child {
  font-size: 18px;
  margin-bottom: 4px;
}

/* What happens next on her page: a vertical line of dots, done ones filled. */
.portal-timeline {
  list-style: none;
  margin: 12px 0 0;
  padding: 0;
  display: grid;
  gap: 0;
}

.portal-timeline li {
  position: relative;
  display: grid;
  grid-template-columns: 22px 1fr;
  grid-template-areas: "dot label" "dot when";
  column-gap: 10px;
  padding: 0 0 16px;
  color: rgba(12, 65, 41, .85);
}

.portal-timeline li::before {
  content: "";
  position: absolute;
  left: 7px;
  top: 16px;
  bottom: -2px;
  width: 2px;
  background: rgba(12, 65, 41, .12);
}

.portal-timeline li:last-child::before {
  display: none;
}

.portal-timeline-dot {
  grid-area: dot;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  margin-top: 2px;
  background: rgba(255, 255, 255, .8);
  border: 2px solid rgba(12, 65, 41, .2);
  box-sizing: border-box;
}

.portal-timeline li.done .portal-timeline-dot {
  background: var(--ink);
  border-color: var(--ink);
}

.portal-timeline li.current .portal-timeline-dot {
  background: var(--lime);
  border-color: var(--ink);
}

.portal-timeline li.done,
.portal-timeline li.current {
  color: var(--ink);
}

.portal-timeline-label {
  grid-area: label;
  font-weight: 700;
  font-size: 14px;
}

.portal-timeline-when {
  grid-area: when;
  font-size: 13px;
  color: var(--ink-soft);
}

/* Today as a sentence, not a cockpit. */
.today-line {
  margin: 0;
  font-size: 17px;
  font-weight: 600;
  color: var(--ink);
}

/* Roster: a list of people. Avatar, name, one sentence, one button. */
.segments {
  position: relative;
  isolation: isolate;
  display: inline-flex;
  padding: 3px;
  border-radius: var(--r-pill);
  background: var(--surface-soft);
  border: 1px solid var(--line);
  gap: 2px;
}

/* The dark pill slides from the old choice to the new one instead of
   swapping. It only takes over once the segments controller has measured
   the active segment (.has-indicator); until then the segment paints its
   own background, so nothing depends on JavaScript to look right. */
.segments::before {
  content: "";
  position: absolute;
  z-index: -1;
  top: 3px;
  bottom: 3px;
  left: var(--seg-x, 3px);
  width: var(--seg-w, 0);
  border-radius: var(--r-pill);
  background: var(--ink);
  transition: left var(--dur-enter) var(--ease-in-out), width var(--dur-enter) var(--ease-in-out);
}

.segment {
  padding: 7px 14px;
  border-radius: var(--r-pill);
  font-size: 13px;
  font-weight: 700;
  color: var(--ink-soft);
  text-decoration: none;
  white-space: nowrap;
  transition: color var(--dur-base) var(--ease-out), transform var(--dur-fast) var(--ease-out);
}

.segment:active {
  transform: scale(.98);
}

.segments.has-indicator .segment.is-active,
.segments.has-indicator .segment:has(input:checked) {
  background: transparent;
}

/* The same control as a form field: a radio pair rather than a dropdown of
   two. The input is hidden from sight but not from the keyboard or a screen
   reader, and :has lets the label answer to its own input rather than needing
   a class written by the server on every render. */
.segment:has(input) {
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  min-height: 34px;
}

.segment:has(input:checked) {
  background: var(--ink);
  color: var(--lime);
}

.segment:has(input:focus-visible) {
  outline: 2px solid var(--ink);
  outline-offset: 2px;
}

/* Off screen, still focusable, still announced. display:none would take it
   out of the tab order and silence it. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

.segment.is-active {
  background: var(--ink);
  color: #fff;
}

.people-list {
  display: flex;
  flex-direction: column;
}

.people-head {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 16px;
  border-bottom: 1px solid var(--line);
}

.people-check {
  width: 22px;
  display: flex;
  align-items: center;
}

.person-row {
  display: grid;
  grid-template-columns: 22px 36px minmax(160px, 1.2fr) minmax(200px, 1.6fr) auto;
  gap: 14px;
  align-items: center;
  padding: 12px 16px;
  min-height: 64px;
  border-bottom: 1px solid var(--line);
}

.person-row:last-child {
  border-bottom: none;
}

.person-row {
  transition: background var(--dur-fast) var(--ease-out), transform var(--dur-fast) var(--ease-out);
}

.person-row:hover {
  background: var(--surface-soft);
}

.person-row:active {
  transform: scale(.995);
}

.avatar {
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background: var(--lavender);
  color: var(--ink);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  font-size: 13px;
}

.person-name {
  display: flex;
  align-items: center;
  gap: 8px;
  font-weight: 700;
  font-size: 15px;
}

.person-meta {
  font-size: 13px;
}

.person-state {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  font-size: 14px;
  color: var(--ink-soft);
}

.person-action {
  justify-self: end;
}

@media (max-width: 760px) {
  /* Label over value rather than beside it. .detail-list dt never shrinks, so
     a long label left a two-word column for a whole sentence. */
  .portal-main .detail-list > div {
    display: grid;
    gap: 2px;
  }

  .portal-main .detail-list dd {
    text-align: left;
  }

  .person-row {
    grid-template-columns: 22px 36px 1fr;
    grid-template-rows: auto auto auto;
  }
  .person-state,
  .person-action {
    grid-column: 3;
    justify-self: start;
  }
}

/* The reading bar on the agreement: where she is, and the way to the signature. */
.portal-readbar {
  position: sticky;
  bottom: calc(12px + var(--safe-b));
  z-index: var(--z-sticky);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-top: 16px;
  padding: 14px 18px;
  border-radius: var(--r-pill);
  background: var(--ink);
  color: #fff;
  font-weight: 700;
  font-size: 14px;
  text-decoration: none;
  box-shadow: var(--shadow-menu);
  transition: background var(--dur-base) var(--ease-out), color var(--dur-base) var(--ease-out);
}

.portal-readbar.is-ready {
  background: var(--lime);
  color: var(--lime-ink);
}

.portal-readbar-arrow {
  font-size: 18px;
}

/* Her clips, as a receipt: a tick, the name, the size. And the team's copy of
   the same list on the editing queue, numbered so "clip 3" means something on
   a call. */
.portal-clips,
.clip-list {
  list-style: none;
  margin: 12px 0 0;
  padding: 0;
}

.portal-clips li,
.clip-list li {
  display: flex;
  align-items: baseline;
  gap: 10px;
  padding: 8px 0;
  border-top: 1px solid var(--line);
  font-size: var(--fs-sm);
  min-width: 0;
}

.portal-clips li:first-child,
.clip-list li:first-child {
  border-top: 0;
}

.portal-clip-check {
  color: var(--ok);
  font-weight: 700;
  flex: 0 0 auto;
}

.clip-index {
  flex: 0 0 22px;
  height: 22px;
  border-radius: var(--r-pill);
  background: rgba(12, 65, 41, .08);
  font-size: 12px;
  font-weight: 700;
  text-align: center;
  line-height: 22px;
  align-self: center;
}

.portal-clip-name,
.clip-name {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-weight: 600;
}

.portal-clip-meta {
  flex: 0 0 auto;
  color: var(--ink-soft);
  font-size: 12px;
}

.portal-poster {
  display: block;
  width: 100%;
  max-width: 320px;
  border-radius: 14px;
  margin: 10px 0;
  border: 1px solid var(--line);
}

/* Settings live behind one entry; help behind a question mark. */
.hub-nav-settings summary {
  color: var(--ink-soft);
}

.page-help {
  position: relative;
}

.page-help summary {
  list-style: none;
  cursor: pointer;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  border: 1px solid var(--line);
  background: var(--surface-soft);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  color: var(--ink-soft);
}

.page-help summary::-webkit-details-marker {
  display: none;
}

.page-help[open] summary {
  background: var(--ink);
  color: #fff;
}

.page-help-body {
  position: absolute;
  right: 0;
  top: 38px;
  z-index: var(--z-menu);
  width: min(360px, 80vw);
  padding: 14px 16px;
  border-radius: 14px;
  background: var(--surface-raised);
  border: 0;
  box-shadow: var(--shadow-menu);
  font-size: 14px;
  color: var(--ink);
  animation: fade-rise var(--dur-base) var(--ease-out);
}

/* The question mark sits above the page, not inside it. It used to be pulled
   28px up into whatever followed, which worked only while the first-day cards
   were there to absorb it; on a page that starts with its own title and
   buttons it landed on top of them. */
.hub-topbar {
  display: flex;
  justify-content: flex-end;
  margin-bottom: 4px;
  min-height: 26px;
}

/* The first day: three cards, once. */
.first-day {
  display: grid;
  gap: 12px;
  margin-bottom: 24px;
}

.first-day-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 12px;
}

.first-day-card {
  padding: 18px;
  border-radius: var(--r);
  background: var(--ink);
  color: #fff;
  display: grid;
  gap: 6px;
}

.first-day-card strong {
  font-size: 16px;
}

.first-day-card span {
  font-size: 14px;
  opacity: .85;
}

/* Filming: she has the brief and the box. Her turn, and a colour of its own. */
.chip-filming {
  background: var(--lavender);
  color: var(--ink);
  border-color: transparent;
}

/* Scripts: who is waiting on the left, her one decision on the right.

   Two panes rather than one column, because the queue repeated the same
   suggestion cards under every person: seven people, twenty-one cards, three
   real choices. The list carries titles, the pane carries the content. */
.split {
  display: grid;
  grid-template-columns: minmax(200px, 260px) 1fr;
  gap: 0;
  padding: 0;
  overflow: hidden;
}

.split-list {
  padding: 22px 20px;
  border-right: 1px solid var(--line);
  background: var(--surface-soft);
  min-width: 0;
}

.split-detail {
  padding: 22px 24px;
  min-width: 0;
}

.people {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

/* One line per person: name, what she posts about, how long she has waited. */
.person-line {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 10px 12px;
  border-radius: var(--r-sm);
  border: 1px solid transparent;
  text-decoration: none;
  color: var(--ink);
  min-height: 44px;
}

.person-line:hover {
  background: rgba(12, 65, 41, .05);
}

/* The selected row stays highlighted for as long as the pane is about her:
   Apple's idiom for a selection that navigates, as opposed to the brief
   flash plus checkmark that means a toggle. */
.person-line.is-selected {
  background: var(--surface-solid);
  border-color: var(--line);
}

.person-line-main {
  display: grid;
  gap: 1px;
  min-width: 0;
}

.person-line-main strong {
  font-size: var(--fs-body);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.person-line-main .page-sub {
  font-size: var(--fs-sm);
}

.decision-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 12px;
  flex-wrap: wrap;
  margin-bottom: 16px;
}

.decision-head .page-sub {
  display: block;
  margin-top: 2px;
}

/* The six subjects Katie already built, as one tap each. A category set this
   small is exactly the case for offering choices rather than a text field. */
.subject-picker {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

/* "Best fit", "Also fits Beauty", and the category headings on the shelf:
   what the thing below it is. The shorthand margin was overriding .mt-sm,
   so a second and third group sat flush against the cards above them. */
.decision-label {
  font-size: var(--fs-label);
  font-weight: 700;
  text-transform: uppercase;
  color: var(--ink-soft);
  margin-bottom: 8px;
}

.script-rows + .decision-label {
  margin-top: var(--s5);
}

/* A split view needs horizontal space; below that the panes stack, the list
   becomes a short scroller and the decision follows underneath it. */
@media (max-width: 840px) {
  .split {
    grid-template-columns: 1fr;
  }

  .split-list {
    border-right: none;
    border-bottom: 1px solid var(--line);
    max-height: 40vh;
    overflow-y: auto;
  }
}

/* The library, as rows.

   It was a grid of cards, and both Apple and NN/g say that is the wrong shape
   for a list of text: "consider using a table instead of a collection for
   text", and "card layouts are less scannable than lists". The mechanism is
   position - in a row the title is always at the left edge and the usage line
   always beside it, so the eye runs down one column instead of hunting around
   a card. Rows also fit roughly three times as many scripts on a screen, and
   the fewer things need scrolling to, the less is being held in memory. */
.script-rows {
  list-style: none;
  margin: 0;
  padding: 0;
  border-top: 1px solid var(--line-soft);
}

.script-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s3);
  padding: 11px 6px;
  border-bottom: 1px solid var(--line-soft);
}

/* Hover and keyboard focus both, because the actions inside only appear on
   one of those and a keyboard user must see where they are. */
.script-row:hover,
.script-row:focus-within {
  background: var(--surface-soft);
}

.script-row-main {
  display: flex;
  align-items: baseline;
  gap: var(--s3);
  min-width: 0;
  flex: 1;
}

.script-row-title {
  font-weight: 700;
  color: var(--ink);
  text-decoration: none;
  /* A long title shortens rather than pushing the usage line out of its
     column: the fixed position is the point of the row. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.script-row-title:hover {
  text-decoration: underline;
}

.script-row-meta {
  display: flex;
  align-items: baseline;
  gap: var(--s2);
  color: var(--ink-soft);
  font-size: var(--fs-sm);
  white-space: nowrap;
  flex-shrink: 0;
}

/* Three actions on every row is thirty-nine links on a shelf of thirteen, all
   competing with the titles. They stay in the markup for screen readers and
   keyboards, and show on the row being used. */
.script-row-actions {
  display: flex;
  gap: var(--s3);
  flex-shrink: 0;
  opacity: 0;
  transition: opacity var(--dur-fast) var(--ease-out);
}

.script-row:hover .script-row-actions,
.script-row:focus-within .script-row-actions {
  opacity: 1;
}

/* Touch has no hover, so they are simply always there. */
@media (hover: none) {
  .script-row-actions {
    opacity: 1;
  }
}

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

.library-search {
  margin-bottom: var(--s3);
}

.scope-bar {
  margin-bottom: var(--s2);
}

.scope-bar-edit {
  margin-left: auto;
}

/* The row stacks on a phone: the actions drop under the title rather than
   squeezing it to nothing. */
@media (max-width: 640px) {
  .script-row {
    flex-wrap: wrap;
  }

  .script-row-main {
    flex-basis: 100%;
    flex-wrap: wrap;
  }

  .script-row-title {
    white-space: normal;
  }

  .scope-bar-edit {
    margin-left: 0;
  }
}

.suggestion {
  display: grid;
  gap: 8px;
  padding: 14px 16px;
  border-radius: var(--r);
  border: 1px solid var(--line);
  background: var(--surface-soft);
  align-content: start;
}

/* The one script chosen for her. White against the recessed shelf cards, and
   wide enough to read a real paragraph rather than a truncated teaser: this
   is the card a decision gets made on. */
.suggestion-best {
  background: var(--surface-solid);
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: 16px;
}

/* On a phone the two columns squeeze the paragraph to a few words a line,
   and this is the card the script decision gets made on. One column, the
   actions under the body, the send button the full width under the thumb. */
@media (max-width: 640px) {
  .suggestion-best {
    grid-template-columns: 1fr;
  }

  .suggestion-best .actions-row {
    width: 100%;
  }

  .suggestion-best .actions-row .btn-primary {
    flex: 1;
    width: 100%;
    min-height: 44px;
    text-align: center;
  }
}

.suggestion-body {
  display: grid;
  gap: 4px;
  min-width: 0;
}

.suggestion-line {
  margin: 0;
  font-size: 13.5px;
  color: var(--ink-soft);
  line-height: 1.45;
}

/* Review chips: the four things that come back most, one tap each. */
.chips-row {
  margin-bottom: 8px;
}

.chip-button {
  position: relative;
  cursor: pointer;
  font-family: inherit;
  border: 1px solid var(--line);
  background: var(--surface-soft);
  min-height: 32px;
}

/* The visible chip stays small; the hit area around it does not. */
.chip-button::after {
  content: "";
  position: absolute;
  inset: -6px;
}

.chip-button:hover {
  border-color: var(--line-strong);
  background: var(--surface-solid);
}

.chip-button:active {
  transform: scale(.98);
}

/* No outline: none here. It used to cancel the global ring and leave the same
   border change hover already makes, so keyboard focus looked like a mouse
   hovering somewhere else. */
.chip-button:focus-visible {
  border-color: var(--ink);
  border-radius: var(--r-pill);
}

/* Drop a file anywhere on the card. */
.dropzone.is-dragover {
  outline: 2px dashed var(--ink);
  outline-offset: 4px;
  background: var(--surface-solid);
}

/* Her words on the timeline. */
.bubble {
  display: inline-block;
  max-width: 60ch;
  margin-top: 6px;
  padding: 10px 14px;
  border-radius: 16px 16px 16px 4px;
  background: var(--surface-soft);
  border: 1px solid var(--line);
  font-size: 14.5px;
  white-space: pre-line;
}

.pay-run-list {
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 12.5px;
  white-space: pre;
  overflow-x: auto;
  font-variant-numeric: tabular-nums;
}

/* Turbo. The progress bar is lime on the hub and ink on the portal, two
   pixels, and only shows after 250ms (the delay is set where Turbo is
   imported) so a quick navigation never flashes it. */
.turbo-progress-bar {
  height: 2px;
  background: var(--lime);
  box-shadow: 0 0 8px var(--lime-glow), 0 0 1px var(--ink);
  z-index: var(--z-nav);
}

body.portal-page .turbo-progress-bar {
  background: var(--ink);
  box-shadow: none;
}

/* A frame that is fetching dims what it has and runs a sheen over it, which
   says "working" without a spinner and without a blank. */
turbo-frame[busy] {
  position: relative;
}

turbo-frame[busy] > * {
  opacity: .55;
  transition: opacity var(--dur-base) var(--ease-out);
}

turbo-frame[busy]::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(100deg, transparent 30%, rgba(255, 255, 255, .5) 50%, transparent 70%) 0 0 / 220% 100%;
  animation: btn-shimmer 1.6s var(--ease-in-out) infinite;
}

/* Page to page: the old page drops, the new one rises, the sidebar and the
   title hold still so the eye has an anchor. */
@view-transition {
  navigation: auto;
}

::view-transition-old(root) {
  animation: fade-drop var(--dur-base) var(--ease-in);
}

::view-transition-new(root) {
  animation: fade-rise var(--dur-enter) var(--ease-out);
}

.hub-sidebar {
  view-transition-name: hub-sidebar;
}

.page-head h1 {
  view-transition-name: page-title;
}

/* Long lists render what is on screen first. */
.timeline,
.script-rows {
  content-visibility: auto;
  contain-intrinsic-size: auto 48px;
}

/* Phone: rows become cards, dialogs become bottom sheets, the primary
   action of a form stays under the thumb. */
@media (max-width: 760px) {
  .needs-row {
    grid-template-columns: 1fr;
    gap: 6px;
    padding: 14px 0;
  }

  .needs-row .actions-row {
    width: 100%;
  }

  .needs-row .actions-row .btn-primary,
  .needs-row .actions-row .btn-secondary {
    flex: 1;
    text-align: center;
  }

  /* The sheet slides up from the bottom edge on the iOS curve, sits above
     the home indicator, and comes down the same way. The blur and scale of
     the laptop entrance are switched off: a sheet moves, it does not fade. */
  .hub-dialog {
    position: fixed;
    inset: auto 0 0 0;
    width: 100%;
    max-width: none;
    margin: 0;
    border-radius: 22px 22px 0 0;
    max-height: calc(92vh - var(--safe-b));
    overflow: auto;
    padding-bottom: var(--safe-b);
    opacity: 1;
    filter: none;
    transform: translateY(100%);
    transition-duration: 360ms;
    transition-timing-function: var(--ease-sheet);
  }

  .hub-dialog[open] {
    transform: none;
  }

  @starting-style {
    .hub-dialog[open] {
      transform: translateY(100%);
      opacity: 1;
      filter: none;
    }
  }

  .hub-dialog .hub-dialog-body {
    padding-bottom: calc(var(--s6) + var(--safe-b));
  }

  /* The grab bar is a real control here: 44px tall to grab, 5px to see. */
  .sheet-handle {
    display: flex;
    align-items: center;
    justify-content: center;
    height: 28px;
    margin: 0;
    padding: 4px 0 0;
    cursor: grab;
    touch-action: none;
  }

  .sheet-handle::before {
    content: "";
    width: 36px;
    height: 5px;
    border-radius: 3px;
    background: var(--line-strong);
  }

  .hub-dialog.is-dragging {
    transition: none;
  }

  .hub-dialog.is-dragging .sheet-handle {
    cursor: grabbing;
  }

  .sheet-cancel {
    display: block;
    width: 100%;
    margin-top: var(--s3);
    padding: 12px;
    min-height: var(--tap);
    border: none;
    border-radius: var(--r-pill);
    background: transparent;
    font-family: "Quicksand", sans-serif;
    font-weight: 700;
    font-size: 14px;
    color: var(--ink-soft);
    cursor: pointer;
    transition: background var(--dur-fast) var(--ease-out), transform var(--dur-fast) var(--ease-out);
  }

  .sheet-cancel:active {
    background: var(--surface-soft);
    transform: scale(.98);
  }

  .card-narrow .form-actions {
    position: sticky;
    bottom: 0;
    z-index: var(--z-sticky);
    padding: 12px 0 calc(12px + var(--safe-b));
    background: rgba(251, 249, 255, .96);
    border-top: 1px solid var(--line);
  }

  .card-narrow .form-actions .btn-primary {
    flex: 1;
    text-align: center;
  }
}
