/* CallbackSeason — the Board direction.
   ====================================================================
   A dense, monospace, keyboard-first tool: a cool slate ground, raised
   panels, a magenta accent, light and dark.

   Written to replace style.css rather than to extend it, and that sheet
   is now deleted. It had grown to 5,548 lines around a
   card-and-rounded-corner look, with colours written out at every use
   and a dark-mode block beside each one; this direction changes the
   ground, the type, the radius, the density and the palette at once,
   so nearly every rule in it would have been rewritten in place.

   Every colour here is a token and there are two sets of them, which
   is the property the old sheet lacked and the reason a third theme
   would now be a block of values rather than a week.

   Sections
     1. tokens + reset
     2. chrome: top bar, footer, panels
     3. jobs
     4. profile
     5. stats
     6. dev strip (unstyled by the design; kept legible, kept obviously
        not part of the product)
   ==================================================================== */

/* --- 1. tokens ------------------------------------------------------ */

:root {
  --bg:     #e2e5ea;   /* page ground */
  --panel:  #eef0f3;   /* panel body */
  --panel2: #d5dae1;   /* panel headers, top bar, selected row, empty cells */
  --line:   #bfc5ce;   /* every border, always 1px */
  --ink:    #14171c;
  /* The three inks are a contrast ladder, not three greys somebody
     liked the look of. Measured against --panel2, which is the ground
     the fact band, the top bar and every panel head sit on, and so
     the one the text has to work hardest against: 12.8 / 7.1 / 4.6.

     ink3 was #7d8592 and came out at 2.6 there -- under the 3.0 that
     counts as visible at all, let alone the 4.5 WCAG asks of text this
     size. It is the colour of every label, hint and secondary line on
     the product, so that was most of the words on most of the
     screens. */
  --ink2:   #3e434d;
  --ink3:   #595e68;
  --acc:    #a3247a;
  --acc-ink:#ffffff;
  /* ok and warn were 3.8 and 3.6 on panel2, and both carry real words
     -- a match reason, a rejection reason -- not just a dot of
     colour. Both clear 4.5 now. */
  --ok:     #1e6b41;
  /* OFFER is the one tag that inverts: white ON the green rather than
     green on the ground, so the two or three that matter are findable
     in a column of outlines. --ok-fill is its own token because dark's
     --ok (#4fd38a) is far too bright to carry white text -- 1.9:1 --
     so the fill there is a deeper green than the ink colour beside it.
     In light it now matches --ok, which the contrast pass moved to the
     same green; the token still earns its keep in dark. */
  --ok-fill:#1e6b41;
  --ok-ink: #ffffff;
  --warn:   #825411;
  --bad:    #b3261e;

  /* The one type scale, named rather than repeated.

     --t-scale is the single knob. Every rung is the size the original
     design drew at, times this, so 1 reproduces the pre-2026-09-15
     app exactly and there is one number to argue about rather than
     nine. The px literals below are that original scale; change the
     knob, not them. */
  --t-scale: 1.2;
  --t-base:  calc(11.5px * var(--t-scale));  /* body and row text */
  --t-label: calc(10px   * var(--t-scale));  /* uppercase labels */
  --t-small: calc(10.5px * var(--t-scale));  /* strip and section labels */
  --t-mid:   calc(12px   * var(--t-scale));  /* funnel counts */
  --t-title: calc(13px   * var(--t-scale));  /* the thing a panel is about */
  --t-kpi:   calc(20px   * var(--t-scale));
  --t-huge:  calc(64px   * var(--t-scale));
  color-scheme: light dark;
}

:root[data-theme="dark"],
html:not([data-theme="light"]) {
  /* placeholder so the cascade order is explicit; real values below */
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #0f1216; --panel: #171b21; --panel2: #242a34; --line: #303845;
    --ink: #e7eaef; --ink2: #b5bcc7; --ink3: #8c929d;
    --acc: #ff5cc0; --acc-ink: #2a0019;
    --ok: #4fd38a; --ok-fill: #1f8552; --ok-ink: #ffffff; --warn: #f2b950; --bad: #ff6b61;
  }
}
:root[data-theme="dark"] {
  --bg: #0f1216; --panel: #171b21; --panel2: #242a34; --line: #303845;
  --ink: #e7eaef; --ink2: #b5bcc7; --ink3: #8c929d;
  --acc: #ff5cc0; --acc-ink: #2a0019;
  --ok: #4fd38a; --ok-fill: #1f8552; --ok-ink: #ffffff; --warn: #f2b950; --bad: #ff6b61;
}

/* Geist Mono everywhere, loaded by a <link> in the page head rather
   than an @font-face here: the family is served as a variable font
   whose file URL is versioned, and naming that URL in our own sheet
   would pin us to a revision that expires. The fallback stack matters
   more than usual — this is a monospace LAYOUT, so a proportional
   fallback would not merely look different, it would break the
   columns. */

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

body {
  /* body owns the window, not #app: the dev strip is #app's SIBLING, so
     a 100vh #app plus a 65px strip is 65px taller than the window, and
     every screen's footer sat just below the fold. */
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}
html, body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: 'Geist Mono', ui-monospace, SFMono-Regular, Menlo,
               'DejaVu Sans Mono', monospace;
  font-size: var(--t-base);
  line-height: 1.45;
}

/* Radius 0 and no shadows, everywhere, without exception. Stated once
   here so no rule below has to keep saying it. */
* { border-radius: 0 !important; box-shadow: none; }

a { color: inherit; text-decoration: none; }
button, input, select, textarea { font: inherit; color: inherit; }

/* --- the label, the single most repeated thing in this design ------- */
.lbl {
  font-size: var(--t-label);
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ink3);
}

/* --- 2. chrome ------------------------------------------------------ */

/* No min-height:0 anywhere in this chain. That property lets a flex
   item shrink below its content, and with it on #app, #persona-body and
   .screen the whole app compressed itself into the viewport: .panes
   came out exactly 780px however long the posting was, so the document
   never grew and the window never got a scrollbar. Without it the
   content decides the height, the document grows, and the window
   scrolls — which is what the reader needs. */
#app { flex: 1 1 auto; display: flex; flex-direction: column; }

.topbar {
  /* the chrome holds while the page scrolls under it */
  position: sticky; top: 0; z-index: 30;
  height: 32px;
  display: flex;
  align-items: stretch;
  background: var(--panel2);
  border-bottom: 1px solid var(--line);
  font-size: var(--t-small);
  letter-spacing: .06em;
}
.brand {
  display: flex; align-items: center;
  padding: 0 12px;
  font-weight: 700;
  text-transform: uppercase;
}
.brand .cb { color: var(--acc); }

.tabs { display: flex; align-items: stretch; }
.tab {
  display: flex; align-items: center;
  padding: 0 12px;
  text-transform: uppercase;
  color: var(--ink2);
  border-left: 1px solid transparent;
  border-right: 1px solid transparent;
}
.tab .k { font-weight: 700; margin-right: .6ch; }
.tab.on {
  background: var(--panel);
  color: var(--ink);
  font-weight: 600;
  border-left-color: var(--line);
  border-right-color: var(--line);
  box-shadow: inset 0 2px 0 var(--acc);
}

.topbar .spacer { flex: 1 1 auto; }
.cell {
  display: flex; align-items: center; gap: 1ch;
  padding: 0 12px;
  border-left: 1px solid var(--line);
  color: var(--ink2);
  white-space: nowrap;
}
.cell b { color: var(--ink); font-weight: 700; }

.foot {
  /* margin-top:auto, so a screen whose content does not fill the window
     still puts the legend at the bottom. Jobs did this by accident --
     .panes is flex:1 and grew into the space -- while Profile and Stats
     have no growing child, so the legend sat wherever the content
     happened to end. Sticky handles the long pages, this handles the
     short ones. */
  margin-top: auto;
  position: sticky; bottom: 0; z-index: 20;
  height: 24px;
  display: flex; align-items: center; gap: 2.5ch;
  padding: 0 12px;
  background: var(--panel2);
  border-top: 1px solid var(--line);
  font-size: var(--t-small);
  letter-spacing: .06em;
  color: var(--ink2);
  text-transform: uppercase;
}
.foot .k { color: var(--ink); font-weight: 700; margin-right: .6ch; }
.foot .spacer { flex: 1 1 auto; }

/* --- panels: the one container this design has ---------------------- */
.panel {
  background: var(--panel);
  border: 1px solid var(--line);
  display: flex;
  flex-direction: column;
  min-width: 0;
}
.panel > .head {
  display: flex; align-items: center; gap: 1ch;
  padding: 6px 12px;
  background: var(--panel2);
  border-bottom: 1px solid var(--line);
  font-size: var(--t-label);
  letter-spacing: .06em;
  text-transform: uppercase;
  font-weight: 600;
}
.panel > .head .meta {
  margin-left: auto;
  font-weight: 400;
  color: var(--ink2);
}
.panel > .body { padding: 10px 12px; min-width: 0; }

/* --- inputs --------------------------------------------------------- */
.in {
  width: 100%;
  padding: 4px 8px;
  background: var(--bg);
  border: 1px solid var(--line);
  color: var(--ink);
}
.in::placeholder { color: var(--ink3); }

/* --- status, one tag shape ------------------------------------------ */
.tag {
  display: inline-block;
  padding: 0 4px;
  font-size: var(--t-label);
  letter-spacing: .06em;
  text-transform: uppercase;
  border: 1px solid currentColor;
  white-space: nowrap;
}
.st-queued    { color: var(--ink3); }
/* APPLIED was --acc (#a3247a plum, #ff5cc0 in dark) sitting one column
   away from REJECTED's --bad (#b3261e, #ff6b61): at 12px uppercase
   with letter-spacing those scan as one colour, so the STATE column
   could not be read at a glance. Green says "you did this" and cannot
   be mistaken for the red. */
.st-applied   { color: var(--ok); }
.st-heard-back{ color: var(--acc); }
.st-advancing { color: var(--warn); }
.st-offer     { color: var(--ok); }
.st-rejected  { color: var(--bad); }
/* Which leaves APPLIED and OFFER both green -- you did the thing, and
   it worked. The list tag fills OFFER in rather than outlining it, so
   the two are still one glance apart. Scoped to .tag: the status
   picker's pills share these st- classes and say their own state by
   being .active, so a filled INACTIVE pill would be a lie. */
.tag.st-offer { background: var(--ok-fill); color: var(--ok-ink); border-color: var(--ok-fill); }

/* the account menu, which is a cell that opens */
.user-menu { position: relative; }
.user-menu > summary {
  list-style: none; cursor: pointer;
  display: flex; align-items: center; height: 100%;
}
.user-menu > summary::-webkit-details-marker { display: none; }
.user-menu .menu-panel {
  position: absolute; right: 0; top: 100%; z-index: 20;
  min-width: 160px;
  background: var(--panel);
  border: 1px solid var(--line);
  display: flex; flex-direction: column;
}
/* colour and font, not just padding. These are a mix of <a> and
   <button>, and with neither set the button took the UA's own default
   text colour -- dark ink on a dark panel in the dark theme, which
   measured 2.8 and was the least legible text on the product. */
/* a and button by name, not "> *". Sign out is wrapped in a
   <form style="display:contents">, so the form is the child and the
   button is a grandchild: the child selector styled the form, which
   has nothing to paint, and left the button on the UA's own
   buttonface grey. */
.user-menu .menu-panel a, .user-menu .menu-panel button {
  padding: 5px 10px; text-align: left;
  background: none; border: 0; cursor: pointer;
  color: var(--ink); font: inherit; font-size: var(--t-base);
  text-decoration: none;
}
.user-menu .menu-panel a:hover, .user-menu .menu-panel button:hover {
  background: var(--panel2);
}

/* the dev strip is not part of the design; make it legible and make it
   obviously ours rather than the product's */
.dev-strip {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 5px 10px; font-size: 10px;
  background: repeating-linear-gradient(45deg, transparent, transparent 6px,
              color-mix(in srgb, var(--bad) 8%, transparent) 6px,
              color-mix(in srgb, var(--bad) 8%, transparent) 12px);
  border-bottom: 1px dashed var(--bad);
}
.dev-strip .dev-strip-inner {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap; width: 100%;
}
.dev-flags { display: flex; gap: 10px; }
.dev-flag { display: inline-flex; align-items: center; gap: .6ch; cursor: pointer; }
/* the personas are a row of buttons, spaced, and each one separates the
   account from what it costs — they were running together as
   "New user (paid)$30 / mo" */
.dev-strip-personas { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.dev-strip-actions  { display: flex; align-items: center; gap: 6px; margin-left: auto; }
.dev-strip button, .dev-strip .persona-btn {
  display: inline-flex; align-items: baseline; gap: .9ch;
  padding: 3px 8px; background: var(--panel);
  border: 1px solid var(--line); cursor: pointer; font-size: 10px;
  white-space: nowrap;
}
.dev-strip button:hover { background: var(--panel2); border-color: var(--ink3); }
.dev-strip .price {
  color: var(--ink3);
  border-left: 1px solid var(--line);
  padding-left: .9ch;
}
.dev-strip .persona-btn.active { background: var(--acc); color: var(--acc-ink); border-color: var(--acc); }
.dev-strip .persona-btn.active .price { color: var(--acc-ink); opacity: .75; border-left-color: var(--acc-ink); }
.login-notice {
  padding: 4px 12px; background: var(--panel2);
  border-bottom: 1px solid var(--line);
}

/* --- 3. jobs -------------------------------------------------------- */

.screen { flex: 1 1 auto; display: flex; flex-direction: column; }
.notice {
  margin: 8px 8px 0; padding: 6px 12px;
  background: var(--panel); border: 1px solid var(--line); color: var(--ink2);
}
/* The window's scrollbar is the right pane's scrollbar. The list holds
   its place while the posting scrolls past it, which is the whole point
   of a two-pane reader: the thing you are choosing from does not move
   while you read what you chose.

   So nothing here traps a scroll of its own. The panes row grows to
   whatever the posting needs, the document grows with it, and the list
   is sticky against the top. */
.panes {
  flex: 1 1 auto;
  display: flex; gap: 8px; padding: 8px;
  align-items: flex-start;
}

/* the list: five columns, one line a job */
.list-pane {
  /* The LIST takes the extra room, not the reader. A posting has a
     width past which it stops reading better -- that is the reader's
     cap below -- where the list has a column that is never long
     enough: ROLE is the 1fr in the row grid, so every pixel the pane
     gains goes to the job title. Basis 520 so a 1280 window lands
     about where it always did; grow 1 so a wider one spends the
     difference here. */
  flex: 1 1 520px; min-width: 440px;
  position: sticky;
  /* under the sticky top bar, plus the screen's own 8px of padding */
  top: 40px;
  /* a list longer than the window scrolls inside itself rather than
     pushing the page, because the page belongs to the reader */
  max-height: calc(100vh - 32px - 24px - 16px);
}
.list-pane::before {
  content: ""; position: absolute;
  left: -1px; right: -1px; bottom: 100%; height: 9px;
  background: var(--bg);
}
.read-pane { position: relative; }
.filters {
  display: flex; align-items: center; gap: 2ch;
  padding: 5px 10px;
  border-bottom: 1px solid var(--line);
  font-size: var(--t-label); letter-spacing: .06em; text-transform: uppercase;
  color: var(--ink2);
}
.filters .spacer { flex: 1 1 auto; }
.filters .f.on { background: var(--acc); color: var(--acc-ink); font-weight: 600; padding: 1px 6px; }
.filters .f.hint { color: var(--ink3); }

.cols, .env {
  display: grid;
  grid-template-columns: 88px 1fr 92px 36px 78px;
  gap: 8px;
  align-items: center;
}
.cols {
  padding: 4px 10px;
  background: var(--panel2);
  border-bottom: 1px solid var(--line);
  font-size: var(--t-label); letter-spacing: .06em; color: var(--ink3);
}
.cols .r, .env .c-pay, .env .c-age { text-align: right; }

.rows { overflow: auto; min-height: 0; flex: 1 1 auto; overscroll-behavior: contain; }
.env {
  padding: 5px 10px;
  border-bottom: 1px solid var(--panel2);
  color: var(--ink);
}
.env > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.env .c-co   { font-weight: 600; }
.env .c-loc  { color: var(--ink3); }
.env .c-pay  { color: var(--ink2); }
.env .c-age  { color: var(--ink3); }
.env .c-state { overflow: visible; }
.env.on { background: var(--panel2); }
.env.locked { opacity: .45; }
.env:hover:not(.on) { background: color-mix(in srgb, var(--panel2) 50%, transparent); }

/* the reader */
/* Grows to 820 and stops. Past that a line of prose gets long enough
   that the eye loses the start of the next one, and the rail beside it
   is a fixed 260 -- so the extra width was going into a measure nobody
   wanted rather than into the list, which can always use it. */
/* shrink factor 2: when the window cannot hold both at their basis the
   reader gives up twice what the list does. Losing width costs the
   reader a shorter line; it costs the list the end of every job title,
   which is the column you are scanning. */
.read-pane { flex: 0 2 820px; min-width: 0; }
.rhead {
  display: flex; align-items: stretch; gap: 0;
  padding: 5px 10px;
  background: var(--panel2);
  border-bottom: 1px solid var(--line);
  position: sticky; top: 40px; z-index: 5;
}
/* The 8px between the top bar and the panel is page ground, and a
   sticky header does not cover it — so the posting scrolled up through
   the gap and appeared above its own header. This paints the gap. */
.rhead::before {
  content: ""; position: absolute;
  left: -1px; right: -1px; bottom: 100%; height: 9px;
  background: var(--bg);
}
.rhead { position: sticky; }
.rhead .spacer { flex: 1 1 auto; }
.rcell { padding: 0 12px; border-left: 1px solid var(--line); min-width: 0; }
.rcell:first-child { border-left: 0; padding-left: 0; }
/* .c-pay-cell / .c-where are shared with the list's columns, where the
   minimums are px sized for a narrow column. In the header there is
   room, and collapsing "$250k-$300k USD / year" onto three lines to
   respect a list column's budget is just wrong. Stated in ch so the
   cell keeps holding a whole band as --t-scale moves. */
.rhead .c-pay-cell { min-width: 16ch; }
.rhead .c-where    { min-width: 18ch; }
.rtitle { font-size: var(--t-title); font-weight: 700; }
.rv { color: var(--ink); }
.rv.b  { font-weight: 600; }
.rv.ok { color: var(--ok); }
/* The band WRAPS rather than overflowing. Every fact cell has a
   measure of its own on purpose -- sharing one let the match sentence
   take room from WHERE and truncate a city mid-word -- so nothing here
   can give way, and on a narrow window the buttons were pushed past
   the right edge of the panel. Wrapping puts them on a second line
   with every cell still the width it was meant to be.

   margin-left:auto keeps them right-aligned on whichever line they
   land on. */
.racts { display: flex; align-items: stretch; gap: 8px; padding-left: 12px;
         flex: 0 0 auto; margin-left: auto; }
/* ONE ROW. A wrapping flex container breaks lines on flex-BASIS and
   only shrinks within a line, so allowing the wrap put the buttons on
   a second row however much the title was willing to give up. nowrap
   makes the line shrink instead, and the minimums below are set so the
   sum of them fits the pane: the title gives way (it clamps to three
   lines, so it wraps rather than cutting a word) while PAY and WHERE
   hold their shape. */
.rhead { flex-wrap: nowrap; }

/* The key sits ABOVE the word rather than beside it, which squares the
   button up and lets the three of them sit in the space the band has
   instead of running along it. Same object either way: what you can do
   and how to do it from the keyboard. */
.tui-btn {
  display: inline-flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 2px;
  min-width: 66px; padding: 6px 8px;
  border: 1px solid var(--line);
  background: none; cursor: pointer;
  font-size: var(--t-label); letter-spacing: .06em; text-transform: uppercase;
  white-space: nowrap; text-align: center;
}
.tui-btn .bk { font-weight: 700; }
.tui-btn[data-key="a"] { background: var(--acc); color: var(--acc-ink); border-color: var(--acc); }
.tui-btn.danger { color: var(--bad); }

.sections {
  display: flex; gap: 0;
  /* Wrap rather than scroll. At the sizes this strip normally runs at
     it does neither -- all seven fit the band. But if the type scale
     goes up or the pane gets narrow, a second row is honest where a
     scroller hides the last two tabs behind an edge with nothing to
     say they are there. Safe because .sec-tab is nowrap: the break
     lands BETWEEN tabs, never inside "THE WORK". */
  flex-wrap: wrap;
  border-bottom: 1px solid var(--line);
  font-size: var(--t-small); letter-spacing: .06em; text-transform: uppercase;
}
.sec-tab {
  /* nowrap, and padding in ch rather than px: seven tabs share one
     row, so the label must never break mid-word ("W THE / WORK") and
     the gutters have to shrink with the type scale rather than stay
     put while the words grow. */
  white-space: nowrap;
  padding: 5px 1ch; color: var(--ink2);
  border: 0; border-bottom: 2px solid transparent;
  background: none; cursor: pointer;
  font: inherit; letter-spacing: inherit; text-transform: inherit;
}
.sec-tab .k { font-weight: 700; margin-right: .8ch; }
/* the selected one, not the first one: the tabs choose what the text
   area shows, so the mark has to follow the choice */
.sec-tab.on { font-weight: 600; color: var(--ink); border-bottom-color: var(--acc); }
.sec-tab.off { color: var(--ink3); cursor: default; }
.sec-pane[hidden] { display: none; }

.rbody { display: grid; grid-template-columns: 1fr 260px; }
/* No max-width. It had one, and inside an already-narrow column it
   read as the text being wrapped by some inner box -- because it was.
   The reading measure is the read-pane's cap now, which is one place
   deciding it rather than two disagreeing. */
/* --ink for the same reason as .raw-text: these are the posting's own
   sections, and switching tabs should not change how dark the words
   are. */
.prose { padding: 10px 12px; color: var(--ink); min-width: 0; }

/* The paragraphs inside carry class="prose" THEMSELVES, so the rule
   above was giving each one the container's 10px of vertical padding
   on top of its own margin: 10 + 14 + 10 = 34px between one line of
   text and the next, against a 17px line. Two blank lines, from a
   selector meant for the box around them.

   One blank line is the rule for everything the listing says, so the
   gap is one line-height and no more. */
.prose .prose { padding: 0; }
.prose p, .prose ul > li { margin: 0 0 17px; }
.prose p:last-child, .prose ul > li:last-child { margin-bottom: 0; }
.prose ul { margin: 0 0 14px; padding-left: 0; list-style: none; }
/* `ul >`, not any li: the history feed is an <ol> of .h-row grid rows
   living inside .prose as well. A generated ::before on a grid
   container is itself a GRID ITEM, so this bullet took the row's first
   90px track and shoved every following cell one place along -- the
   note field landed in the 18px track meant for the delete button and
   rendered one character per line. */
.prose ul > li::before { content: "\25aa"; color: var(--acc); margin-right: 1ch; }
.sec-mark { margin: 18px 0 6px; }
.sec-mark:first-child { margin-top: 0; }
.prose .sec-head { display: none; }   /* the mark above it already says this */
/* --ink, not --ink3. This is the whole posting in the employer's own
   words, and since the reader lands here it is the most-read text on
   the product -- it was painted in the ink reserved for labels and
   counts. Raising the token to AA fixed the label it was meant for
   and left a page of prose at the bare minimum, which is legible in
   the sense that a test passes and not in the sense that anybody
   wants to read it. */
.raw-text {
  margin: 0; white-space: pre-wrap; overflow-wrap: break-word;
  color: var(--ink); font-size: var(--t-base); line-height: 1.5;
}

.rail {
  background: var(--panel2);
  border-left: 1px solid var(--line);
  padding: 10px 12px;
  min-width: 0;
}
.rf { margin-bottom: 10px; }
.rf .rv { overflow-wrap: break-word; }
.rf .rv.ok   { color: var(--ok); }
.rf .rv.warn { color: var(--warn); }
.rf .rv.ink2 { color: var(--ink2); }

/* section-block brings its own heading; the sec-mark above it already
   names the section, so the inner one is redundant and its margins are
   what made the prose twice as airy as the design */
.prose .sec { margin: 0; }
.prose .sec-head { display: none; }
.prose .unstated { color: var(--ink3); }
.tui-actions { display: flex; align-items: center; flex-wrap: wrap; }
.tui-actions .btn-row { display: flex; gap: 8px; }

/* Signed out, the three verbs grow: "Apply" becomes "Sign in to
   apply", "Reject" becomes "Sign in to reject". A flex item does not
   shrink below its own content, so the row pushed past the pane and
   the last button scrolled out of reach entirely -- the one that says
   how to sign in, on the page whose job is to get you to sign in.

   So let the row wrap and let a label break. The block grows downward
   into the space it has, about three lines of it, instead of sideways
   out of the pane. Signed in the labels are short and none of this
   does anything. */
.tui-actions .btn-row { flex-wrap: wrap; }

/* `.tui-btn` sets `white-space: nowrap` and `min-width: 66px` up at
   line 512, which is right for a one-word verb and is why letting the
   ROW wrap did nothing on its own: the labels still could not break,
   so each button stayed as wide as its longest word and the row stayed
   wider than the pane.
   
   Scoped to the reader's action row, and by specificity rather than
   order, so every other .tui-btn on the site keeps the nowrap it was
   given. A cap of 10ch puts "Sign in to reject" on two label lines
   under the key glyph -- three lines of text in a button that has the
   vertical space for them, and a row that fits. */
.tui-actions .tui-btn {
  min-width: 0; max-width: 10ch;
  white-space: normal; height: auto;
  /* Every button as tall as the tallest, and its contents starting at
     the top rather than floating in the middle. PEEK is one short word
     beside two that now take three lines, so centred it sat as a small
     box halfway down the row with its key glyph aligned to nothing.
     Stated rather than left to the default: `stretch` is what a flex
     row does anyway, and it plainly was not happening here. */
  align-self: stretch;
  justify-content: flex-start;
}
.tui-actions .btn-row { align-items: stretch; }
.tui-actions .tui-btn .bl { overflow-wrap: break-word; line-height: 1.15; }

/* The header band is a row of fixed answers, not a paragraph that
   reflows: without widths the MATCH sentence squeezed the job title
   into five lines and pushed WHERE under it. Each cell gets a measure
   and clamps; the rail below carries the same facts in full. */
.rhead { align-items: flex-start; }
/* The title is the one cell that may give way, and the only one that
   can do it safely: it is already clamped to three lines, so losing
   width wraps it rather than cutting a word. PAY and WHERE stay fixed
   -- those are short facts of a known shape, and squeezing them is how
   a city got truncated mid-word before. */
.rcell.who     { flex: 1 1 200px; min-width: 110px; }
.rcell         { flex: 0 0 auto; }
/* Shrinkable now, with a floor. They were pinned because the match
   sentence beside them grew without limit and took their width; that
   sentence has left the band, so the only thing that can squeeze them
   is the window itself, and giving way to it beats overflowing. Both
   clamp to three lines, so narrowing wraps them rather than cutting. */
.c-pay-cell    { flex: 0 1 108px; min-width: 74px; }
.c-where       { flex: 0 1 180px; min-width: 104px; }
.rcell .rv, .rtitle {
  display: -webkit-box; -webkit-box-orient: vertical; overflow: hidden;
}
.rtitle    { -webkit-line-clamp: 3; }
.rcell .rv { -webkit-line-clamp: 3; }

/* --- 4. profile ----------------------------------------------------- */

/* Wraps rather than scrolls. The strip is one sentence read left to
   right -- pool, then each gate, then what is left -- and a gate
   pushed off the edge of a scroller is a step of that sentence the
   reader does not know is missing. Six cells no longer fit a phone
   (LEVEL joined on 2026-09-11), so they fold onto a second line and
   the whole sentence stays on screen. */
.funnel {
  display: flex; align-items: center; flex-wrap: wrap;
  margin: 8px 8px 0;
  background: var(--panel); border: 1px solid var(--line);
  font-size: var(--t-small); letter-spacing: .06em; text-transform: uppercase;
}
.fcell {
  display: flex; align-items: baseline; gap: .6ch;
  padding: 6px 12px; border-right: 1px solid var(--line);
  color: var(--ink2); white-space: nowrap;
}
.fcell.last { background: var(--panel2); border-right: 0; }
/* the cell that starts a wrapped second line keeps its left edge from
   butting against the panel border with no rule of its own */
.funnel > .fcell:last-child { border-right: 0; }
.fcell .chev { color: var(--acc); }
.fcell .flabel { color: var(--ink3); }
.fcell b {
  font-size: var(--t-mid); font-weight: 700; color: var(--ink);
  /* inline-block so the pop below can transform it; an inline box
     ignores transform entirely */
  display: inline-block;
}

/* A gate the reader has not narrowed still shows its count -- the
   question is how many jobs, not whether a gate is on -- but dimmed,
   so the eye can tell at a glance which figures are the result of
   something you chose. This replaces printing "not limited" in place
   of the number, which under COMP FLOOR read as a value. */
.fcell b.off { color: var(--ink3); font-weight: 400; }

/* The momentary pop. Small on purpose: enough to catch the eye at the
   edge of vision while you are looking at the control you just moved,
   not enough to read as the layout shifting. */
@keyframes count-pop {
  0%   { transform: scale(1);    }
  28%  { transform: scale(1.22); }
  100% { transform: scale(1);    }
}
.fcell b.pop { animation: count-pop 260ms cubic-bezier(.2,.8,.3,1); }

@media (prefers-reduced-motion: reduce) {
  .fcell b.pop { animation: none; }
}
.fcell .ftrail { color: var(--ink3); text-transform: none; letter-spacing: 0; }

.pcols {
  flex: 1 1 auto;
  display: grid; grid-template-columns: 1fr 1fr 1.3fr;
  gap: 8px; padding: 8px;
  align-items: start;          /* panels are their own height */
  align-content: start;
}
.pcol { display: flex; flex-direction: column; gap: 8px; min-width: 0; }

/* the glyph checkbox: the real input stays for the keyboard and the
   form, the glyph beside it is what the design draws */
.ck { display: flex; align-items: baseline; gap: 1ch; cursor: pointer; padding: 2px 0; }
.ck > input { position: absolute; opacity: 0; width: 0; height: 0; }
/* Drawn, not typed. ☑/☐ are the right picture and the wrong mechanism:
   Geist Mono has neither, so every one of them fell back to whatever
   font did have it and came out a different size from the row it sits
   in. A box and a tick made of borders are the same picture in the
   accent colour at exactly the size we ask for. */
.ck .box {
  flex: 0 0 auto; position: relative;
  width: 11px; height: 11px;
  border: 1px solid var(--acc);
  align-self: center;
}
.ck > input:checked ~ .box::after {
  content: ""; position: absolute;
  left: 3px; top: 0px; width: 3px; height: 7px;
  border: solid var(--acc); border-width: 0 1.5px 1.5px 0;
  transform: rotate(45deg);
}
.ck > input:focus-visible ~ .box { outline: 1.5px solid var(--ink); outline-offset: 1px; }
.ck > input:indeterminate ~ .box::after {
  content: ""; position: absolute; left: 1px; top: 4px;
  width: 7px; height: 0; border-top: 1.5px solid var(--acc);
  transform: none;
}
.ck .cl { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ck .cn { color: var(--ink3); font-variant-numeric: tabular-nums; }
.ck.parent { font-weight: 600; margin-top: 6px; }
.ck.parent:first-child { margin-top: 0; }
.ck.kid { padding-left: 18px; }
.ck.kid:not(:has(input:checked)) .cl { color: var(--ink2); }

.in.big { font-size: var(--t-title); font-weight: 600; }

.rfile { display: flex; align-items: baseline; gap: 1ch; flex-wrap: wrap; }
.rfile .fmeta { color: var(--ink3); }
.rfile .file-actions { margin-left: auto; display: flex; gap: 1.5ch; }
/* Bordered, because "this is a control" has to be carried by the
   border, the ground and the pointer in a design with no radius and no
   shadow -- and .act was carrying it with colour alone, which reads as
   a link, not a button. */
.act {
  color: var(--acc); font-weight: 600; cursor: pointer;
  background: var(--panel); border: 1px solid var(--line); padding: 3px 8px;
  font-size: var(--t-small); letter-spacing: .06em; text-transform: uppercase;
}
.act .k { margin-right: .6ch; }
.act .file-picker input, .file-picker input { display: none; }

.kv { display: grid; grid-template-columns: 56px 1fr; gap: 4px 12px; align-items: baseline; }
.kvv { overflow-wrap: break-word; }

/* the skill spectrum as a weighted list */
.scols, .sp-list.stanced .sp-item {
  display: grid; grid-template-columns: 14px 1fr 96px 100px 56px;
  gap: 8px; align-items: center;
}
.scols {
  padding: 4px 4px; margin-top: 8px;
  font-size: var(--t-label); letter-spacing: .06em; color: var(--ink3);
}
.scols .r { text-align: right; }
.sp-list { list-style: none; margin: 0; padding: 0; }
.sp-list.stanced .sp-item { padding: 3px 4px; border-bottom: 1px solid var(--panel2); }
.sp-item .sp-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sp-item .sp-count { text-align: right; color: var(--ink3); font-variant-numeric: tabular-nums; }
.sp-x {
  background: none; border: 0; cursor: pointer; color: var(--bad);
  padding: 0; visibility: hidden;
}
.sp-list.stanced .sp-item:hover .sp-x,
.sp-list.stanced .sp-item:focus-within .sp-x { visibility: visible; }
.sp-list:not(.stanced) .sp-x { visibility: visible; }

/* The weight control. Five cells you click -- cell n is stance n -- with
   the real range input hidden behind them for the keyboard, the same
   arrangement .ck uses for a checkbox.

   These are drawn as a control, not as a readout, because for a while
   they were a readout: the slider was replaced by cells and nothing was
   bound to them, so the one thing on this row you are meant to change
   was the one thing that looked inert. Per the affordance rules below,
   a control gets a border, a brighter ground on hover, and a pointer. */
.wts { display: flex; gap: 2px; position: relative; }
.wts > .sp-range { position: absolute; opacity: 0; width: 0; height: 0; }
.wt {
  width: 17px; height: 11px; cursor: pointer;
  background: var(--panel2); border: 1px solid var(--line);
}
.wt.strongly-want, .wt.want { background: var(--ok); }
.wt.rather-not, .wt.strongly-avoid { background: var(--bad); }
/* the whole run lifts on hover, so it reads as one control rather than
   five unrelated squares, and the cell under the pointer leads */
.wts:hover .wt { border-color: var(--ink3); }
.wt:hover { background: var(--acc); border-color: var(--acc); }
/* keyboard focus lands on the hidden range; say so on the cells */
.wts:focus-within .wt { border-color: var(--acc); }

.sp-item[data-stance="strongly-want"] .sp-label,
.sp-item[data-stance="strongly-want"] .sp-stance { color: var(--ok); font-weight: 700; }
.sp-item[data-stance="want"] .sp-label,
.sp-item[data-stance="want"] .sp-stance { color: var(--ok); }
.sp-item[data-stance="neutral"] .sp-label,
.sp-item[data-stance="neutral"] .sp-stance { color: var(--ink2); }
.sp-item[data-stance="rather-not"] .sp-label,
.sp-item[data-stance="rather-not"] .sp-stance { color: var(--bad); }
.sp-item[data-stance="strongly-avoid"] .sp-label,
.sp-item[data-stance="strongly-avoid"] .sp-stance { color: var(--bad); font-weight: 700; }

/* the locations list keeps the three-column shape it had */
.sp-list:not(.stanced) .sp-item {
  display: grid; grid-template-columns: 14px 1fr auto; gap: 8px;
  align-items: baseline; padding: 2px 0;
}
.sp-remote > input[type="checkbox"] { margin: 0; }
.sp-count.unknown { color: var(--bad); font-style: italic; }
/* Under the rungs sit a note and a choice, and the rule goes BETWEEN
   them rather than above both: "nothing ticked means every rung"
   describes the boxes above it, while the unstated checkbox is its own
   decision about a bucket bigger than every rung put together. A single
   rule above the pair would have grouped the note with the control it
   has nothing to do with. */
/* A checkbox directly under a text input is a second question, not a
   label belonging to the first. .ck's own 2px of padding read as one
   control with its caption wrapped underneath -- most visibly the comp
   floor and "estimate missing pay from local average". */
.panel .body > .in + .ck { margin-top: 10px; }

/* The pace segments. Real radios, hidden behind drawn segments, the
   same arrangement .ck uses for a checkbox and .wts for the weights:
   the design draws the control, the real input stays for the keyboard
   and the form. Arrow keys move within the group for free because
   they are a radio group, which is most of why this is not a select. */
.seg-label { color: var(--ink2); margin-bottom: 6px; }
.segs { display: flex; gap: 4px; }
.seg { flex: 1 1 0; cursor: pointer; min-width: 0; }
.seg > input { position: absolute; opacity: 0; width: 0; height: 0; }
.seg-l {
  display: block; text-align: center; padding: 5px 4px;
  background: var(--panel); border: 1px solid var(--line);
  color: var(--ink2); letter-spacing: .06em;
}
.seg:hover .seg-l { background: var(--panel2); border-color: var(--ink3); }
.seg > input:checked + .seg-l {
  background: var(--acc); border-color: var(--acc); color: var(--bg);
}
.seg > input:focus-visible + .seg-l { border-color: var(--acc); }

.lv-note { margin-top: 6px; color: var(--ink2); line-height: 1.5; }
.lv-unstated {
  margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--panel2);
}

.sp-foot {
  display: flex; align-items: baseline; gap: 1ch;
  margin-top: 8px; padding-top: 6px; border-top: 1px solid var(--panel2);
  color: var(--ink2);
}
.sp-foot .act { margin-left: auto; }
.sp-search { position: relative; margin-bottom: 4px; }
.sp-menu {
  position: absolute; left: 0; right: 0; top: 100%; z-index: 30;
  background: var(--panel); border: 1px solid var(--line);
  max-height: 260px; overflow: auto;
}
.sg-opt {
  display: flex; gap: 1ch; width: 100%; text-align: left;
  padding: 3px 8px; background: none; border: 0; cursor: pointer;
}
.sg-opt:hover, .sg-opt.on { background: var(--panel2); }
.sg-opt .sg-kind { color: var(--ink3); }
.sg-opt .sg-count { margin-left: auto; color: var(--ink3); }

/* The preferences form is display:contents so its three panels are grid
   items; the résumé stack is a fourth item placed under the middle
   column. See prefs-section for why the résumé cannot live inside the
   form. */
form#prefs { display: contents; }
/* Nothing here places itself explicitly any more. The three grid items
   are the roles panel and two .pcol stacks, and auto-placement puts
   them in the three columns; the skills panel moved inside the third
   stack when PACE went after it, so its old grid-column pinned a
   child rather than an item. */
.ck.bare { display: inline-flex; padding: 0; }
.role-group + .role-group { margin-top: 6px; }

/* --- 5. stats ------------------------------------------------------- */

.kpis {
  display: flex; align-items: stretch;
  margin: 8px 8px 0;
  background: var(--panel); border: 1px solid var(--line);
}
.kpis .spacer { flex: 1 1 auto; }
.kpi { padding: 6px 12px; border-right: 1px solid var(--line); white-space: nowrap; }
.kpis .spacer + .kpi { border-right: 0; border-left: 1px solid var(--line); text-align: right; }
.kpi .kv { font-size: var(--t-kpi); font-weight: 700; line-height: 1.2; }
.kpi .kv.acc  { color: var(--acc); }
.kpi .kv.ok   { color: var(--ok); }
.kpi .kv.warn { color: var(--warn); }
.kpi .kv.bad  { color: var(--bad); }
.kpi .trail { color: var(--ink3); font-weight: 400; }

.sgrid {
  flex: 1 1 auto;
  display: grid; grid-template-columns: 1fr 1.4fr; grid-template-rows: auto 1fr;
  gap: 8px; padding: 8px;
}
.s-funnel { grid-column: 1; grid-row: 1; }
.s-rate   { grid-column: 1; grid-row: 2; }
.s-subs   { grid-column: 2; grid-row: 1 / span 2; }

.brow { display: grid; grid-template-columns: 90px 1fr 24px; gap: 8px; align-items: center; margin-bottom: 6px; }
.blabel { font-size: var(--t-label); letter-spacing: .06em; text-align: right; text-transform: uppercase; }
.blabel.ink3 { color: var(--ink3); } .blabel.acc { color: var(--acc); }
.blabel.warn { color: var(--warn); } .blabel.ok { color: var(--ok); }
.blabel.bad  { color: var(--bad); }
.btrack { height: 12px; background: var(--panel2); }
.bfill  { height: 100%; }
.bfill.ink3 { background: var(--ink3); } .bfill.acc { background: var(--acc); }
.bfill.warn { background: var(--warn); } .bfill.ok { background: var(--ok); }
.bfill.bad  { background: var(--bad); }
.bn { font-weight: 600; text-align: right; }

.s-subs .chart {
  flex: 1 1 auto; display: flex; align-items: stretch; gap: 6px;
  min-height: 0; overflow: hidden;
}
.s-subs .col { flex: 1 1 0; display: flex; flex-direction: column; min-width: 0; }
.s-subs .barbox { flex: 1 1 auto; min-height: 0; display: flex; align-items: flex-end; }
.s-subs .bar { width: 100%; background: var(--acc); position: relative; min-height: 1px; }
.s-subs .bc { position: absolute; top: 2px; left: 0; right: 0; text-align: center;
              color: var(--acc-ink); font-size: var(--t-label); }
.s-subs .wl { text-align: center; color: var(--ink3); font-size: var(--t-label); padding-top: 4px; }
/* the chart IS the body, so it must not also be told to stack: the
   column rule turned twelve vertical bars into twelve horizontal rules */
.s-subs .body.chart { flex-direction: row; }

.huge { font-size: var(--t-huge); font-weight: 700; color: var(--acc); line-height: 1.05; margin-bottom: 10px; }
.rcap { color: var(--ink2); margin: 10px 0 0; }
.panel > .body { flex: 1 1 auto; min-height: 0; }

/* #persona-body is the wrapper the view is rendered into. It has to
   pass the height through or every screen sits at its content height in
   a window that is taller: the stats grid stopped at 437px of a 900px
   window and left grey below the footer. */
#persona-body {
  flex: 1 1 auto;
  display: flex; flex-direction: column;
}/* the résumé form, inline under DETAILS */
#resume .derived-head,
.reconcile .derived-head { display: flex; align-items: baseline; gap: 1ch; margin: 14px 0 6px; }#resume .derived-head h4, .reconcile .derived-head h4 { margin: 0; font-size: var(--t-label); letter-spacing: .06em;
                           text-transform: uppercase; color: var(--ink3); }#resume .field-row, .reconcile .field-row { display: grid; grid-template-columns: 120px 1fr; gap: 6px 12px;
                     align-items: baseline; margin-bottom: 4px; }#resume input[type="text"], #resume input[type="email"], #resume input[type="date"], #resume textarea, #resume input:not([type]), .reconcile input[type="text"], .reconcile input[type="email"], .reconcile input[type="date"], .reconcile textarea, .reconcile input:not([type]) {
  width: 100%; padding: 3px 6px; background: var(--bg);
  border: 1px solid var(--line); color: var(--ink);
}#resume .saved-note, .reconcile .saved-note { color: var(--ok); }#resume .position-card, #resume .position-edit, .reconcile .position-card, .reconcile .position-edit {
  border: 1px solid var(--line); padding: 8px; margin-bottom: 8px;
}

/* the applied toggle: a real switch, because it is the one control that
   undoes something rather than doing it */
.applied-toggle { display: inline-flex; align-items: center; gap: 1ch; cursor: pointer; }
.applied-toggle > input { position: absolute; opacity: 0; width: 0; height: 0; }
.applied-toggle .switch {
  width: 22px; height: 11px; background: var(--panel2);
  border: 1px solid var(--line); position: relative; flex: 0 0 auto;
}
.applied-toggle .switch::after {
  content: ""; position: absolute; top: 1px; left: 1px;
  width: 7px; height: 7px; background: var(--ink3);
}
.applied-toggle.on .switch { background: var(--acc); border-color: var(--acc); }
.applied-toggle.on .switch::after { left: auto; right: 1px; background: var(--acc-ink); }
.applied-label { font-size: var(--t-label); letter-spacing: .06em; text-transform: uppercase; }

/* the history feed and note rows, in the reader */
.hist { list-style: none; margin: 8px 0 0; padding: 0; }
.h-row { display: grid; grid-template-columns: 90px 1fr 18px; gap: 8px; align-items: baseline;
         padding: 2px 0; border-bottom: 1px solid var(--panel2); }
.h-when { color: var(--ink3); font-size: var(--t-label); }
.h-verb { text-transform: uppercase; font-size: var(--t-label); letter-spacing: .06em; }
.x-del { background: none; border: 0; color: var(--bad); cursor: pointer; padding: 0; }
.x-del.spacer { visibility: hidden; }
.note-field { width: 100%; background: var(--bg); border: 1px solid var(--line);
              color: var(--ink); padding: 2px 6px; }
.app-status-pills { display: flex; gap: 1ch; flex-wrap: wrap; }
.status-pill { padding: 1px 6px; border: 1px solid var(--line); cursor: pointer;
               font-size: var(--t-label); letter-spacing: .06em; text-transform: uppercase; }
.status-pill.active { background: var(--panel2); border-color: var(--ink3); }
.status-pill.st-heard-back.active { color: var(--acc); border-color: var(--acc); }
.status-pill.st-advancing.active { color: var(--warn); border-color: var(--warn); }
.status-pill.st-offer.active     { color: var(--ok); border-color: var(--ok); }
.status-pill.st-rejected.active  { color: var(--bad); border-color: var(--bad); }
.tui-actions .k { font-size: var(--t-label); letter-spacing: .06em; color: var(--ink3); margin-right: 1ch; }
.flash { color: var(--ok); margin-right: 1.5ch; }


/* Narrow: the preferences columns reflow before anything else does.
   Three columns of checkboxes with a count right-aligned in each row
   need roughly 300px apiece to stop the count colliding with the
   label; below that they drop to two, then to one. Nothing needs
   releasing at each step any more: with auto-placement the items just
   rewrap. */
@media (max-width: 1100px) {
  .pcols { grid-template-columns: 1fr 1fr; }
}
@media (max-width: 720px) {
  .pcols { grid-template-columns: 1fr; }
}

/* Narrow: one column. The reader opens inline under the list, and
   nothing is sticky — a pane pinned against the top of a window it
   almost fills is a pane you cannot scroll past.

   The breakpoint moved from 980px to 1100px on 2026-09-14. Measured:
   at 1150px the reader pane is 604px and the fact band fits its three
   cells and the three action buttons on one row; at 1050px the pane is
   504px and it does not, whatever the minimums are set to. Two panes
   are worth having while the reader can hold a posting; below that the
   reader is better off with the whole window. */
@media (max-width: 1100px) {
  .panes { flex-direction: column; }
  .list-pane { top: auto; }
  .list-pane {
    flex: 0 0 auto; position: static; max-height: none; width: 100%;
  }
  .rows { overflow: visible; max-height: none; }
  .rhead { position: static; }
  .rbody { grid-template-columns: 1fr; }
  .rail { border-left: 0; border-top: 1px solid var(--line); }
}
.pblock { margin-top: 14px; }
.pblock .lbl { margin-bottom: 4px; }

/* ====================================================================
   6. Affordance — what is clickable, and what it does when you are
   about to click it.

   The Board direction is flat by design: no radius, no shadow, one
   border weight. That leaves nothing to say "this is a control"
   EXCEPT the border, the ground and the pointer, so those three have
   to carry it deliberately rather than by accident.

   Three rules, applied to everything interactive:
     · a control sits on --panel with a --line border (it is raised off
       the panel2 band, or inset into the panel body)
     · hovering brightens its ground and darkens its border
     · the pointer is a pointer, always
   ==================================================================== */

/* --- the reader's verbs --------------------------------------------- */
/* Apply, Peek and Reject were 10px labels in a hairline box, which is
   what a tag looks like, not what a button looks like. They are the
   three things this screen is FOR. */
.tui-btn {
  padding: 7px 10px;
  font-size: var(--t-base);
  letter-spacing: .04em;
  background: var(--panel);
  border: 1px solid var(--line);
  transition: background 90ms linear, border-color 90ms linear;
}
.tui-btn .bk {
  font-weight: 700; color: var(--ink3);
  font-size: var(--t-label); line-height: 1; opacity: .85;
}
.tui-btn .bl { line-height: 1.1; }
.tui-btn:hover {
  background: var(--panel2);
  border-color: var(--ink3);
}
.tui-btn:active { background: var(--line); }
.tui-btn:focus-visible { outline: 2px solid var(--acc); outline-offset: 1px; }

/* Apply is the primary and reads as one at rest, not only on hover */
.tui-btn[data-key="a"] {
  font-weight: 700;
  border-color: var(--acc);
}
.tui-btn[data-key="a"] .bk { color: var(--acc-ink); opacity: .75; }
.tui-btn[data-key="a"]:hover {
  background: color-mix(in srgb, var(--acc) 82%, black);
  border-color: color-mix(in srgb, var(--acc) 82%, black);
}
.tui-btn.danger { color: var(--bad); border-color: color-mix(in srgb, var(--bad) 45%, var(--line)); }
.tui-btn.danger:hover { background: color-mix(in srgb, var(--bad) 12%, var(--panel)); border-color: var(--bad); }

.racts { gap: 8px; align-items: center; }

/* --- everything else that answers a click --------------------------- */
.tab, .sec-tab, .act, .status-pill, .sp-x, .x-del, .sg-opt,
.ck, .applied-toggle, .env, .cell, .persona-btn, .dev-action,
.user-menu > summary, .file-picker {
  cursor: pointer;
}

.tab:hover:not(.on)     { background: color-mix(in srgb, var(--panel) 55%, transparent); color: var(--ink); }
.sec-tab:hover:not(.on):not(.off) { color: var(--ink); border-bottom-color: var(--ink3); }
.cell:hover, .user-menu > summary:hover { color: var(--ink); }
.act:hover               { background: var(--panel2); border-color: var(--acc); }

/* The call to action, and the one place this design fills a shape.
   Everything else here is a hairline on a flat ground, which is right
   for a screen of controls you use all day and wrong for the single
   thing a visitor who is not signed in is meant to do: SIGN IN sat in
   .cell, the same chrome as the readout beside it, so the only button
   on the bar looked exactly like the two numbers you can only read.

   Compound selectors because .cell and .act each set colour, ground
   and padding of their own, and a bare .cta would only tie with them. */
.cell.cta, .act.cta, .banner-actions button, .banner-actions .primary,
.plan-cta.primary, .plan-cta.secondary {
  /* the same pair Apply uses -- .tui-btn[data-key="a"] -- so every
     affirmative button on the product is one colour rather than each
     screen picking its own */
  background: var(--acc); color: var(--acc-ink); border: 1px solid var(--acc);
  font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  cursor: pointer;
}
.cell.cta { border: 0; padding: 0 14px; }
.act.cta  { padding: 5px 12px; }
/* .act.accent is the skills panel's "+ ADD ALL" -- an affirmative, and
   it had no rule of its own, so it rendered as a plain .act */
.act.accent { color: var(--acc); font-weight: 700; }
.act.accent:hover { text-decoration: underline; }
.cell.cta:hover, .act.cta:hover, .banner-actions button:hover,
.banner-actions .primary:hover, .plan-cta.primary:hover, .plan-cta.secondary:hover {
  background: var(--ink); border-color: var(--ink);
  color: var(--bg); text-decoration: none;
}

/* --- the guest banner -----------------------------------------------
   #anonymous-banner, .banner-body and .banner-actions had NO rules.
   They were defined in style.css and nothing replaced them when it
   was deleted, so the whole thing rendered as bare left-aligned flow
   content: a heading, a sentence and a browser-default button, none
   of which reads as the one thing a visitor is here to do.

   Centred, because it is the only thing on the screen addressed to
   somebody who has not signed in, and an accent rule along the top to
   pull the eye before the queue below it does. */
/* The one thing on screen addressed to somebody who has not set up yet.
   An amber outline all the way round rather than a rule along the top:
   it has to win against a queue of a hundred rows below it, and --warn
   is the only colour on the page not already spoken for by a state.
   The whole box is the target, not just the button -- see banner-click
   in views. */
#anonymous-banner, .banner {
  margin: 8px 8px 0;
  background: var(--panel);
  border: 2px solid var(--warn);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--warn) 25%, transparent);
  cursor: pointer;
}
#anonymous-banner:hover, .banner:hover {
  background: color-mix(in srgb, var(--warn) 7%, var(--panel));
}
.banner-body {
  display: flex; flex-direction: column; align-items: center;
  gap: 8px; padding: 20px 16px; text-align: center;
}
.banner-body h3 {
  margin: 0; font-size: var(--t-title); font-weight: 700;
  letter-spacing: .08em; text-transform: uppercase; color: var(--ink);
}
.banner-body p { margin: 0; color: var(--ink2); max-width: 46ch; line-height: 1.5; }
.banner-actions { margin-top: 4px; }
.banner-actions button, .banner-actions .primary {
  padding: 9px 22px; font-size: var(--t-base);
  display: inline-block; text-decoration: none;
}

/* --- the sign-in dialog ---------------------------------------------
   A real <dialog>, opened with showModal(). Everything about WHERE it
   sits is the browser's: the top layer centres it over the page and
   ::backdrop dims what is behind, so none of it depends on a rule
   being present. The rules that used to fake this (.backdrop, .card,
   .auth-card) went with style.css, and what was left rendered as
   plain flow content at the end of <body> — which is where it was. */
#auth-modal {
  padding: 0; border: 1px solid var(--line); background: var(--panel);
  color: var(--ink); max-width: min(92vw, 400px);
}
#auth-modal::backdrop { background: rgb(0 0 0 / 55%); }
#auth-modal-body { padding: 16px; }
#auth-modal h2 {
  margin: 0 0 6px; font-size: var(--t-base); font-weight: 700;
  letter-spacing: .06em; text-transform: uppercase;
}
#auth-modal .hint { margin: 0 0 12px; color: var(--ink2); line-height: 1.5; }
#auth-modal .mockup-hint { margin: 0 0 12px; color: var(--acc); line-height: 1.5; }
#auth-modal .in { width: 100%; }
#auth-modal .actions {
  display: flex; align-items: center; gap: 1.5ch;
  margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--panel2);
}
/* the affirmative sits at the right end, the way it does in the foot */
#auth-modal .actions > .cta, #auth-modal .actions > .hint { margin-left: auto; }
.status-pill:hover       { background: var(--panel2); border-color: var(--ink3); }
.sp-x:hover, .x-del:hover { background: color-mix(in srgb, var(--bad) 15%, transparent); }
.ck:hover .cl, .ck:hover .box { color: var(--ink); }
.ck:hover .box           { border-color: var(--ink); }
.applied-toggle:hover .switch { border-color: var(--ink3); }
.in:hover                { border-color: var(--ink3); }.in:focus, .note-field:focus, #resume input:focus, #resume textarea:focus, .reconcile input:focus, .reconcile textarea:focus {
  outline: none; border-color: var(--acc);
}

/* a labelled rule between groups in a column */
/* The number in a panel head. Same treatment the checklist's step
   markers get, so a numbered step looks like a numbered step wherever
   it appears. */
.step-n {
  flex: 0 0 auto; width: 16px; height: 16px; margin-right: 1ch;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--acc); color: var(--acc-ink);
  font-size: var(--t-label); font-weight: 700;
}

.sep { display: flex; align-items: center; gap: 1ch; margin: 4px 0 0; }
.sep:first-child { margin-top: 0; }
.sep-l {
  font-size: var(--t-label); letter-spacing: .08em; text-transform: uppercase;
  color: var(--ink3); font-weight: 600;
}
.sep-r { flex: 1 1 auto; height: 1px; background: var(--line); }
.kv-edit { grid-column: 1 / -1; text-align: right; margin-bottom: 2px; }

/* --- the profile's bands -------------------------------------------- */
/* Full-width rules, because they separate SECTIONS of the screen rather
   than items in a column. */
.bands { display: flex; flex-direction: column; gap: 8px; padding: 8px; }
.band  { display: flex; flex-direction: column; gap: 8px; }
/* PREFERENCES / RESUME / DETAILS divide the SCREEN, where .sep
   elsewhere divides items in a column -- so they are heavier than the
   hairline-and-a-grey-word that reads fine inside a panel: the label
   is ink rather than ink3, the rule is 2px, and there is real air
   above so the eye registers a break rather than a caption. */
.bands .sep { margin: 22px 0 2px; align-items: center; }
.bands .sep:first-child { margin-top: 2px; }
.bands .sep-l {
  color: var(--ink); font-weight: 700; font-size: var(--t-base);
  letter-spacing: .12em;
}
.bands .sep-r { height: 2px; background: var(--line); }
.bands .pcols { padding: 0; }/* The résumé form is the page, not a dialog: its fields get the same
   grid every other labelled thing on this screen gets. */
#resume .derived-fields,
.reconcile .derived-fields { display: grid; grid-template-columns: 120px 1fr; gap: 4px 12px;
                          align-items: baseline; }#resume .field-row, .reconcile .field-row { display: contents; }#resume .field-label, .reconcile .field-label { font-size: var(--t-label); letter-spacing: .06em;
                       text-transform: uppercase; color: var(--ink3); }#resume .entries, .reconcile .entries { margin-top: 6px; }/* An entry is: the controls that reorder or remove it, the fields that
   describe it, and the highlights that fill it out. auto-fit columns
   gave the × a column of its own the full height of the card, which is
   why the first attempt looked like a spreadsheet. */
#resume .position-edit, #resume .edu-edit, #resume .proj-edit, #resume .cert-edit, #resume .entry-edit,
.reconcile .position-edit, .reconcile .edu-edit, .reconcile .proj-edit, .reconcile .cert-edit, .reconcile .entry-edit {
  position: relative;
  display: grid;
  grid-template-columns: minmax(220px, 1fr) minmax(240px, 1.4fr);
  gap: 6px 12px; align-items: start;
  /* room on the left for the arrows, on the right for the × */
  padding: 8px 28px 8px 32px;
  border: 1px solid var(--line); margin-bottom: 8px;
}/* Remove goes to the top-right corner, where a remove goes. It was in
   the first column, which read as a field label rather than as a way
   out of the card. */
#resume .pos-x,
.reconcile .pos-x {
  position: absolute; top: 4px; right: 4px;
  border: 0; background: none; color: var(--bad);
  padding: 3px; line-height: 1;
}/* The reorder arrows belong to the whole entry, so they sit against the
   middle of it rather than against its first line. Positioned rather
   than placed: `grid-row: 1 / -1` spans the EXPLICIT rows, and these
   cards have one explicit row and the rest implicit, so it pinned them
   to the top exactly as before. */
#resume .pos-move-col,
.reconcile .pos-move-col {
  position: absolute; left: 5px; top: 50%; transform: translateY(-50%);
  display: flex; flex-direction: column; gap: 3px;
}#resume .pos-move, .reconcile .pos-move { padding: 0 3px; line-height: 1.1; }#resume .pos-fields-col, .reconcile .pos-fields-col { grid-column: 1; display: grid; gap: 4px; }#resume .pos-dates-col, .reconcile .pos-dates-col { grid-column: 2; display: grid; gap: 4px; align-content: start; }#resume .pos-notes-col, #resume .entry-notes, .reconcile .pos-notes-col, .reconcile .entry-notes { grid-column: 1 / -1; }#resume .pos-field, #resume .entry-field, .reconcile .pos-field, .reconcile .entry-field { display: grid; gap: 2px; }#resume textarea, .reconcile textarea { min-height: 4.5em; }#resume .add-btn, #resume .pos-x, #resume .pos-move, .reconcile .add-btn, .reconcile .pos-x, .reconcile .pos-move {
  background: var(--panel); border: 1px solid var(--line); cursor: pointer;
  padding: 2px 8px; font-size: var(--t-label); letter-spacing: .06em;
  text-transform: uppercase;
}#resume .add-btn:hover, #resume .pos-x:hover, #resume .pos-move:hover, .reconcile .add-btn:hover, .reconcile .pos-x:hover, .reconcile .pos-move:hover {
  background: var(--panel2); border-color: var(--ink3);
}#resume label, .reconcile label { font-size: var(--t-label); letter-spacing: .06em;
                text-transform: uppercase; color: var(--ink3); display: block; }


/* ====================================================================
   8. The screens the redesign never opened.

   style.css defined these and its deletion took them with it, so each
   rendered as raw HTML: browser-default buttons, bulleted lists, and
   headings at whatever size the UA picked. Found by walking every
   page a real signed-up account can reach and listing, per page, the
   classes present in the DOM that no rule here matches — see
   test/coverage_walk.clj, which is that walk kept as a test.
   ==================================================================== */

/* --- the setup checklist -------------------------------------------- */
/* Two steps between a fresh account and a ranked queue. It was a <ul>
   with bullets and "1Please upload your resume" run together, because
   .step-mark had no rule to separate the marker from the words. */
/* The checklist pairs with the banner above it, so it takes the same
   amber -- as a top rule rather than a full outline, because two boxes
   outlined alike would be two things competing to be the one to read
   first, and the banner carries the button. */
.setup-checklist {
  margin: 8px 8px 0; padding: 16px;
  background: var(--panel);
  border: 1px solid var(--line); border-top: 2px solid var(--warn);
  display: flex; flex-direction: column; align-items: center; text-align: center;
}
/* the step links are controls, so they look like the rest of them */
.setup-steps li a {
  border: 1px solid var(--line); background: var(--panel2);
  padding: 2px 8px; text-decoration: none;
}
.setup-steps li a:hover { border-color: var(--acc); background: var(--panel); }
.setup-lead { margin: 0 0 10px; color: var(--ink); }
.setup-steps {
  list-style: none; margin: 0; padding: 0;
  display: inline-grid; gap: 6px; justify-items: start; text-align: left;
}
.setup-steps li { display: flex; align-items: baseline; gap: 1.5ch; }
.step-mark {
  flex: 0 0 auto; width: 18px; height: 18px;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--line); background: var(--panel2);
  color: var(--ink2); font-size: var(--t-label); font-weight: 700;
}
.setup-steps li.done .step-mark {
  background: var(--ok); border-color: var(--ok); color: var(--acc-ink);
}
.setup-steps li.todo .step-mark { border-color: var(--acc); color: var(--acc); }
.setup-steps li.done { color: var(--ink2); }
.setup-steps li a { color: var(--acc); font-weight: 600; }
.setup-note { margin: 10px 0 0; color: var(--ink2); line-height: 1.5; }

/* --- account ---------------------------------------------------------- */
.account-view { padding: 8px; display: flex; flex-direction: column; gap: 8px; }
.account-head h2, .pricing-head h3, .why-card h3 {
  margin: 0; font-size: var(--t-title); font-weight: 700;
  letter-spacing: .08em; text-transform: uppercase;
}
.card { background: var(--panel); border: 1px solid var(--line); padding: 14px 16px; }
.account-current .current-left { display: grid; gap: 6px; }
.current-plan-line { display: flex; align-items: baseline; gap: 1.5ch; }
.plan-badge {
  padding: 2px 8px; border: 1px solid var(--line); background: var(--panel2);
  font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
}
.plan-badge.hunt, .plan-badge.maintain {
  background: var(--acc); border-color: var(--acc); color: var(--acc-ink);
}
.renew { color: var(--ink3); }
.pricing-head { padding: 6px 2px 0; }
.section-desc { margin: 6px 0 0; color: var(--ink2); max-width: 78ch; line-height: 1.5; }

/* --- the plan grid ---------------------------------------------------- */
.pricing-grid {
  display: grid; gap: 8px;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  align-items: stretch;
}
.plan-card {
  position: relative; display: flex; flex-direction: column; gap: 8px;
  padding: 16px; background: var(--panel); border: 1px solid var(--line);
}
.plan-card.featured { border-color: var(--acc); }
.plan-card.is-current { background: var(--panel2); }
.plan-ribbon {
  position: absolute; top: -1px; right: -1px;
  padding: 2px 8px; background: var(--acc); color: var(--acc-ink);
  font-size: var(--t-label); font-weight: 700;
  letter-spacing: .06em; text-transform: uppercase;
}
.plan-name-row { display: flex; align-items: baseline; gap: 1.5ch; }
.plan-name-row h3 {
  margin: 0; font-size: var(--t-title); font-weight: 700;
  letter-spacing: .06em; text-transform: uppercase;
}
.current-pill {
  padding: 1px 6px; border: 1px solid var(--ink3); color: var(--ink2);
  font-size: var(--t-label); letter-spacing: .06em; text-transform: uppercase;
}
.plan-price { font-size: var(--t-kpi); font-weight: 700; color: var(--ink); }
.cadence { font-size: var(--t-base); font-weight: 400; color: var(--ink3); }
.plan-tagline { margin: 0; color: var(--ink2); line-height: 1.5; min-height: 3em; }
.plan-features { list-style: none; margin: 0; padding: 0; display: grid; gap: 5px; }
.plan-features li { display: flex; align-items: baseline; gap: 1ch; color: var(--ink2); }
.plan-features li.no { color: var(--ink2); }
.feat-mark { flex: 0 0 auto; color: var(--ok); font-weight: 700; }
.plan-features li.no .feat-mark { color: var(--bad); }
/* the button sits at the bottom whatever the tagline's length */
.plan-cta { margin-top: auto; padding: 9px 12px; width: 100%; }
.plan-cta.current {
  background: var(--panel2); border: 1px dashed var(--line); color: var(--ink3);
  cursor: default; font-weight: 700;
  letter-spacing: .06em; text-transform: uppercase;
}
.plan-cta.secondary { background: none; color: var(--acc); }
.plan-cta.secondary:hover { background: var(--ink); color: var(--bg); }

/* --- why ------------------------------------------------------------- */
.why-grid {
  display: grid; gap: 12px 16px; margin-top: 10px;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
}
.why-item h4 {
  margin: 0 0 4px; font-size: var(--t-base); font-weight: 700; color: var(--ink);
}
.why-item p { margin: 0; color: var(--ink2); line-height: 1.5; }

/* --- the plan cell, and the locked note ------------------------------- */
/* The tier, in the chrome, always a link to the page that changes it. */
.plan-cell { text-decoration: none; }
.plan-cell b { color: var(--ink); font-weight: 700; }
.plan-cell b.hunt, .plan-cell b.maintain { color: var(--acc); }
.plan-cell:hover { background: var(--panel); color: var(--ink); }
.plan-cell:hover b { color: var(--acc); }
.locked-note .act.cta { display: inline-block; vertical-align: baseline; }
.locked-note .hint { color: var(--ink2); }


/* --- the resume editor's own furniture -------------------------------- */
/* .btn was style.css's button, used across the resume editor and the
   reconcile view; without it every one rendered as a browser default
   in a screen where nothing else does. Same three rules the affordance
   section sets out: a ground, a border, a pointer. */
.btn {
  padding: 5px 12px;
  border: 1px solid var(--line); background: var(--panel);
  color: var(--ink2); cursor: pointer;
  font-size: var(--t-label); letter-spacing: .06em; text-transform: uppercase;
  white-space: nowrap;
}
.btn:hover { background: var(--panel2); border-color: var(--ink3); color: var(--ink); }
.btn.primary {
  background: var(--acc); border-color: var(--acc); color: var(--acc-ink);
  font-weight: 700;
}
.btn.primary:hover { background: var(--ink); border-color: var(--ink); color: var(--bg); }
.btn.danger { color: var(--bad); text-decoration: none; display: inline-block; }
.btn.danger:hover { background: color-mix(in srgb, var(--bad) 12%, transparent); }
.btn:disabled { opacity: .45; cursor: default; }
.btn:disabled:hover { background: var(--panel); border-color: var(--line); }
.btn-row { display: inline-flex; gap: 1ch; align-items: center; }

/* Experience / Education / Projects / Certifications. Browser-default
   headings in a screen where every other label is a small uppercase
   rule. */
.section-title {
  margin: 14px 0 8px; font-size: var(--t-label); font-weight: 700;
  letter-spacing: .08em; text-transform: uppercase; color: var(--ink3);
  border-bottom: 1px solid var(--panel2); padding-bottom: 4px;
}
.entry-group { margin-top: 4px; }
.entry-list { display: grid; gap: 8px; }
.entry-blank { color: var(--ink2); padding: 6px 0; }
.add-btn { margin-top: 8px; }
.experience { display: block; }

/* one stored position / degree / project */
.entry {
  position: relative; padding: 12px 14px;
  background: var(--panel); border: 1px solid var(--line);
}
.pos-company { font-weight: 700; color: var(--ink); }
.pos-role    { color: var(--ink); }
.pos-loc, .pos-date, .pos-dates, .pos-to, .pos-span, .pos-ongoing { color: var(--ink3); }
.pos-dates-row { display: flex; align-items: baseline; gap: 1ch; flex-wrap: wrap; }
.pos-notes { color: var(--ink2); line-height: 1.5; }
.pos-spacer { flex: 1 1 auto; }
.field-value { color: var(--ink); }
.readonly { color: var(--ink2); }
.muted { color: var(--ink3); }
.autogrow { resize: vertical; }
select.degree-select {
  background: var(--panel); border: 1px solid var(--line); color: var(--ink);
  padding: 3px 6px; font: inherit;
}
select.degree-select:hover { border-color: var(--ink3); }

/* --- the parse review ------------------------------------------------
   Reached by uploading a resume: it takes over the body until resolved.
   It renders the same field components the resume editor does, but
   lives OUTSIDE #resume -- so every rule scoped to that id missed it,
   and it came out as browser-default inputs jammed against their
   labels. The rules above now carry a .reconcile twin; these are the
   parts only this view has. */
.reconcile {
  margin: 8px; padding: 16px 18px;
  background: var(--panel); border: 1px solid var(--line);
  border-top: 2px solid var(--acc);
}
.reconcile > h3 {
  margin: 0 0 4px; font-size: var(--t-title); font-weight: 700;
  letter-spacing: .08em; text-transform: uppercase; color: var(--ink);
}
.reconcile > .hint { margin: 0 0 14px; color: var(--ink2); line-height: 1.5; }
.reconcile h4 {
  margin: 14px 0 8px; font-size: var(--t-label); font-weight: 700;
  letter-spacing: .08em; text-transform: uppercase; color: var(--ink3);
  border-bottom: 1px solid var(--panel2); padding-bottom: 4px;
}
.head-actions { display: flex; align-items: center; gap: 1ch; margin: 10px 0 14px; }
.reconcile-old, .reconcile-new { display: grid; gap: 6px; }
.reconcile-old { color: var(--ink2); }
.rm-actions { display: flex; align-items: center; gap: 1ch; margin-top: 10px; }
.rm-msg { color: var(--ink2); line-height: 1.5; }

/* label-and-field pairs inside an entry: they were running together
   as "TitleSenior Software Engineer" with no gap at all */
.pos-field {
  display: inline-flex; align-items: baseline; gap: 1ch;
  margin: 0 12px 6px 0;
  font-size: var(--t-label); letter-spacing: .06em;
  text-transform: uppercase; color: var(--ink3);
}
.pos-field input, .pos-field select { text-transform: none; letter-spacing: normal; }
.pos-fields-col { display: flex; flex-wrap: wrap; align-items: baseline; }
.pos-ongoing-col { display: inline-flex; align-items: baseline; gap: 1ch; }
.pos-ongoing { color: var(--ink2); text-transform: none; letter-spacing: normal; }

/* --- the parse status card -------------------------------------------
   A resume upload patches this fragment in and the stream's next
   render replaces it, so everything here has to be CSS: a patched
   fragment cannot rely on a script running. It promised a spinner, a
   moving bar and one stage message at a time, and delivered all five
   messages stacked in a column with no card around them, because
   style.css held every rule and nothing replaced them.

   The progress is honest theatre. We cannot know how far along a
   parse is, so the bar eases toward 92% over the typical parse time
   and holds there rather than pretending to finish; the last stage
   message holds too. The real completion is the review arriving,
   which replaces this whole card. */
.parse-status {
  position: fixed; right: 12px; bottom: 34px; z-index: 40;
  width: min(92vw, 340px); padding: 14px 16px;
  background: var(--panel);
  border: 1px solid var(--line); border-left: 2px solid var(--acc);
}
.ps-head { display: flex; align-items: center; gap: 1.5ch; margin-bottom: 10px; }
.ps-head strong { color: var(--ink); font-weight: 700; }

/* A rotating square, not a ring: the reset sets border-radius: 0
   !important on everything, so a circular spinner is not available
   here and a square one is honest about the design it lives in. */
.spinner {
  flex: 0 0 auto; width: 11px; height: 11px;
  border: 2px solid var(--panel2); border-top-color: var(--acc);
  animation: ps-spin 900ms linear infinite;
}
@keyframes ps-spin { to { transform: rotate(360deg); } }

.ps-track { height: 3px; background: var(--panel2); overflow: hidden; }
.ps-bar {
  height: 100%; width: 0; background: var(--acc);
  animation: ps-fill 20s cubic-bezier(.15,.85,.25,1) forwards;
}
@keyframes ps-fill { to { width: 92%; } }

/* one message at a time, in the same place, and the last one stays */
.ps-steps { position: relative; height: 2.6em; margin-top: 10px; }
.ps-step {
  position: absolute; inset: 0; opacity: 0;
  color: var(--ink2); line-height: 1.3;
}
.ps-step:nth-child(1) { animation: ps-say 4.4s ease-out 0s    forwards; }
.ps-step:nth-child(2) { animation: ps-say 4.4s ease-out 4s    forwards; }
.ps-step:nth-child(3) { animation: ps-say 4.4s ease-out 8s    forwards; }
.ps-step:nth-child(4) { animation: ps-say 4.4s ease-out 12s   forwards; }
/* the last holds, because we do not know when it ends */
.ps-step:nth-child(5) { animation: ps-hold 1s ease-out 16s    forwards; }
@keyframes ps-say  { 0% {opacity:0} 12% {opacity:1} 88% {opacity:1} 100% {opacity:0} }
@keyframes ps-hold { from {opacity:0} to {opacity:1} }

@media (prefers-reduced-motion: reduce) {
  .spinner { animation: none; border-top-color: var(--acc); }
  .ps-bar  { animation: none; width: 50%; }
  /* still one at a time, just without the fades */
  .ps-step { animation-duration: 0.01s !important; }
}

/* the answers band on Profile. Its own class names, not the résumé
   form's: it sits inside #resume, and every #resume .field-* rule would
   otherwise lay it out as the résumé's two-column grid. */
#answers .ans-row { display: grid; grid-template-columns: 220px minmax(0, 1fr); gap: 6px 12px;
                    align-items: baseline; margin-bottom: 4px; }
#answers .ans-label { color: var(--ink2); }
#answers .ans-label .hint { font-size: var(--t-label); }
#answers .ans-value { min-width: 0; }
#answers input[type="text"] { width: 100%; padding: 3px 6px; background: var(--bg);
                              border: 1px solid var(--line); color: var(--ink); }
#answers input[type="text"]:focus { border-color: var(--ink3); }
#answers .guess { display: inline-block; color: var(--ink3); font-size: var(--t-label); margin-top: 2px; }
#answers .ans-head { margin: 14px 0 6px; font-size: var(--t-label); letter-spacing: .06em;
                     text-transform: uppercase; color: var(--ink3); }
#answers .ans-choice { display: inline-flex; align-items: center; gap: .5ch; margin-right: 2ch;
                       white-space: nowrap; }
#answers .ans-days { width: 6ch; padding: 1px 4px; background: var(--bg);
                     border: 1px solid var(--line); color: var(--ink); }
@media (max-width: 600px) {
  #answers .ans-row { grid-template-columns: minmax(0, 1fr); gap: 2px; margin-bottom: 8px; }
}
