/*
 * Worksider 2.0.
 *
 * The rule this file is written against: the homepage IS the list of jobs.
 * No hero, no gradient, no three feature cards, no testimonial carousel. A
 * student opening a Telegram link on a phone should see roles in the first
 * screenful, because that is the only thing they came for.
 *
 * One accent colour, thin dividers, no shadows, no rounded cards.
 */

/*
 * THE COLOURS, AND BOTH THEMES OF THEM.
 *
 * Every colour on this site is named here and nowhere else. Nothing below this
 * block writes a hex code: a rule asks for --line or --bg-soft and is handed
 * whichever of the two themes the reader is in. That is the only way a second
 * theme stays true, because a literal buried nine hundred lines down is a rule
 * that only ever had one.
 *
 * WHY light-dark() AND NOT A SECOND BLOCK UNDER A MEDIA QUERY. A media query
 * means writing the whole list three times: once light, once for the system
 * preference, once for the manual override. Three lists drift, and the one
 * that drifts is the one nobody is looking at. light-dark() resolves against
 * `color-scheme`, so the list is written once and the toggle is a single
 * attribute on <html>.
 *
 * THE PLAIN VALUES COME FIRST AND @supports REPLACES THEM. A browser too old
 * for light-dark() keeps the light ones and sees exactly the site that was
 * here before any of this, which is a perfectly good thing to be shown. It is
 * the same trade the forms make with the (i) buttons: ship the state that
 * works, fold it away only where it is understood. This block is the only
 * place in the file where a value is written twice, and that is what it is
 * for.
 */

:root {
  /* Brand. --brand is the mark and accents; --brand-ink is anything a
     person has to READ: #09AAD4 on white is about 2.7:1 and fails AA. In the
     dark theme the pair swaps jobs, because a colour dark enough to read on
     white is too dark to see on navy. */
  --brand: #09AAD4;
  --brand-ink: #0A7FA6;
  --brand-wash: #F7FCFE;            /* the faintest wash of it */

  /* Text. */
  --ink: #001D38;                   /* headings and body text: the old navy */
  --muted: #5B6670;                 /* meta, labels, anything secondary */

  /* Surfaces. --bg is the page; --surface is anything that has to read as
     sitting ON the page: an input, a menu, a card, a chip. They are the same
     white in the light theme and deliberately are not in the dark one, which
     is the whole reason they are two names. */
  --bg: #FFFFFF;
  --bg-soft: #F5F7FA;               /* soft blocks, row hover, tinted boxes */
  --surface: #FFFFFF;
  --logo-bg: #FFFFFF;               /* STAYS LIGHT: see the note on .row-logo */

  /* Lines. */
  --line: #E6E9EE;                  /* dividers and the default border */
  --line-hover: #CBD4DE;            /* a border under the pointer */
  --line-file: #C7D0DB;             /* the dashed edge of a file input */
  --line-dash: #D5DCE5;             /* the dashed edge of an empty state */
  --box-br: #C2CBD6;                /* the square a checkbox draws itself */

  /* The one other colour, and it earns it. */
  --pay: #0B7A45;                   /* pay figures */

  /* The primary button, and the solid ink fill behind an avatar or a
     chosen chip. Both put text on a filled shape, so both have to name the
     colour of that text: white stops being the right answer the moment the
     fill is a pale one, which in the dark theme it is. */
  --btn-bg: #0A7FA6;
  --btn-bg-hover: #096A8C;
  --btn-fg: #FFFFFF;
  --fill: #001D38;
  --fill-fg: #FFFFFF;
  --avatar-bg: #001D38;             /* the same fill, turned down: see .avatar */
  --avatar-fg: #FFFFFF;
  --t-fg: #FFFFFF;                  /* text on a Learn AI tier colour */

  /* Four tints. Each pairs a readable ink with a wash pale enough in the
     light theme, and deep enough in the dark one, to sit under 16px of
     stroke without muddying it. */
  --sky: #0A7FA6;
  --sky-bg: #E6F5FA;
  --sky-br: #09AAD4;
  --leaf: #0B7A45;
  --leaf-bg: #E7F4EC;
  --leaf-br: #56B288;
  --plum: #6244A3;
  --plum-bg: #EFEBFA;
  --plum-br: #9C86D2;
  --sun: #9A5B0C;
  --sun-bg: #FBF1E2;
  --sun-br: #D8A660;

  /* States: something worked, something failed, something is waiting. */
  --good: #14603A;
  --good-bg: #F0F9F4;
  --good-br: #BFE3CD;
  --bad: #8A1C25;
  --bad-bg: #FDF1F2;
  --bad-br: #F3C9CD;
  --bad-line: #D0555F;              /* the border of the box an error is about */
  --bad-wash: #FFFBFB;              /* and its fill */
  --bad-ic: #C24450;                /* the mark beside the sentence */
  --warn: #8A5B00;
  --warn-ink: #6B4A00;              /* the verify bar, which runs darker */
  --warn-bg: #FFF6E5;
  --warn-br: #F5D9A0;
  --state-wait-br: #EFD8B2;         /* the dashboard's amber pill */
  --info: #0A6C8C;
  --info-bg: #E8F7FC;
  --info-br: #B9E6F3;
  --flag: #7A4A08;                  /* all 25 levels: allowed to shout once */
  --flag-bg: #FDF3E7;
  --flag-br: #F0D9B5;

  /* Somebody else's colours, which do not get a second theme. Telegram
     blue is Telegram blue, and the dev bar is meant to be unmissable. */
  --tg: #229ED9;
  --tg-hover: #1B8ABF;
  --tg-fg: #FFFFFF;
  --devbar-bg: #3B2C00;
  --devbar-fg: #FFD97A;

  /* Shadows and focus rings. A shadow tinted with the page's own navy
     disappears on a navy page, so the dark theme uses plain black, and more
     of it. */
  --shadow: rgba(0, 29, 56, .10);
  --shadow-soft: rgba(0, 29, 56, .08);
  --ring: rgba(9, 170, 212, .18);
  --ring-strong: rgba(9, 170, 212, .25);
  --bad-ring: rgba(208, 85, 95, .2);

  --w: 920px;                       /* text stays readable; the list never sprawls */
  --r: 4px;
}

/*
 * The two glyphs that are drawn rather than coloured: the chevron on a select
 * and the tick a checkbox fills with. Both are an SVG in a data URI, which is
 * an opaque string with the colour baked into it, so neither can take a token
 * the way everything else here does. They are the one thing on the site that
 * needs the media query as well as the attribute.
 */
:root {
  --chevron-light: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%235B6670' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 6l4 4 4-4'/%3E%3C/svg%3E");
  --chevron-dark: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%239DB2C4' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 6l4 4 4-4'/%3E%3C/svg%3E");
  --tick-light: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23fff' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2.5 8.5l4 4 7-9'/%3E%3C/svg%3E");
  --tick-dark: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%230B1724' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2.5 8.5l4 4 7-9'/%3E%3C/svg%3E");

  --sel-chevron: var(--chevron-light);
  --opt-tick: var(--tick-light);
}

@supports (color: light-dark(#fff, #000)) {
  :root {
    /* BOTH VALUES, so light-dark() has something to resolve against and so the
       browser paints its own scrollbars, form controls, spinners and autofill
       to match. Without this line the page goes dark and the scrollbar does
       not. */
    color-scheme: light dark;

    --brand: light-dark(#09AAD4, #22B8E0);
    --brand-ink: light-dark(#0A7FA6, #5CCBEB);
    --brand-wash: light-dark(#F7FCFE, #12303F);
    --ink: light-dark(#001D38, #E7EFF6);
    --muted: light-dark(#5B6670, #9DB2C4);
    --bg: light-dark(#FFFFFF, #0B1724);
    --bg-soft: light-dark(#F5F7FA, #132434);
    --surface: light-dark(#FFFFFF, #16283A);
    --logo-bg: light-dark(#FFFFFF, #E8EEF4);
    --line: light-dark(#E6E9EE, #22394E);
    --line-hover: light-dark(#CBD4DE, #36566F);
    --line-file: light-dark(#C7D0DB, #36566F);
    --line-dash: light-dark(#D5DCE5, #2F4A62);
    --box-br: light-dark(#C2CBD6, #3E5E79);
    --pay: light-dark(#0B7A45, #3FD08A);
    --btn-bg: light-dark(#0A7FA6, #22B8E0);
    --btn-bg-hover: light-dark(#096A8C, #52CDEE);
    --btn-fg: light-dark(#FFFFFF, #04222E);
    --fill: light-dark(#001D38, #E7EFF6);
    --fill-fg: light-dark(#FFFFFF, #0B1724);
    --avatar-bg: light-dark(#001D38, #2A4A66);
    --avatar-fg: light-dark(#FFFFFF, #E7EFF6);
    --t-fg: light-dark(#FFFFFF, #0B1724);
    --sky: light-dark(#0A7FA6, #5CCBEB);
    --sky-bg: light-dark(#E6F5FA, #12313F);
    --sky-br: light-dark(#09AAD4, #2E8AAC);
    --leaf: light-dark(#0B7A45, #5FD69C);
    --leaf-bg: light-dark(#E7F4EC, #123027);
    --leaf-br: light-dark(#56B288, #3A7F60);
    --plum: light-dark(#6244A3, #B6A1E8);
    --plum-bg: light-dark(#EFEBFA, #241F3A);
    --plum-br: light-dark(#9C86D2, #6A57A8);
    --sun: light-dark(#9A5B0C, #E8A94E);
    --sun-bg: light-dark(#FBF1E2, #332711);
    --sun-br: light-dark(#D8A660, #8A6A2E);
    --good: light-dark(#14603A, #5FD69C);
    --good-bg: light-dark(#F0F9F4, #123027);
    --good-br: light-dark(#BFE3CD, #3A7F60);
    --bad: light-dark(#8A1C25, #FFA1A8);
    --bad-bg: light-dark(#FDF1F2, #341A1D);
    --bad-br: light-dark(#F3C9CD, #7A3C43);
    --bad-line: light-dark(#D0555F, #E0707A);
    --bad-wash: light-dark(#FFFBFB, #2A1417);
    --bad-ic: light-dark(#C24450, #E0707A);
    --warn: light-dark(#8A5B00, #E8B84E);
    --warn-ink: light-dark(#6B4A00, #F0C877);
    --warn-bg: light-dark(#FFF6E5, #33280F);
    --warn-br: light-dark(#F5D9A0, #8A7030);
    --state-wait-br: light-dark(#EFD8B2, #8A7030);
    --info: light-dark(#0A6C8C, #6BD2F0);
    --info-bg: light-dark(#E8F7FC, #12313F);
    --info-br: light-dark(#B9E6F3, #2E8AAC);
    --flag: light-dark(#7A4A08, #E8B84E);
    --flag-bg: light-dark(#FDF3E7, #33280F);
    --flag-br: light-dark(#F0D9B5, #8A7030);
    --tg: light-dark(#229ED9, #229ED9);
    --tg-hover: light-dark(#1B8ABF, #3FB2E8);
    --tg-fg: light-dark(#FFFFFF, #FFFFFF);
    --devbar-bg: light-dark(#3B2C00, #3B2C00);
    --devbar-fg: light-dark(#FFD97A, #FFD97A);
    --shadow: light-dark(rgba(0, 29, 56, .10), rgba(0, 0, 0, .55));
    --shadow-soft: light-dark(rgba(0, 29, 56, .08), rgba(0, 0, 0, .45));
    --ring: light-dark(rgba(9, 170, 212, .18), rgba(34, 184, 224, .30));
    --ring-strong: light-dark(rgba(9, 170, 212, .25), rgba(34, 184, 224, .42));
    --bad-ring: light-dark(rgba(208, 85, 95, .2), rgba(224, 112, 122, .30));
  }

  /* The override. Absent, everything above follows the system; present, it
     wins, and it is what the control in the header sets. `only` rather than a
     bare single value, so the browser does not offer to invert it back. */
  :root[data-theme="light"] { color-scheme: only light; }
  :root[data-theme="dark"]  { color-scheme: only dark; }
}

/* ---------------------------------------------------- which way the toggle points */

/*
 * TWO BUTTONS SHIP, AND CSS SHOWS ONE. The server cannot know which way round
 * to draw this: with no cookie set the theme is whatever the reader's phone
 * says, and the phone does not tell the server. So the markup carries both
 * ("go dark" and "go light") and the rules below reveal whichever one the
 * reader is not already in. No script, and never the wrong arrow.
 */

/* Dark by system preference, unless the reader has said otherwise. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --sel-chevron: var(--chevron-dark);
    --opt-tick: var(--tick-dark);
  }

  :root:not([data-theme="light"]) .theme-to-dark { display: none; }
  :root:not([data-theme="light"]) .theme-to-light { display: inline-flex; }
}

/* Dark because the reader said so. */
:root[data-theme="dark"] {
  --sel-chevron: var(--chevron-dark);
  --opt-tick: var(--tick-dark);
}

:root[data-theme="dark"] .theme-to-dark { display: none; }
:root[data-theme="dark"] .theme-to-light { display: inline-flex; }

/* And back, for a reader on a dark phone who asked this site for light. */
:root[data-theme="light"] {
  --sel-chevron: var(--chevron-light);
  --opt-tick: var(--tick-light);
}

:root[data-theme="light"] .theme-to-dark { display: inline-flex; }
:root[data-theme="light"] .theme-to-light { display: none; }

* { box-sizing: border-box; }

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

/* NOTHING ON A PHONE DRAGS SIDEWAYS. Every piece of the site is built to fit
   its width, and the rows that scroll sideways (the tabs, the logo strip)
   clip their own content. This is the guarantee on top of that: whatever
   some future element or some long unbroken string does, the page itself
   never pans left and right under a thumb.

   clip, NOT hidden. hidden on html and body turns body into a scroll box of
   its own, and the sticky header then sticks to body instead of the screen,
   which is to say it stops sticking. clip cuts the overflow without making a
   scroll box. A browser too old for clip (Safari before 16) gets hidden on
   body alone, which it hands up to the viewport and so leaves the header
   alone as well. */
body { overflow-x: hidden; }

@supports (overflow-x: clip) {
  html, body { overflow-x: clip; }
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  /* 17px, and no sentence on the site is set below 14. Only the things that
     annotate rather than read stay smaller: pill badges, uppercase labels.
     The first version set a 16px base and then undercut it everywhere, rows at
     14, meta at 13, prose at 15.5, and people told us it was small. They were
     right. A job board is read on a phone, at arm's length, often by someone
     who is tired, so the type has to be legible before it is tidy. Every size
     in this file moved up one rung together, so the hierarchy that was already
     working is the hierarchy that is still here. */
  font: 17px/1.55 Inter, Roboto, -apple-system, BlinkMacSystemFont, "Segoe UI", Arial, sans-serif;
  /* Pay figures line up in a column of rows, so digits must not wobble. */
  font-variant-numeric: tabular-nums;

  /*
   * MOST OF THIS SITE IS TEXT SOMEONE ELSE TYPED, and none of it can be trusted
   * to contain a space. A pasted tracking URL, a run of "testtesttest", a long
   * German compound: by default the browser refuses to break any of them, the
   * line runs off the side, and the whole page gets a horizontal scrollbar. It
   * is inherited, so one declaration here covers the job description, the
   * listing title, a company name and every form of it we have not written yet.
   *
   * break-word, not anywhere: break-word leaves minimum content width alone, so
   * it cannot change how any flex item or table column is sized. Somewhere a
   * long word sits inside a flex or table cell, the cell's own width is the
   * thing that overflows and this rule cannot reach it, so those few places ask
   * for anywhere by name.
   */
  overflow-wrap: break-word;
}

a { color: var(--brand-ink); text-decoration: none; }
a:hover { text-decoration: underline; }

.wrap { max-width: var(--w); margin: 0 auto; padding: 0 16px; }

/* ------------------------------------------------------------------ header */

.site-head {
  border-bottom: 1px solid var(--line);
  position: sticky;
  top: 0;
  background: var(--bg);
  z-index: 10;
}

.site-head .wrap {
  display: flex;
  align-items: center;
  gap: 16px;
  min-height: 64px;
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  color: var(--ink);
  font-weight: 600;
  font-size: 20px;
  letter-spacing: -0.02em;
}

.brand:hover { text-decoration: none; }
.brand svg { width: 22px; height: 22px; color: var(--brand); display: block; }

/* The header's right side. One plain link, one button, one avatar: the button
   is the thing a company came to do, and it should not look like the link next
   to it. 22px of gap because 14 had them reading as one blurred item. */
.head-links { margin-left: auto; display: flex; align-items: center; gap: 22px; font-size: 15.5px; }
.head-link { color: var(--ink); }
.btn-sm { padding: 7px 14px; font-size: 15px; }

/* Three words that must stay on one line. Wrapped, this button doubles the
   height of the header, which on a 320px phone is most of what is above the
   first role. */
.head-links .btn-sm { white-space: nowrap; }

/* Icons sit on the text baseline rather than floating above it. */
.ic { width: 16px; height: 16px; vertical-align: -3px; flex: 0 0 auto; }

/* ---------------------------------------------------------- the theme toggle */

/*
 * A form, not a script. It posts to /theme, which sets a cookie and sends the
 * reader back to the page they were on, so this works with JavaScript off like
 * everything else in the header. theme.js then intercepts it and flips the
 * attribute on <html> without the round trip, which is the only part of this
 * that needs script and the only part that is allowed to need it.
 *
 * Both buttons are in the markup; which one is drawn is decided by the rules
 * at the top of this file, because the server cannot know what the reader's
 * phone is set to.
 */
.themer { display: flex; margin: 0; }

.theme-btn {
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: none;
  color: var(--muted);
  cursor: pointer;
}

.theme-btn:hover { color: var(--ink); background: var(--bg-soft); }
.theme-btn:focus-visible { outline: 2px solid var(--brand-ink); outline-offset: 1px; }
.theme-btn .ic { width: 18px; height: 18px; vertical-align: 0; }

/* The default pair: a light page offers the dark one. Every rule that changes
   this is up with the colours, where the theme is worked out. */
.theme-to-dark { display: inline-flex; }
.theme-to-light { display: none; }

/* The footer copy of the control, which is the only one on a small phone. The
   buttons are .linkish, so they take the footer's own colour and size and read
   as the links they sit beside rather than as three more buttons. */
.foot-theme { display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px; margin: 6px 0 0; }
.themer-foot, .themer-auto form { display: inline; }
.foot-theme .linkish { color: var(--muted); text-decoration: underline; }
.foot-theme .linkish:hover { color: var(--ink); }

/* Getting back to "whatever my phone says" once you have overridden it. It is
   in the markup on every page and hidden until there is something to undo,
   which the server decides and theme.js takes over once it has flipped the
   theme without a reload. [hidden] wins over this, see the rule that says so. */
.themer-auto { display: inline-flex; align-items: baseline; gap: 6px; }

/* This used to disappear below 380px, because the header was full before the
   toggle existed: brand, Learn AI, the button and an avatar already overflowed
   a 360px phone. Learn AI has since moved into the phone menu, which bought
   back the room, so the toggle stays and runs one size down to be sure of it.
   The footer keeps its copy in words regardless: it is the only place "match
   system" can live, since an icon that sets an explicit theme has no third
   state to cycle into. */
@media (max-width: 400px) {
  .themer:not(.themer-foot) .theme-btn { width: 30px; height: 30px; }
  .themer:not(.themer-foot) .theme-btn .ic { width: 17px; height: 17px; }
}

/* ------------------------------------------------------------ user menu */

.usermenu { position: relative; }
.usermenu summary { list-style: none; cursor: pointer; display: flex; align-items: center; }
.usermenu summary::-webkit-details-marker { display: none; }

.avatar {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  /* --avatar-bg, NOT the --fill the chosen filter chip uses, though they are
     the same navy in the light theme. A chip that is on has to shout, and
     inverting it to near-white in the dark theme is the honest translation of
     that. This circle is only saying whose account it is, and inverted it
     became the brightest thing on the page for no reason at all. */
  background: var(--avatar-bg);
  color: var(--avatar-fg);
  font-size: 15px;
  font-weight: 600;
  display: flex;
  align-items: center;
  justify-content: center;
  letter-spacing: 0;
}

.usermenu[open] .avatar { box-shadow: 0 0 0 3px var(--ring-strong); }
.usermenu summary:focus-visible { outline: 2px solid var(--brand-ink); outline-offset: 2px; border-radius: 999px; }

/* THE SAME MENU FOR SOMEBODY WITH NO ACCOUNT. It is what lets Learn AI and
   Sign in leave the bar on a phone, because until now the only menu on the
   site belonged to the avatar and a signed-out reader had nowhere for them to
   go. Same <details>, same panel, three lines instead of a letter. It exists
   only at the width where those links are hidden. */
.menu-more { display: none; }

.burger {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border-radius: 999px;
  color: var(--muted);
}

.menu-more[open] .burger { color: var(--ink); background: var(--bg-soft); }
.burger .ic { width: 20px; height: 20px; vertical-align: 0; }

@media (max-width: 640px) {
  .menu-more { display: block; }
  .burger { width: 30px; height: 30px; }
}

.usermenu-panel {
  position: absolute;
  right: 0;
  top: calc(100% + 10px);
  min-width: 190px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 6px;
  /* The one shadow on the site. A menu floating over content is the single
     case where a border alone does not read as "above". */
  box-shadow: 0 6px 24px var(--shadow);
  padding: 6px;
  z-index: 20;
}

.usermenu-who {
  margin: 0;
  padding: 6px 10px 8px;
  font-size: 13.5px;
  color: var(--muted);
  border-bottom: 1px solid var(--line);
  overflow: hidden;
  text-overflow: ellipsis;
}

.usermenu-panel a, .usermenu-panel .linkish {
  display: block;
  width: 100%;
  text-align: left;
  padding: 9px 10px;
  color: var(--ink);
  font-size: 15px;
  border-radius: 4px;
  /* The panel widens to fit a line rather than wrapping it. "Get found by
     companies" is a shade over the 190px and split in two, it read as two
     items. */
  white-space: nowrap;
}

.usermenu-panel a:hover, .usermenu-panel .linkish:hover { background: var(--bg-soft); text-decoration: none; }

/* -------------------------------------------------------------------- tabs */

.tabs {
  display: flex;
  gap: 4px;
  overflow-x: auto;
  border-bottom: 1px solid var(--line);
  /* The scrollbar would otherwise sit under the tabs on Windows and eat 8px of
     a phone screen for a row that only scrolls when it has to. */
  scrollbar-width: none;
}

.tabs::-webkit-scrollbar { display: none; }

/* THE ROW SCROLLS, AND NOW SAYS SO. On a phone the last tab was cut clean at
   the edge, which reads as the page being too wide rather than as more tabs.
   A fade to the page colour pinned to the right edge says "there is more".
   It is a sticky flex item, not a mask on the row: a mask would fade the
   row's bottom rule out too. Sticky keeps it at the visible edge while
   there is more to scroll, and at the end of the row it settles into its own
   place after the last tab, so the last tab is never left under it. */
.tabs::after {
  content: "";
  position: sticky;
  right: 0;
  flex: 0 0 28px;
  background: linear-gradient(90deg, transparent, var(--bg));
  pointer-events: none;
}

.tab {
  padding: 10px 12px;
  white-space: nowrap;
  color: var(--muted);
  font-size: 16px;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
}

.tab:hover { text-decoration: none; color: var(--ink); }
.tab[aria-current="page"] { color: var(--ink); font-weight: 600; border-bottom-color: var(--brand); }

/* ----------------------------------------------------------------- filters */

.filters {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  padding: 12px 0;
  font-size: 15px;
  color: var(--muted);
}

.chip {
  display: inline-block;
  padding: 5px 10px;
  border: 1px solid var(--line);
  border-radius: 999px;
  color: var(--ink);
  background: var(--surface);
  font-size: 14px;
  line-height: 1.3;
}

.chip:hover { text-decoration: none; border-color: var(--brand-ink); }
.chip.on { background: var(--fill); border-color: var(--fill); color: var(--fill-fg); }

.count { font-size: 15px; color: var(--muted); padding: 12px 0 0; }
.count strong { color: var(--ink); }

/* ------------------------------------------------------------------- rows */

/*
 * The rules and the hover fill run 10px WIDER than the text column on each
 * side. A hairline can stop level with a word and still look deliberate; a
 * filled band cannot, so hovering a row painted a block whose edge touched the
 * title on one side and the age on the other. Bleeding the list outward buys
 * that room without moving a single word: every row pads the 10px straight
 * back, so the logo and the title stay lined up with the tabs and the filters
 * above them.
 */
.rows { border-top: 1px solid var(--line); margin: 0 -10px; }

/* The empty state is a box of its own, so it stays on the text column. */
.rows > .empty-box { margin: 4px 10px 0; }

.row {
  position: relative;
  display: flex;
  gap: 12px;
  padding: 14px 10px;
  border-bottom: 1px solid var(--line);
}

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

/*
 * The whole row opens the listing. The hover shade already said it would, and
 * a thumb aiming at a phone-width row should not have to find the title. The
 * title's link stretches over the row instead of an <a> wrapping all of it,
 * so a screen reader still hears one short link, not the logo, company and
 * every fact read out as its name. Plain CSS, so it works before any JS does.
 */
.row-title::after { content: ""; position: absolute; inset: 0; }
.row-title:focus-visible { outline: none; }
.row-title:focus-visible::after { outline: 2px solid var(--brand-ink); outline-offset: -2px; }

/*
 * --logo-bg, AND IT STAYS LIGHT IN THE DARK THEME. This square holds a file
 * somebody else drew, and almost every company logo is drawn as dark marks on
 * an assumed white page. Put one on a navy tile and half of them vanish: not
 * dimmed, gone, because the mark and the tile are the same colour. So the tile
 * keeps its own light background in both themes and the logo keeps the
 * surroundings it was made for. The same reasoning covers .co-logo and the
 * product marks on the Learn AI page.
 */
.row-logo {
  flex: 0 0 28px;
  width: 28px;
  height: 28px;
  border: 1px solid var(--line);
  border-radius: var(--r);
  object-fit: contain;
  background: var(--logo-bg);
  margin-top: 2px;
}

.row-logo-blank {
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--muted);
  font-size: 14px;
  font-weight: 600;
}

.row-main { min-width: 0; flex: 1; }

/* Title and age share one line. min-width:0 is what lets the title shrink and
   wrap normally instead of forcing the row wider than the screen. */
.row-head { display: flex; align-items: baseline; gap: 10px; }

.row-title {
  font-size: 17.5px;
  font-weight: 600;
  color: var(--ink);
  flex: 1;
  min-width: 0;
  margin-bottom: 2px;
}

.row-company { color: var(--muted); font-size: 15px; }

/* The facts line. Dot separators are added by CSS so the markup stays a plain
   list and an empty field simply disappears instead of leaving a stray dot. */
.row-facts {
  margin-top: 4px;
  font-size: 14.5px;
  color: var(--muted);
  display: flex;
  flex-wrap: wrap;
  gap: 4px 0;
}

/* The dot TRAILS its item rather than leading the next one. As a ::before it
   landed at the start of a wrapped line, which read as a bullet list. */
.row-facts span:not(:last-child)::after { content: " ·"; margin: 0 6px 0 6px; color: var(--line); }
.row-facts .pay { color: var(--pay); font-weight: 600; }
.row-age { flex: 0 0 auto; color: var(--muted); font-size: 14px; white-space: nowrap; }

.badge {
  display: inline-block;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  padding: 2px 6px;
  border-radius: 3px;
  background: var(--bg-soft);
  color: var(--muted);
  border: 1px solid var(--line);
  vertical-align: 1px;
}

.badge.featured { background: var(--warn-bg); border-color: var(--warn-br); color: var(--warn); }
.badge.ai { background: var(--info-bg); border-color: var(--info-br); color: var(--info); }

/* ------------------------------------------------------------------ pieces */

.empty { padding: 32px 0; color: var(--muted); }

.cta-band {
  margin: 24px 0;
  padding: 18px 20px;
  background: var(--bg-soft);
  border: 1px solid var(--line);
  border-radius: var(--r);
  font-size: 16px;
  line-height: 1.55;
}

/* The button is a second thing, not a word in the last sentence. Side by side
   where there is room, stacked underneath where there is not. */
.cta-split {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
}

.cta-split .cta-copy { max-width: 60ch; }
.cta-split .tg-btn { margin-top: 0; flex: 0 0 auto; }

/* Above the tabs the band is a stop on the way to the list, not a closing ask,
   so it sits tighter and the tabs keep their own air underneath. */
.cta-top { margin: 16px 0 6px; }

.btn {
  display: inline-block;
  padding: 10px 16px;
  background: var(--btn-bg);
  color: var(--btn-fg);
  border-radius: var(--r);
  font-weight: 600;
  font-size: 16px;
  border: 0;
  cursor: pointer;
}

.btn:hover { text-decoration: none; background: var(--btn-bg-hover); }
.btn.ghost { background: transparent; color: var(--ink); border: 1px solid var(--line); }

.site-foot {
  margin-top: 40px;
  border-top: 1px solid var(--line);
  padding: 20px 0 40px;
  color: var(--muted);
  font-size: 15px;
}

.site-foot a { color: var(--muted); }
.site-foot .tagline { color: var(--ink); font-weight: 600; }

/* ------------------------------------------------------------------- phone */

@media (max-width: 640px) {
  /* Only 16px of page gutter here, so the bleed shrinks rather than crowding
     the edge of the screen. */
  .rows { margin: 0 -6px; }
  .rows > .empty-box { margin: 4px 6px 0; }
  .row { gap: 10px; padding: 14px 6px; }
  .row-age { font-size: 13px; }
  .head-links { gap: 10px; font-size: 14px; }
  .brand { font-size: 18.5px; }
}

/* ----------------------------------------------------------- filter form */

.filters label { display: inline-flex; flex-direction: column; gap: 3px; }
.f-label { font-size: 12px; text-transform: uppercase; letter-spacing: 0.04em; color: var(--muted); }

.filters select {
  font: inherit;
  font-size: 16px;
  padding: 6px 8px;
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--surface);
  color: var(--ink);
  /* 205, not 190: the option labels are the same words at a larger size now, and
     a truncated category is a filter nobody trusts. */
  max-width: 205px;
}

.filters .f-check { flex-direction: row; align-items: center; gap: 6px; font-size: 15px; color: var(--ink); align-self: end; padding-bottom: 6px; }
.filters .btn { align-self: end; padding: 7px 14px; font-size: 15px; }
.f-clear { align-self: end; padding-bottom: 8px; font-size: 15px; }

/* ------------------------------------------------------------ detail page */

.page-h { font-size: 26px; margin: 20px 0 4px; letter-spacing: -0.01em; }
.page-sub { color: var(--muted); margin: 0 0 8px; }
.section-h { font-size: 18.5px; margin: 28px 0 8px; }

.detail h1 { font-size: 28px; margin: 20px 0 4px; letter-spacing: -0.01em; }
.detail-company { color: var(--muted); margin: 0 0 16px; }

.notice {
  background: var(--bg-soft);
  border: 1px solid var(--line);
  border-left: 3px solid var(--muted);
  padding: 10px 12px;
  margin: 16px 0;
  font-size: 15.5px;
}

.facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 12px 20px;
  margin: 0 0 20px;
  padding: 14px 0;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}

.facts dt { font-size: 12px; text-transform: uppercase; letter-spacing: 0.04em; color: var(--muted); }
.facts dd { margin: 2px 0 0; font-size: 16px; }
.facts .pay { color: var(--pay); }
.facts .pay-hidden { color: var(--muted); font-style: italic; font-size: 15px; }

.prose { font-size: 17px; line-height: 1.65; max-width: 68ch; }

/* A job description, drawn by ws_desc_render(). The h2s are the posting form's
   four boxes, so every listing reads in the same order; the h3s are the
   writer's own "Must have:" lines inside one of them. */
.desc h2 { font-size: 18.5px; margin: 28px 0 6px; }
.desc h2:first-child { margin-top: 4px; }
.desc h3 { font-size: 17px; margin: 18px 0 4px; }
.desc p { margin: 0 0 12px; }
.desc ul, .desc ol { margin: 0 0 12px; padding-left: 22px; }
.desc li { margin-bottom: 6px; padding-left: 2px; }
.desc li::marker { color: var(--muted); }
.apply { margin: 24px 0 8px; display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.apply-note { color: var(--muted); font-size: 14.5px; }

.warn { color: var(--muted); font-size: 14px; border-top: 1px solid var(--line); padding-top: 12px; margin-top: 24px; }

@media (max-width: 640px) {
  .filters { gap: 10px; }
  .filters select { max-width: none; width: 100%; }
  .filters label { width: 47%; }
  .detail h1 { font-size: 23px; }
}

/* -------------------------------------------------------------- forms */

.form-page { max-width: 420px; margin: 28px auto 0; }
.form-page h1 { font-size: 24px; margin: 0 0 6px; }
.form-page label { display: block; margin: 14px 0; font-size: 15px; color: var(--muted); }

.form-page input[type=email],
.form-page input[type=password],
.form-page input[type=text],
.form-page input[type=url],
.form-page input[type=number],
.form-page textarea,
.form-page select {
  display: block;
  width: 100%;
  margin-top: 4px;
  font: inherit;
  /* 16px is a floor, not a taste: below it, iOS zooms the whole page the moment
     the field takes focus, and the person then has to pinch back out to see the
     rest of the form. Every field, select and search box on the site holds it. */
  font-size: 16px;
  padding: 9px 10px;
  border: 1px solid var(--line);
  border-radius: var(--r);
  color: var(--ink);
  background: var(--surface);
}

.form-page textarea { min-height: 150px; resize: vertical; line-height: 1.5; }
/* The optional description boxes: room for the two-line example, and the
   corner handle for more. Four full-height boxes read as an essay to write. */
.form-page textarea.short { min-height: 96px; }
.form-page .hint { display: block; margin-top: 4px; font-size: 14px; color: var(--muted); }
.btn.block { display: block; width: 100%; text-align: center; margin-top: 18px; }

/* The Google mark keeps its own colours and its own size, so it needs to be
   let out of the 16px the rest of the icons share. */
.btn.google .ic-g { width: 18px; height: 18px; vertical-align: -4px; margin-right: 10px; }
.btn.ghost.google:hover { background: var(--bg-soft); }
.or { text-align: center; color: var(--muted); font-size: 14px; margin: 12px 0; }
.muted { color: var(--muted); font-size: 15px; }
/* -------------------------------------------- which account am I making
 *
 * The two options above the signup form, one of them visibly the chosen one.
 * They are links, so this is a pair of states rather than a control: the chosen
 * one is a <span> and draws a tick, the other is an <a>.
 *
 * STACKED, NOT SIDE BY SIDE, at every width. .form-page is 420px, so two
 * columns would leave each option about 205px, and a 30px mark plus the padding
 * takes 67 of them: the sentence underneath then wraps to five lines and the
 * pair ends up taller than stacking it was. Full width also gives each option
 * the same shape as the .choice cards on the posting form, which is the other
 * place on the site where somebody picks one of a few things and carries on.
 */
.rolepick { display: grid; gap: 10px; margin: 14px 0 20px; }

.rolepick-opt {
  display: flex;
  align-items: flex-start;
  gap: 11px;
  padding: 12px 13px;
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--surface);
  color: var(--ink);
}

.rolepick-body { flex: 1; min-width: 0; }

a.rolepick-opt:hover {
  text-decoration: none;
  border-color: var(--line-hover);
  background: var(--bg-soft);
}

a.rolepick-opt:focus-visible { outline: 2px solid var(--brand-ink); outline-offset: 2px; }

/* The chosen one. A tinted edge and wash rather than a solid fill, because the
   unchosen option next to it still has to be readable and inviting: this is a
   choice being shown, not a button that has been pressed. */
.rolepick-opt.on {
  border-color: var(--brand-ink);
  background: var(--brand-wash);
  /* The same inset ring the paid-tier cards use for "this one", so a chosen
     thing looks chosen the same way twice on the site. */
  box-shadow: inset 0 0 0 1px var(--brand-ink);
}

.rolepick-name { display: block; font-weight: 600; font-size: 15.5px; }
.rolepick-opt.on .rolepick-name { color: var(--brand-ink); }
.rolepick-why { display: block; margin-top: 2px; font-size: 14px; color: var(--muted); }

/* The tick sits at the far edge of the chosen row. 4px lines it up with the cap
   height of the name rather than the middle of the whole card, which is where
   flex would otherwise leave it as the description grows. */
.rolepick-tick { margin: 4px 0 0 auto; color: var(--brand-ink); }

.error {
  background: var(--bad-bg);
  border: 1px solid var(--bad-br);
  color: var(--bad);
  padding: 10px 12px;
  border-radius: var(--r);
  font-size: 15.5px;
}

.ok {
  background: var(--good-bg);
  border: 1px solid var(--good-br);
  color: var(--good);
  padding: 10px 12px;
  border-radius: var(--r);
  font-size: 15.5px;
}

/* The honeypot. Off-screen rather than display:none, because some bots skip
   fields that are explicitly hidden. */
.hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

/* -------------------------------------------------------- posting form */

.form-page.wide { max-width: 640px; }

.form-page fieldset {
  border: 0;
  border-top: 1px solid var(--line);
  padding: 4px 0 8px;
  margin: 22px 0 0;
}

.form-page legend {
  font-size: 13px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--muted);
  font-weight: 600;
  padding-right: 8px;
}

.form-page fieldset.inline { border-top: 0; margin-top: 10px; }
.two, .three { display: flex; gap: 14px; }
.two > label, .three > label,
.two > .col, .three > .col { flex: 1; min-width: 0; }
.check, .tier { display: flex; align-items: flex-start; gap: 8px; font-size: 15.5px; color: var(--ink); }
.check { display: inline-flex; align-items: center; margin-right: 14px; }
.check input, .tier input { width: auto; margin: 3px 0 0; }
.tier { margin: 10px 0; }
.tier span { flex: 1; }

@media (max-width: 640px) {
  .two, .three { flex-direction: column; gap: 0; }
}

/* ------------------------------------------------------------- tables */

.tbl { width: 100%; border-collapse: collapse; font-size: 15.5px; margin: 8px 0 20px; }
.tbl th { text-align: left; font-size: 12px; text-transform: uppercase; letter-spacing: 0.04em;
          color: var(--muted); font-weight: 600; padding: 8px 10px 8px 0; border-bottom: 1px solid var(--line); }
.tbl td { padding: 10px 10px 10px 0; border-bottom: 1px solid var(--line); vertical-align: top; }

/* A cell holds a Telegram error or a note you typed yourself, and the column is
   sized from the longest unbreakable word in it, so a long one stretches the
   table past the page instead of wrapping. anywhere rather than the break-word
   on body, because only anywhere shrinks that measurement. */
.tbl td, .tbl th { overflow-wrap: anywhere; }
.muted-sm { display: block; color: var(--muted); font-size: 14px; }
.linkish { background: none; border: 0; padding: 0; font: inherit; color: var(--brand-ink); cursor: pointer; }
.linkish:hover { text-decoration: underline; }

@media (max-width: 640px) {
  .tbl th:nth-child(3), .tbl td:nth-child(3) { display: none; }
}

.badge.ok-badge { background: var(--good-bg); border-color: var(--good-br); color: var(--good); }
/* The small "optional" hint inside a label. */
.opt { font-weight: 400; text-transform: none; letter-spacing: 0; color: var(--muted); font-size: 13px; }

/* The pay box on a company page: real figures that company published itself. */
.paybox { border: 1px solid var(--line); border-radius: var(--r); padding: 16px; margin: 18px 0; background: var(--bg-soft); }
.paybox h2 { font-size: 15px; margin: 0 0 8px; color: var(--muted); font-weight: 600; }
.paybox-main { font-size: 32px; font-weight: 600; color: var(--pay); margin: 0; letter-spacing: -0.02em; }
.paybox-main span { font-size: 16px; font-weight: 400; color: var(--muted); margin-left: 6px; }
.paybox-sub { margin: 6px 0 0; font-size: 15px; color: var(--muted); }

.page-prose h2 { font-size: 18.5px; margin: 26px 0 6px; }
.page-prose p { margin: 0 0 12px; }

.co-head { display: flex; align-items: center; gap: 12px; }
.co-logo { width: 44px; height: 44px; object-fit: contain; border: 1px solid var(--line); border-radius: var(--r); background: var(--logo-bg); flex: 0 0 44px; }

/* ------------------------------------------------------------- applicants */

.applicant { border-bottom: 1px solid var(--line); padding: 16px 0; }
.applicant-head { display: flex; align-items: center; gap: 10px; }
.applicant-facts { color: var(--muted); font-size: 15px; margin: 4px 0 0; }
.applicant-note, .applicant-answer { font-size: 15.5px; margin: 10px 0 0; padding-left: 12px; border-left: 2px solid var(--line); }

/* The listing title is a flex item beside its badge and age, so the wrapping
   baseline on body is not enough on its own: the item is sized from the longest
   unbreakable word in it, and one long word makes the item itself wider than
   the row. anywhere is the value that shrinks that measurement, and min-width
   lets the item actually take the smaller size. */
.applicant-head strong { min-width: 0; overflow-wrap: anywhere; }
.applicant-answer .q { display: block; color: var(--muted); font-size: 14px; margin-bottom: 2px; }
.applicant-actions { font-size: 15px; margin: 10px 0 0; }
.applicant-buttons { margin-top: 10px; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.applicant-buttons .btn { padding: 6px 12px; font-size: 14.5px; }

/* the apply panel */
.apply-form { margin: 24px 0 8px; }
.apply-form label { display: block; font-size: 15px; color: var(--muted); margin: 12px 0; }
.apply-form textarea, .apply-form input[type=file] { display: block; width: 100%; margin-top: 4px; font: inherit; font-size: 16px; padding: 9px 10px; border: 1px solid var(--line); border-radius: var(--r); }
/* Colours as .form-page gives its fields. Without them a dark-mode browser
   paints its own grey box, which went unnoticed while the note started empty
   and is the first thing anyone sees now it arrives filled with their intro. */
.apply-form textarea { color: var(--ink); background: var(--surface); line-height: 1.5; resize: vertical; }
.apply-form details { margin: 8px 0 16px; }
.apply-form summary { cursor: pointer; color: var(--brand-ink); font-size: 15px; }
.apply-form .hint { display: block; margin-top: 4px; font-size: 14px; color: var(--muted); }

.devbar { background: var(--devbar-bg); color: var(--devbar-fg); font-size: 13px; text-align: center; padding: 4px 8px; letter-spacing: 0.02em; }

.filters input[type=search] { font: inherit; font-size: 16px; padding: 6px 8px; border: 1px solid var(--line); border-radius: var(--r); background: var(--surface); color: var(--ink); min-width: 220px; }

/* The phone rule above gives every filter label 47%, which is right for two
   selects side by side and wrong for a search box: the 220px input spilled
   out of its 47% label and ran under the Search button. A search is one field
   and its button, so the field takes whatever the button leaves. The button
   is built to the field's exact height (same line, padding and border), or
   it sits 8px short and reads as a separate thing. */
.filters .f-search + .btn { font-size: 16px; line-height: 1.55; padding: 6px 14px; border: 1px solid transparent; }
@media (max-width: 640px) {
  .filters .f-search { flex: 1; width: auto; min-width: 0; }
  .filters .f-search input { width: 100%; min-width: 0; }
}
.pager { display: flex; gap: 18px; padding: 18px 0 0; font-size: 16px; }

/* The trust rail. It was jammed against the tabs with no air; it is the first
   thing a visitor reads, so it gets its own band. */
.rail {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 24px;
  padding: 16px 0 18px;
  margin: 0;
  font-size: 14.5px;
  color: var(--muted);
  border-bottom: 1px solid var(--line);
}

.rail-item { display: inline-flex; align-items: center; gap: 7px; color: var(--muted); }
.rail-item .ic { color: var(--brand); opacity: .9; }
.rail strong { color: var(--ink); font-weight: 600; }
a.rail-item:hover { color: var(--ink); text-decoration: none; }
a.rail-item:hover strong { text-decoration: underline; }

/* THE RAIL NUMBERS COUNT UP ONCE, on load (assets/js/count.js). The server
   writes the real figure and that is what shows without the script or with
   less motion asked for. Where a script is going to run, the figure is held
   back until it does, so nobody sees 640 flick to 0 and climb again; and if
   the script never arrives, the hold lets go by itself after 1.5s. Tabular
   figures and a width held by the script keep the words beside the number
   from shuffling sideways while it climbs. */
.count-up { font-variant-numeric: tabular-nums; transition: opacity .2s ease; }
.count-up.is-counting { display: inline-block; text-align: right; }

@media (scripting: enabled) and (prefers-reduced-motion: no-preference) {
  .count-up:not(.is-counted) { opacity: 0; animation: count-late 0s 1.5s forwards; }
}

@keyframes count-late { to { opacity: 1; } }

/* THE LOGOS UNDER THE RAIL are the same band, so the rail hands its bottom
   rule down to them rather than drawing a line between two kinds of proof.

   Every logo sits on an identical tile. Twelve brands are twelve different
   shapes and colours; twelve equal boxes in two aligned rows is what stops
   that reading as a mess. The tile is --surface, so it goes dark with the
   page, and the marks that would vanish on it (anything drawn in black, and
   Seedly's and Yahoo's colours, which fall under 3:1 there) swap to a white
   twin; see .proof-on-dark below. Inside the tile, the height is a base
   times --k, the area correction index.php works out from the file. */
.rail:has(+ .proof) { border-bottom: 0; padding-bottom: 14px; }

/* minmax(0, 1fr), NOT THE DEFAULT auto COLUMN. An auto column grows to fit
   the widest thing in it, and once the row slides that is the whole doubled
   track, well over a thousand pixels: the band pushed the page sideways and
   the right-hand fade ended up off the screen. */
.proof {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 8px;
  padding: 0 0 18px;
  border-bottom: 1px solid var(--line);
}

.proof-row { display: flex; align-items: center; gap: 20px; }
.proof-label { flex: 0 0 12.5em; font-size: 13.5px; color: var(--muted); white-space: nowrap; }
.proof-window { flex: 1 1 auto; min-width: 0; }

/* Where the row fits, the six tiles share it equally, so the two rows line
   up column for column and the copy for the slide is not needed. */
.proof-logos {
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: 8px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.proof-echo { display: none; }

.proof-tile {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 44px;
  padding: 0 10px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r);
}

.proof-tile img {
  display: block;
  width: auto;
  max-width: 100%;
  height: calc(18px * var(--k, 1));
  object-fit: contain;
}

/* WHICH TWIN SHOWS follows the same three cases as the theme toggle at the
   top of this file: dark by system preference unless told light, or dark
   because told so. All of it sits inside the same @supports as the dark
   tokens, because a browser without light-dark() never gets a dark tile,
   and a white logo on its white tile would be no logo at all. */
.proof-tile .proof-on-dark { display: none; }

@supports (color: light-dark(#fff, #000)) {
  @media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) .proof-tile .proof-on-light { display: none; }
    :root:not([data-theme="light"]) .proof-tile .proof-on-dark { display: block; }
  }

  :root[data-theme="dark"] .proof-tile .proof-on-light { display: none; }
  :root[data-theme="dark"] .proof-tile .proof-on-dark { display: block; }
}

a.proof-tile { transition: border-color .15s ease, transform .15s ease; }
a.proof-tile:hover { border-color: var(--line-hover); transform: translateY(-1px); }

/* WHERE SIX TILES DO NOT FIT, THE ROW SLIDES, which is what the old site's
   carousel did, minus the dots and arrows. The track holds the row twice and
   moves by exactly one copy, so the loop never jumps; each list carries its
   own trailing gap as padding so the seam is the same 8px as every other
   gap. The two rows run opposite ways, and a finger or pointer on a row
   holds it still so a logo can be tapped. The fade at each edge is a mask,
   which reads only alpha: black there is "opaque", not a colour. */
@media (max-width: 860px) {
  .proof-window {
    overflow: hidden;
    -webkit-mask-image: linear-gradient(90deg, transparent, black 16px, black calc(100% - 16px), transparent);
            mask-image: linear-gradient(90deg, transparent, black 16px, black calc(100% - 16px), transparent);
  }

  .proof-track { display: flex; width: max-content; animation: proof-slide 40s linear infinite; }
  .proof-row + .proof-row .proof-track { animation-direction: reverse; }
  .proof-logos { display: flex; padding-right: 8px; }
  .proof-tile { width: 100px; }

  .proof-window:hover .proof-track,
  .proof-window:focus-within .proof-track { animation-play-state: paused; }
}

@keyframes proof-slide { to { transform: translateX(-50%); } }

/* On a phone the label keeps its place on the left but folds onto two short
   lines, so both rows together cost about what the rail does. */
@media (max-width: 640px) {
  .rail:has(+ .proof) { padding-bottom: 12px; }
  .proof { padding-bottom: 14px; }
  .proof-row { gap: 10px; }
  .proof-label { flex-basis: 6.4em; white-space: normal; font-size: 12px; line-height: 1.3; }
  .proof-tile { width: 88px; height: 38px; padding: 0 8px; }
  .proof-tile img { height: calc(15px * var(--k, 1)); }
}

/* Less motion asked for: nothing slides, and the row becomes one a thumb can
   scroll instead. */
@media (prefers-reduced-motion: reduce) {
  .proof-track { animation: none; }
  .proof-echo { display: none; }
  .proof-window { overflow-x: auto; }
  a.proof-tile { transition: none; }
  a.proof-tile:hover { transform: none; }
}

/* The tabs follow the rail, so they need their own breathing room. */
.tabs { margin-top: 4px; }
.filters { padding-top: 14px; }

.page-prose ul { margin: 0 0 14px; padding-left: 20px; }
.page-prose li { margin-bottom: 8px; }


@media (max-width: 640px) {
  /* The plain links fold into the menu; the button and avatar stay. */
  .head-links { gap: 14px; }
  .head-links .head-link { display: none; }
  .rail { gap: 6px 16px; padding: 12px 0 14px; font-size: 13.5px; }
}

/* Specificity, not order: `.usermenu-panel a` is (0,1,1) and beats a bare
   `.only-phone` at (0,1,0), so the phone-only link showed on desktop too. */
.usermenu-panel .only-phone { display: none; }
@media (max-width: 640px) { .usermenu-panel .only-phone { display: block; } }

/* --------------------------------------------------------------- admin */

.kpis { display: flex; flex-wrap: wrap; gap: 10px; margin: 16px 0 8px; }
.kpis > div {
  flex: 1 1 130px;
  border: 1px solid var(--line);
  border-radius: var(--r);
  padding: 12px 14px;
  font-size: 13.5px;
  color: var(--muted);
}
.kpis span { display: block; font-size: 24px; font-weight: 600; color: var(--ink); letter-spacing: -0.02em; }
.applicant-buttons input[type=text] {
  font: inherit; font-size: 16px; padding: 6px 10px;
  border: 1px solid var(--line); border-radius: var(--r); min-width: 200px;
}

/* Admin sections are scanned, not read: empty ones should not take a screenful. */
.kpis + h2.sec-head { margin-top: 18px; }
.sec-head + .empty-box { padding: 14px 16px; }

/* Not an error and not a success: something to do before something else works. */
.verifybar { background: var(--warn-bg); border-bottom: 1px solid var(--warn-br); color: var(--warn-ink); font-size: 14.5px; }
.verifybar .wrap { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; padding: 9px 16px; }
.verifybar .linkish { color: var(--warn-ink); text-decoration: underline; font-size: 14.5px; }

/* A struck-through list price. Only ever shown when it is genuinely higher
   than what is being charged, see ws_price_is_discounted(). */
.was { text-decoration: line-through; color: var(--muted); margin-right: 3px; }

/* The join button. Telegram blue, because a link to Telegram that looks like
   every other link gets read as one. */
.tg-btn {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 8px 14px;
  background: var(--tg);
  color: var(--tg-fg);
  border-radius: var(--r);
  font-weight: 600;
  font-size: 15.5px;
  margin-top: 8px;
}

.tg-btn:hover { text-decoration: none; background: var(--tg-hover); }
.tg-btn .ic { width: 17px; height: 17px; vertical-align: -3px; }
.cta-band strong { display: block; margin-bottom: 3px; font-size: 17.5px; }

@media (max-width: 720px) {
  .cta-split { display: block; }
  .cta-split .cta-copy { max-width: none; }
  .cta-split .tg-btn { margin-top: 14px; }
}

/* THE TOP BAND ON A PHONE IS A SIGNPOST, NOT A PITCH. It sits above the tabs,
   before the reader has seen a single role, and at full height it was most of
   the first screen: a headline, three lines of copy and a button, roughly
   270px of a 650px viewport spent saying something they can act on in four
   words. So it loses the middle sentence, the button loses its last two words,
   and what is left is the hook, the number and somewhere to tap, on one row.

   The band under the list keeps every word. By the time a reader is down
   there they have seen the roles, and the long version is the close. */
@media (max-width: 640px) {
  .cta-top {
    display: flex;
    align-items: center;
    gap: 12px;
    margin: 12px 0 2px;
    padding: 11px 13px;
    font-size: 14.5px;
    line-height: 1.4;
  }

  .cta-top .cta-long { display: none; }
  .cta-top strong { display: inline; margin: 0; font-size: 14.5px; }
  .cta-top .tg-btn { margin-top: 0; flex: 0 0 auto; padding: 8px 13px; font-size: 14.5px; }
  .cta-top .tg-btn .ic { width: 16px; height: 16px; }
  .cta-top .tg-more { display: none; }
}

/* --------------------------------------------------- the posting form */

/* Jump links. A long form with no landmarks reads as longer than it is. */
.form-jump {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 14px 0 4px;
  padding-bottom: 14px;
  border-bottom: 1px solid var(--line);
}

.form-jump a {
  font-size: 14px;
  color: var(--muted);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 5px 11px;
}

.form-jump a:hover { text-decoration: none; color: var(--ink); border-color: var(--brand-ink); }

/* Sections get room to be sections. */
.form-page fieldset[id] { scroll-margin-top: 70px; padding-top: 10px; }
.form-page fieldset[id] > legend { font-size: 14px; color: var(--ink); }

/* Optional detail, folded away until wanted. */
.form-page .more { margin: 14px 0 4px; }
.form-page .more summary { cursor: pointer; color: var(--brand-ink); font-size: 15px; padding: 4px 0; }
.form-page .more[open] summary { margin-bottom: 6px; }

/* The recommended choice should look like one without shouting. */
.tier:has(input:checked) { background: var(--bg-soft); border-radius: var(--r); }
.tier { padding: 8px 10px; margin: 4px -10px; }

/* Specificity again: `.form-page label` is (0,1,1) and beat a bare `.check`,
   so the Open-to boxes stacked one per line instead of sitting in a row. */
.form-page label.check { display: inline-flex; align-items: center; width: auto; }
.form-page label.tier { display: flex; }
.form-page fieldset.inline label.check { margin: 4px 16px 4px 0; }

/* [hidden] MUST WIN. Any display rule, including `.form-page label { display:
   block }`, beats the browser's own [hidden] { display: none }, so a field the
   script hides stays on screen. This is why the Area box was still there for a
   remote role. */
[hidden] { display: none !important; }

/* The jump nav follows you down a long form: on a phone, section 5 is a long
   way from the top and going back meant scrolling blind. */
.form-jump {
  position: sticky;
  /* BELOW THE SITE HEADER, which is itself sticky at top: 0. At 0 this did
     stick, invisibly, underneath it. 64px is .site-head's min-height. */
  top: 64px;
  z-index: 5;
  background: var(--bg);
  padding: 10px 0;
  margin-bottom: 0;
  box-shadow: 0 6px 6px -6px var(--shadow-soft);
}

@media (max-width: 640px) {
  .form-jump { top: 56px; }
}

/* -------------------------------------------------- choice cards */

/* A radio with a paragraph beside it reads as a wall. A card with a price on
   the right reads as a choice, which is what it is. */
.choice {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 14px;
  margin: 10px 0;
  border: 1px solid var(--line);
  border-radius: 6px;
  cursor: pointer;
  background: var(--surface);
}

/* SPECIFICITY, FOR THE THIRD TIME. `.form-page label` is (0,1,1) and beats a
   bare class at (0,1,0), so every display rule aimed at a <label> inside a form
   has to be written with the element in it or it silently loses. It cost a
   stacked checkbox row, a field the script could not hide, and this card
   laying its price out underneath instead of beside. */
.form-page label.choice { display: flex; }
.choice:hover { border-color: var(--brand-ink); }
.choice:has(input:checked) { border-color: var(--brand-ink); background: var(--brand-wash); box-shadow: inset 0 0 0 1px var(--brand-ink); }
.choice input { width: auto; margin: 3px 0 0; flex: 0 0 auto; }
.choice-body { flex: 1; min-width: 0; }
.choice-title { display: block; font-weight: 600; color: var(--ink); font-size: 16px; }
.choice-note { display: block; color: var(--muted); font-size: 15px; margin-top: 3px; }
.choice-label { font-weight: 400; color: var(--muted); font-size: 13px; }

.choice-list { margin: 8px 0 0; padding: 0; list-style: none; color: var(--muted); font-size: 15px; }
.choice-list li { padding-left: 18px; position: relative; margin-bottom: 3px; }
.choice-list li::before { content: ""; position: absolute; left: 3px; top: 8px; width: 5px; height: 5px;
                       border-radius: 50%; background: var(--brand); }

.choice-price {
  flex: 0 0 auto;
  text-align: right;
  font-size: 22px;
  font-weight: 600;
  color: var(--ink);
  letter-spacing: -0.02em;
}

.choice-price-note { display: block; font-size: 12.5px; font-weight: 400; color: var(--muted);
                  line-height: 1.35; margin-top: 2px; }
.choice-price-alt { display: block; font-size: 14px; font-weight: 600; color: var(--pay); margin-top: 8px; }

.pill {
  display: inline-block;
  font-size: 12px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  padding: 2px 7px;
  border-radius: 999px;
  background: var(--info-bg);
  color: var(--info);
  vertical-align: 2px;
}

@media (max-width: 640px) {
  .choice { flex-wrap: wrap; }
  .choice-price { text-align: left; width: 100%; padding-left: 28px; }
  .choice-price-note { display: inline; margin-left: 6px; }
  .form-jump { padding-bottom: 10px; }
}

/* ------------------------------------------------------- the AI journey */

/*
 * learn-ai.php. The one page on the site that is allowed to look like a game.
 *
 * THE REST OF THE SITE IS A LIST OF JOBS and earns nothing by being decorated.
 * This page has to make somebody who has never opened an AI tool want a second
 * level, so it gets colour, real product marks and motion. None of it is load
 * bearing: strip every rule here and the page is still a numbered list.
 *
 * ONE PALETTE, FOUR TIMES. Every rule below is written once against --t and
 * --t-tint rather than four times against four hard-coded palettes, and the
 * four palettes are declared just underneath, keyed on the data-tier attribute
 * that both the bar segment and the section already carry.
 *
 * THEY USED TO BE SET INLINE FROM includes/journey.php, and they moved here
 * when the site got a second theme. A style attribute cannot carry a fallback:
 * @supports does not reach inside one, so a browser too old for light-dark()
 * would have been handed a colour it could not read and this page would have
 * lost every tier colour it has. In a stylesheet the plain value can go first
 * and the two-theme one can replace it, which is what the rest of the file
 * does.
 *
 * WHO IT IS DRAWN FOR. Tier 1 is written for somebody who has never opened an
 * AI tool and may be 65, on a phone, in the Telegram in-app browser. That is
 * the widest part of the audience and the place they bounce, so tier 1 gets
 * bigger type and bigger targets than anything else on the site, and the later
 * tiers tighten as the reader gets more capable. The density gradient is itself
 * a signal of progress.
 */

/*
 * The four tier palettes. All four light values carry white text at AA, because
 * the tick circle fills with the tier colour and the number sits inside it; all
 * four dark values carry --t-fg, which is the navy, for the same reason.
 */
[data-tier="1"] { --t: #147A45; --t-tint: #EAF7F0; }   /* green: the one that says go */
[data-tier="2"] { --t: #0A7FA6; --t-tint: #E8F7FC; }   /* the house brand ink */
[data-tier="3"] { --t: #5B46B8; --t-tint: #EFECFB; }
[data-tier="4"] { --t: #A85A0B; --t-tint: #FDF3E7; }

@supports (color: light-dark(#fff, #000)) {
  [data-tier="1"] { --t: light-dark(#147A45, #5FD69C); --t-tint: light-dark(#EAF7F0, #123027); }
  [data-tier="2"] { --t: light-dark(#0A7FA6, #5CCBEB); --t-tint: light-dark(#E8F7FC, #12313F); }
  [data-tier="3"] { --t: light-dark(#5B46B8, #B6A1E8); --t-tint: light-dark(#EFECFB, #241F3A); }
  [data-tier="4"] { --t: light-dark(#A85A0B, #E8A94E); --t-tint: light-dark(#FDF3E7, #33280F); }
}

/* Announced, not drawn. */
.vh {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

.lv-intro { padding: 4px 0 2px; max-width: 34em; }
.lv-lede { font-size: 20px; color: var(--ink); margin: 2px 0 10px; }
.lv-thesis { font-size: 17px; line-height: 1.6; color: var(--muted); margin: 0 0 4px; }

/* -------------------------------------------------------------- progress */

/* The rank is the reason to keep going, and from level 4 down the list it is
   off screen. It follows, below the site header, which is itself sticky. */
.lv-progress {
  position: sticky;
  top: 64px;
  z-index: 5;
  background: var(--bg);
  padding: 12px 0 10px;
  margin: 10px 0 0;
  border-bottom: 1px solid var(--line);
  box-shadow: 0 6px 6px -6px var(--shadow-soft);
}

/* FOUR SEGMENTS, ONE PER TIER, each grown to the number of levels it holds.
   A single bar says how far; this says where, so the colour you are filling
   tells you which part of the journey you are actually in. */
.lv-bar { display: flex; gap: 3px; height: 8px; }

.lv-bar-seg {
  flex: 1 1 0;
  background: var(--bg-soft);
  border-radius: 999px;
  overflow: hidden;
}

.lv-bar-seg i {
  display: block;
  height: 100%;
  background: var(--t);
  border-radius: 999px;
  /* Ticking a level has to visibly move something. It is the whole reward. */
  transition: width .32s cubic-bezier(.2,.8,.3,1);
}

.lv-count { margin: 8px 0 0; font-size: 15px; color: var(--muted); }
.lv-count strong { color: var(--ink); font-size: 18px; font-weight: 600; letter-spacing: -0.01em; }

.lv-signin { margin: 16px 0 0; padding: 12px 14px; background: var(--bg-soft);
             border-radius: var(--r); font-size: 15.5px; color: var(--muted); }

/* Every one of the 25. Earned, so it is allowed to shout once. */
.lv-allofit {
  margin: 14px 0 0;
  padding: 12px 14px;
  border-radius: 6px;
  background: var(--flag-bg);
  border: 1px solid var(--flag-br);
  color: var(--flag);
  font-size: 16px;
  font-weight: 600;
}

.lv-allofit .ic { vertical-align: -3px; margin-right: 4px; }

/* ------------------------------------------------------------ the tiers */

.lv-tier { margin: 32px 0 0; }
.lv-tier-head { display: flex; align-items: center; gap: 10px; margin: 0 0 4px; }

/* The tier's colour, stated once at the top of its section. */
.lv-tier-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: var(--t);
  color: var(--t-fg);
  flex: 0 0 auto;
}

.lv-tier-badge .ic { width: 17px; height: 17px; vertical-align: 0; }
.lv-tier-h { font-size: 20px; margin: 0; letter-spacing: -0.01em; }

.lv-tier-count {
  margin-left: auto;
  font-size: 14px;
  font-weight: 600;
  color: var(--t);
  background: var(--t-tint);
  border-radius: 999px;
  padding: 4px 11px;
  white-space: nowrap;
}

.lv-tier-blurb { margin: 0 0 8px; font-size: 15.5px; color: var(--muted); max-width: 46em; }

.lv-tier-won {
  margin: 0 0 8px;
  font-size: 15px;
  font-weight: 600;
  color: var(--t);
  background: var(--t-tint);
  border-radius: 6px;
  padding: 9px 12px;
}

.lv-tier-won .ic { vertical-align: -3px; margin-right: 3px; }

.lv-list { list-style: none; margin: 0; padding: 0; border-top: 1px solid var(--line); }
.lv { border-bottom: 1px solid var(--line); }

/* scroll-margin so the no-script redirect to #l12 does not land the row
   underneath the two sticky bars above it. */
.lv { scroll-margin-top: 130px; }

.lv-row { display: flex; align-items: flex-start; gap: 14px; padding: 15px 12px; margin: 0; }

/* A finished level is tinted in its tier's colour, with the colour repeated as
   an edge. Twenty-five bordered cards would read as heavy; a row that changes
   state reads as progress. */
.lv.is-done { background: var(--t-tint); border-radius: 8px; box-shadow: inset 3px 0 0 var(--t); }

/* --------------------------------------------------------------- the tick */

.lv-tick {
  flex: 0 0 auto;
  /* 44px is the tap target, not a look. Anything smaller is a miss on a phone,
     and a miss on level 1 is somebody who never comes back. */
  width: 44px;
  height: 44px;
  border-radius: 50%;
  /* colour-mix keeps the ring in the tier's hue without a second variable per
     tier. Anything without it falls back to the plain --line border above. */
  border: 2px solid var(--line);
  border-color: color-mix(in srgb, var(--t) 35%, var(--surface));
  background: var(--surface);
  color: var(--t);
  font: inherit;
  font-size: 16px;
  font-weight: 700;
  letter-spacing: 0;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  transition: background .18s ease, border-color .18s ease, color .18s ease, transform .18s ease;
}

a.lv-tick:hover { text-decoration: none; }
.lv-tick:hover { border-color: var(--t); transform: scale(1.06); }
.lv-tick:focus-visible { outline: 3px solid var(--t); outline-offset: 2px; }
.lv-tick:disabled { opacity: .55; cursor: default; }

.is-done .lv-tick { background: var(--t); border-color: var(--t); color: var(--t-fg); }

/* The pop. Added by the script when a level lands and removed when it ends, so
   it fires on the tick and never on a page load. */
.lv-tick.is-pop { animation: lv-pop .42s cubic-bezier(.2,1.6,.4,1); }

@keyframes lv-pop {
  0%   { transform: scale(1); }
  40%  { transform: scale(1.28); }
  100% { transform: scale(1); }
}

/* Both states ship in the markup and CSS picks one, so the script toggles a
   single class and never carries a copy of the icon. */
.lv-check { display: none; }
.lv-check .ic { width: 20px; height: 20px; vertical-align: 0; }
.is-done .lv-n { display: none; }
.is-done .lv-check { display: inline-flex; }

/* ---------------------------------------------------------------- the row */

.lv-body { flex: 1; min-width: 0; padding-top: 3px; }
.lv-title { margin: 0; font-size: 17.5px; font-weight: 600; color: var(--ink); line-height: 1.4; }
.lv-note { margin: 4px 0 0; font-size: 15px; color: var(--muted); line-height: 1.5; }

/* A finished level stays legible. Struck through, it becomes a list of things
   you can no longer read, and the point is that it is a record. */
.is-done .lv-title { color: var(--muted); font-weight: 500; }

/* -------------------------------------------------------------- the tools */

/* REAL PRODUCT MARKS, served from assets/img/tools. They cannot be hotlinked:
   the site CSP is img-src 'self' data:, so anything remote simply does not
   render. The files are simple-icons (CC0); the trademarks are not, and each
   one sits beside a link to that company own product, which is what it is
   for. A tool with no file gets a plain monogram from ws_tool_mark(). */
.lv-tools { margin: 8px 0 0; display: flex; flex-wrap: wrap; gap: 7px; }

.lv-tool {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 5px 12px 5px 8px;
  background: var(--surface);
  color: var(--ink);
  font-size: 14.5px;
  font-weight: 500;
  transition: border-color .15s ease, transform .15s ease;
}

.lv-tool:hover { text-decoration: none; border-color: var(--t); transform: translateY(-1px); }

/* THE TILE IS WHY THESE ARE STILL THERE IN THE DARK THEME. Six of the eleven
   marks in that folder are drawn in flat black, because simple-icons ships them
   that way and a brand mark is not ours to recolour. On a navy chip they are
   not dim, they are absent. So each one sits on its own light tile, which is
   invisible in the light theme (the chip behind it is the same white) and is
   the only reason the row reads at all in the dark one. 18px of box around a
   16px mark: border-box means the padding comes out of the tile, so the mark
   itself is the size it always was. */
.lv-tool-mark {
  width: 18px;
  height: 18px;
  flex: 0 0 18px;
  padding: 1px;
  object-fit: contain;
  display: block;
  background: var(--logo-bg);
  border-radius: 3px;
}

/* The fallback, for a product whose mark is not in the folder. Deliberately
   neutral: an invented brand colour sitting beside real logos would read as
   a real mark. */
.lv-tool-letter {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 4px;
  background: var(--bg-soft);
  border: 1px solid var(--line);
  color: var(--ink);
  font-size: 11px;
  font-weight: 700;
}

/* ------------------------------------------------------------- the proof */

/* Six levels ask for a link to what was made. They are the only part of the
   journey worth anything to an employer, because everything else is
   self-ticked and unprovable. */
.lv-proof { margin: 9px 0 0; font-size: 14.5px; overflow-wrap: anywhere; }
.lv-proof a { color: var(--pay); font-weight: 600; }

/* The link box is its own form, sitting under the row rather than inside it,
   so it is indented past the circle by hand: 44px tick, 14px gap, 12px of row
   padding. See the comment in learn-ai.php for why it is a separate form. */
.lv-ask { margin: 0; padding: 0 12px 14px 70px; }
.lv-ask-label { display: block; font-size: 13px; color: var(--muted); margin-bottom: 4px; }
.lv-ask-row { display: flex; gap: 8px; align-items: center; max-width: 440px; }

.lv-ask-btn {
  flex: 0 0 auto;
  font: inherit;
  font-size: 14.5px;
  font-weight: 600;
  padding: 8px 12px;
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--surface);
  color: var(--t);
  cursor: pointer;
}

.lv-ask-btn:hover { border-color: var(--t); }
.lv-ask-btn:focus-visible { outline: 3px solid var(--t); outline-offset: 2px; }
.lv-ask-btn:disabled { opacity: .55; cursor: default; }

.lv-ask-input {
  font: inherit;
  font-size: 16px;      /* 16px or more, or iOS zooms the page on focus */
  padding: 8px 10px;
  border: 1px solid var(--line);
  border-radius: var(--r);
  width: 100%;
  max-width: 340px;
}

.lv-ask-input:focus { outline: 2px solid var(--t); outline-offset: 1px; border-color: var(--t); }
.lv-ask-note { margin: 9px 0 0; font-size: 14px; color: var(--muted); font-style: italic; }

/* ONCE THERE IS A LINK, THE BOX GOES, and only then. A level ticked WITHOUT a
   link keeps its box, so one can be added later without unticking first, which
   is the case the separate form exists to make work. :has because the two are
   no longer siblings; the file already relies on it for .choice and .tier. */
.lv:has(.lv-proof:not([hidden])) .lv-ask { display: none; }

/* -------------------------------------------------------- an even board */

/* TIER 1 USED TO BE BIGGER. It was written for a 65-year-old who had never
   opened an AI tool, and it got 54px circles and 19px titles to match. The
   page is now aimed at someone three or four years into a career who used a
   chat model this week, and oversized type reads to that person as a page for
   somebody else, which is the same bounce from the opposite direction. Every
   tier is now one size.

   The 44px tap target stays everywhere. That is a thumb on a phone, not an
   age, and it is the one thing here that was never decoration. */
.lv-tier-4 .lv-row { padding: 16px 12px; }

/* ---------------------------------------------------------- the confetti */

/* Created by the script, never in the markup: with no JavaScript there is no
   canvas at all. Fixed and click-through so it cannot swallow a tap. */
#lv-confetti {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: 50;
}

@media (max-width: 640px) {
  .lv-progress { top: 56px; }
  .lv { scroll-margin-top: 118px; }
  .lv-lede { font-size: 18px; }
  .lv-row { gap: 12px; padding-left: 10px; padding-right: 10px; }
  .lv-ask { padding-left: 64px; }
  .lv-ask-input { max-width: none; }
  .lv-tier-h { font-size: 18.5px; }
}

/* Somebody has asked for less movement, so nothing moves: no confetti at all
   (the script checks the same query), no pop, no lift. The bar still fills,
   it just arrives there. */
@media (prefers-reduced-motion: reduce) {
  .lv-bar-seg i, .lv-tick, .lv-tool { transition: none; }
  .lv-tick.is-pop { animation: none; }
  .lv-tick:hover, .lv-tool:hover { transform: none; }
}

/* Learn AI was the one head-link that stayed on a phone, because the menu the
   others fold into belonged to the avatar and only existed for somebody signed
   in, and this page is aimed squarely at people who are not. .menu-more gives
   everybody that menu, so Learn AI goes the same way as the rest and the phone
   header is a wordmark, one button, the theme and one handle. */
@media (max-width: 640px) {
  .head-links { gap: 12px; }
}

@media (max-width: 400px) {
  .head-links { gap: 8px; }
  .head-links .btn-sm { padding: 7px 11px; font-size: 14.5px; }
  .brand { font-size: 18.5px; }
}

/* The narrowest phone still in use. Everything in the bar goes down a notch
   together rather than one item being allowed to wrap. */
@media (max-width: 340px) {
  .head-links { gap: 7px; }
  .head-links .btn-sm { padding: 7px 9px; font-size: 14px; }
  .brand { font-size: 17px; }
  .brand svg { width: 20px; height: 20px; }
}

/* ------------------------------------------------------- forms, everywhere */

/*
 * The form system. It began on the profile page and now dresses every form on
 * the site, because two forms that ask the same kind of question should not
 * look like they came from different products.
 *
 * What it changes, none of it decoration:
 *
 * 1. SHORT LISTS ARE CHIPS. A handful of options is a thing you tap, not a
 *    column of grey squares you read down. Nine languages fit in two lines.
 *
 * 2. EVERY SECTION HAS A FACE. A 16px glyph in a tinted square, so scrolling a
 *    long form tells you where you are without reading a word.
 *
 * 3. PROSE HIDES BEHIND THE (i). A sentence that explains rather than asks is
 *    one tap away instead of in the way, and it is still there in full with
 *    JavaScript off, because the server ships it open.
 *
 * 4. THE NATIVE SELECT ARROW IS GONE. It is the single strongest "this is a
 *    Google Form" signal, and this site is mostly selects.
 *
 * The rule at the top of this file still holds where it counts: the colour is
 * on glyphs, chip fills, state pills and borders. Nothing a person has to READ
 * changed colour, and there is still no gradient, no shadow and no card.
 */

/* The four tints are declared with every other colour, at the top of the file. */

.form-page h1 { font-size: 26px; margin-bottom: 4px; }
.page-lede { color: var(--muted); font-size: 16px; margin: 0 0 10px; }

/* The one promise worth keeping on screen. Two sentences became three words
   plus an (i), which is the trade this whole page is making. */
.page-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin: 0 0 4px;
  padding: 5px 11px 5px 9px;
  border-radius: 999px;
  background: var(--leaf-bg);
  color: var(--leaf);
  font-size: 14px;
  font-weight: 600;
}

/* With no lede between them, the pill would sit almost against the heading. */
h1 + .page-pill { margin-top: 6px; }

.page-pill .info-btn { color: var(--leaf); opacity: .75; }

/* ---- sections, shared with the dashboard ---- */

.form-page fieldset { border-top: 0; padding: 0; margin: 26px 0 0; }
.form-page fieldset:first-of-type { margin-top: 18px; }

.form-page legend { padding: 0; text-transform: none; letter-spacing: 0; }

.sec-head {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-size: 17.5px;
  font-weight: 600;
  color: var(--ink);
  letter-spacing: -0.01em;
}

.sec-ic {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  flex: 0 0 30px;
  border-radius: 9px;
}

.sec-ic.sky  { background: var(--sky-bg);  color: var(--sky); }
.sec-ic.leaf { background: var(--leaf-bg); color: var(--leaf); }
.sec-ic.plum { background: var(--plum-bg); color: var(--plum); }
.sec-ic.sun  { background: var(--sun-bg);  color: var(--sun); }

/* A group of chips inside a section: still a real fieldset, because a screen
   reader has to hear "Kinds of work" before the nine checkboxes. */
.form-page fieldset.field-group { margin: 14px 0 0; }

.form-page .field-group-label {
  font-size: 14.5px;
  font-weight: 500;
  color: var(--muted);
  text-transform: none;
  letter-spacing: 0;
}

/* ---- fields ---- */

.form-page label { color: var(--ink); font-size: 14.5px; font-weight: 500; margin: 14px 0 0; }

.form-page input[type=text],
.form-page select {
  margin-top: 6px;
  padding: 11px 12px;
  border-radius: 8px;
  font-weight: 400;
}

.form-page input[type=text]:hover,
.form-page select:hover { border-color: var(--line-hover); }

.form-page input[type=text]:focus,
.form-page select:focus,
.form-page input[type=file]:focus {
  outline: none;
  border-color: var(--brand);
  box-shadow: 0 0 0 3px var(--ring);
}

/* The native arrow is the single strongest "this is a Google Form" signal on
   the page, and there are three selects here. Same chevron as ws_icon(). */
.form-page select {
  appearance: none;
  -webkit-appearance: none;
  padding-right: 34px;
  background-image: var(--sel-chevron);
  background-repeat: no-repeat;
  background-position: right 11px center;
  background-size: 15px;
}

/* ---- chips ---- */

.chip-row { display: flex; flex-wrap: wrap; gap: 8px; margin: 8px 0 0; }

.form-page .chip-btn { display: inline-block; margin: 0; font-weight: 400; }

/* Off-screen rather than display:none: the box still takes focus, so the whole
   group is reachable and answerable from a keyboard. Same for the standing
   choices below, which draw their own box on the row's ::before. */
.chip-btn input,
.form-page .opt-row input { position: absolute; width: 1px; height: 1px; opacity: 0; margin: 0; }

.chip-btn > span {
  display: inline-flex;
  align-items: center;
  padding: 9px 15px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--surface);
  font-size: 15.5px;
  color: var(--ink);
  cursor: pointer;
  user-select: none;
  transition: background .12s, border-color .12s, color .12s;
}

.chip-btn > span:hover { border-color: var(--line-hover); background: var(--bg-soft); }

.chip-btn input:checked + span {
  background: var(--sky-bg);
  border-color: var(--brand);
  color: var(--sky);
  font-weight: 600;
}

.chip-btn input:focus-visible + span {
  outline: 2px solid var(--brand-ink);
  outline-offset: 2px;
}

/* A chip fills in its own section's colour, so a filled-in form is four
   quiet blocks of it rather than one. The class sits on the fieldset, which
   is the thing the colour actually belongs to. */
.tint-leaf .chip-btn input:checked + span { background: var(--leaf-bg); border-color: var(--leaf-br); color: var(--leaf); }
.tint-plum .chip-btn input:checked + span { background: var(--plum-bg); border-color: var(--plum-br); color: var(--plum); }
.tint-sun  .chip-btn input:checked + span { background: var(--sun-bg);  border-color: var(--sun-br);  color: var(--sun); }

/* The tick, so "chosen" is not carried by colour alone. It grows out of zero
   width rather than appearing, which keeps the row from jumping. */
.chip-tick { width: 0; opacity: 0; overflow: hidden; transition: width .12s, opacity .12s, margin .12s; }
.chip-btn input:checked + span .chip-tick { width: 13px; opacity: 1; margin-right: 6px; }

/* ---- the two standing choices, and "remove my CV" ---- */

.form-page .opt-row { display: block; margin: 10px 0 0; }

.form-page .opt-row > span {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 13px 14px;
  border: 1px solid var(--line);
  border-radius: 9px;
  font-size: 15.5px;
  font-weight: 400;
  color: var(--ink);
  cursor: pointer;
  transition: background .12s, border-color .12s;
}

.form-page .opt-row > span:hover { border-color: var(--line-hover); }

.form-page .opt-row > span::before {
  content: "";
  flex: 0 0 18px;
  width: 18px;
  height: 18px;
  border: 1.5px solid var(--box-br);
  border-radius: 5px;
  background: var(--surface) no-repeat center / 12px;
}

.form-page .opt-row input:checked + span { border-color: var(--brand); background: var(--sky-bg); }

.form-page .opt-row input:checked + span::before {
  border-color: var(--brand);
  background-color: var(--brand);
  background-image: var(--opt-tick);
}

.form-page .opt-row input:focus-visible + span { outline: 2px solid var(--brand-ink); outline-offset: 2px; }

/* ---- the CV ---- */

.file-row {
  display: flex;
  align-items: center;
  gap: 9px;
  margin: 12px 0 0;
  padding: 11px 13px;
  border: 1px solid var(--line);
  border-radius: 9px;
  background: var(--bg-soft);
  font-size: 15px;
}

.file-row .ic { color: var(--sun); flex: 0 0 16px; }
.file-row span { overflow-wrap: anywhere; }
.file-row em { margin-left: auto; color: var(--muted); font-style: normal; font-size: 14px; white-space: nowrap; }

.form-page input[type=file] {
  display: block;
  width: 100%;
  margin-top: 6px;
  padding: 12px;
  font: inherit;
  font-size: 14.5px;
  color: var(--muted);
  border: 1px dashed var(--line-file);
  border-radius: 9px;
  background: var(--bg-soft);
}

.form-page input[type=file]::file-selector-button {
  font: inherit;
  font-size: 14.5px;
  font-weight: 500;
  margin-right: 11px;
  padding: 7px 13px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--surface);
  color: var(--ink);
  cursor: pointer;
}

.form-page input[type=file]::file-selector-button:hover { border-color: var(--brand); color: var(--sky); }

/* ---- the (i) and what it hides ---- */

.info-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  margin-left: 5px;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: none;
  color: var(--muted);
  cursor: pointer;
  vertical-align: -5px;
}

/* An author display: beats the UA sheet's [hidden] at equal specificity, and
   the server ships every one of these hidden. Without this line a browser
   running no JavaScript shows buttons that do nothing. */
.info-btn[hidden] { display: none; }

.info-btn:hover,
.info-btn[aria-expanded="true"] { color: var(--brand-ink); }
.info-btn:focus-visible { outline: 2px solid var(--brand-ink); outline-offset: 1px; }

.field-note {
  margin: 8px 0 0;
  padding: 10px 12px;
  border-radius: 8px;
  background: var(--bg-soft);
  font-size: 14px;
  font-weight: 400;
  line-height: 1.55;
  color: var(--muted);
}

/* ---- the box an error is about ---- */

/* The red is the pointer, not the message: the sentence itself is repeated
   here, against the box, because one line at the top of a five-section form
   leaves you counting inputs to work out which it meant. */
.form-page input[aria-invalid="true"],
.form-page select[aria-invalid="true"],
.form-page textarea[aria-invalid="true"] {
  border-color: var(--bad-line);
  background: var(--bad-wash);
}

/* Beats the blue ring above at equal specificity by coming after it, which is
   the point: the field you have just been sent to should not look focused and
   fine at the same time. */
.form-page input[aria-invalid="true"]:focus,
.form-page select[aria-invalid="true"]:focus,
.form-page textarea[aria-invalid="true"]:focus {
  outline: none;
  border-color: var(--bad-line);
  box-shadow: 0 0 0 3px var(--bad-ring);
}

.field-bad {
  display: flex;
  align-items: flex-start;
  gap: 5px;
  margin: 6px 0 0;
  font-size: 14px;
  font-weight: 500;
  line-height: 1.45;
  color: var(--bad);
}

/* flex-start above aligns the mark with the top of the text box, not the top
   of the letters. One pixel down and it sits on the first line. */
.field-bad .ic { margin-top: 1px; color: var(--bad-ic); }

/* ---- saving ---- */

.form-save { margin-top: 28px; }

/* On a phone this form is six screens long, and the button was at the bottom
   of all six. */
@media (max-width: 640px) {
  .form-save {
    position: sticky;
    bottom: 0;
    margin-top: 24px;
    padding: 10px 0 calc(10px + env(safe-area-inset-bottom));
    background: var(--bg);
    border-top: 1px solid var(--line);
  }

  .form-save .btn.block { margin-top: 0; }
  .form-page .two { gap: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .chip-btn > span, .chip-tick, .form-page .opt-row > span { transition: none; }
}

/* ------------------------------------------------------------ the dashboard */

/*
 * Rows, not tables.
 *
 * Both halves of this page were a four-column <table>, and the phone rules
 * answered that by hiding the third column, which is where the applicant count
 * lives. A row carries the same four facts in two lines and carries all of
 * them at every width.
 *
 * The only colour is on the state pill, and it is there to be scanned: amber
 * is the pile that is waiting on somebody.
 */

/* The section heads are shared with the profile form, where .sec-head sits
   inside a <legend> and has to stay inline. Here it is the heading itself. */
h2.sec-head { display: flex; font-size: 18.5px; margin: 30px 0 10px; }

.drows { border-top: 1px solid var(--line); }

.drow {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px 16px;
  padding: 14px 10px;
  margin: 0 -10px;
  border-bottom: 1px solid var(--line);
  color: var(--ink);
}

/* A whole row is the tap target where the row means one thing. The listing
   rows are not links, because they already hold two of their own. */
a.drow:hover { background: var(--bg-soft); text-decoration: none; }

.drow-main { flex: 1 1 220px; min-width: 0; }

.drow-title {
  display: block;
  font-size: 17px;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--ink);
  overflow-wrap: anywhere;
}

a.drow-title { color: var(--brand-ink); }
a.drow-title:hover { text-decoration: underline; }
a.drow:hover .drow-title { color: var(--brand-ink); }

.drow-sub { display: block; margin-top: 3px; font-size: 14.5px; color: var(--muted); }

.drow-side { display: flex; align-items: center; gap: 14px; margin-left: auto; flex: 0 0 auto; }
.drow-age { font-size: 14px; color: var(--muted); white-space: nowrap; }

.drow-stats { display: flex; gap: 14px; font-size: 14.5px; color: var(--muted); white-space: nowrap; }
.drow-stats strong { color: var(--ink); font-weight: 600; }

/* The upsell gets its own line rather than a fifth thing in the row. */
.drow-boost { flex: 1 1 100%; font-size: 14px; }

.state {
  flex: 0 0 auto;
  padding: 4px 11px;
  border: 1px solid;
  border-radius: 999px;
  font-size: 13px;
  font-weight: 600;
  white-space: nowrap;
}

.state-go   { background: var(--leaf-bg); border-color: var(--good-br); color: var(--leaf); }
.state-wait { background: var(--sun-bg);  border-color: var(--state-wait-br); color: var(--sun); }
.state-info { background: var(--sky-bg);  border-color: var(--info-br); color: var(--sky); }
.state-off  { background: var(--bg-soft); border-color: var(--line); color: var(--muted); }

.dash-empty {
  margin: 4px 0 0;
  padding: 22px 18px;
  border: 1px dashed var(--line-dash);
  border-radius: 10px;
  background: var(--bg-soft);
  color: var(--muted);
  font-size: 15.5px;
}

/* The 4px above is for a .dash-empty tucked under a section heading, which is
   where it used to be the only way it appeared. On a dashboard with neither
   listings nor applications it now follows the page title instead, and 4px
   against a 26px heading reads as the box being part of it. */
.page-h + .dash-empty { margin-top: 16px; }

.dash-act { margin: 20px 0 0; }

@media (max-width: 640px) {
  .drow { gap: 6px 12px; }
  .drow-side { margin-left: 0; width: 100%; }
  .drow-age { margin-left: auto; order: 2; }
  .state { order: 1; }
}

/* ------------------------------------------- the rest of the site, harmonised */

/*
 * Everything below brings the older pages onto the system the forms and the
 * dashboard now use: a section has a face, a state wears a colour, a short list
 * is chips, and a sentence that explains rather than asks hides behind an (i).
 *
 * Nothing here adds a hero, a card or a shadow. The homepage is still the list
 * of jobs.
 */

/* ---- page furniture ---- */

.page-sub { color: var(--muted); font-size: 16px; margin: 0 0 4px; max-width: 62ch; }

/* An empty state is a place somebody landed, not an error. Soft box, dashed
   edge, and the way out is a link inside it. */
.empty-box {
  margin: 4px 0 0;
  padding: 22px 18px;
  border: 1px dashed var(--line-dash);
  border-radius: 10px;
  background: var(--bg-soft);
  color: var(--muted);
  font-size: 15.5px;
}

/* A one-line fact worth keeping on screen next to a heading. */
.page-pill.sky  { background: var(--sky-bg);  color: var(--sky); }
.page-pill.plum { background: var(--plum-bg); color: var(--plum); }
.page-pill.sun  { background: var(--sun-bg);  color: var(--sun); }
.page-pill.sky .info-btn  { color: var(--sky); opacity: .75; }
.page-pill.sun .info-btn  { color: var(--sun); opacity: .75; }
.page-pill.plum .info-btn { color: var(--plum); opacity: .75; }

/* A link inside a pill keeps the pill's colour and says it is a link with the
   underline, because a second colour in a pill this small is noise. */
.page-pill a { color: inherit; text-decoration: underline; }
.page-pill a:hover { text-decoration: none; }

.pill-row { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin: 0 0 6px; }
.pill-row .page-pill { margin: 0; }

/* ---- the posting form's jump nav ---- */

/* It was a row of bordered boxes that read as five buttons competing with the
   form. As chips it reads as what it is: where you are and how far is left. */
.form-jump { gap: 8px; }

.form-jump a {
  padding: 7px 13px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--surface);
  color: var(--muted);
  font-size: 14.5px;
  font-weight: 500;
}

.form-jump a:hover { border-color: var(--brand); background: var(--sky-bg); color: var(--sky); text-decoration: none; }

/* ---- the choice cards ---- */

.choice { border-radius: 10px; padding: 15px; }
.choice:has(input:checked) { border-color: var(--brand); background: var(--sky-bg); box-shadow: inset 0 0 0 1px var(--brand); }
.choice input { accent-color: var(--brand); }
.choice-price { color: var(--pay); }

/* ---- a form's own sections ---- */

/* The posting form numbers its sections because the jump nav above refers to
   them by number. The number sits in the tinted square where an icon sits
   elsewhere, which is the same landmark doing the same job. */
.sec-n {
  font-size: 14px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

/* ---- prose pages ---- */

.page-prose { max-width: 68ch; }
.page-prose h2 { display: flex; align-items: center; gap: 10px; font-size: 18.5px; margin: 30px 0 8px; }
.page-prose p { font-size: 17px; line-height: 1.65; color: var(--ink); }
.page-prose ul { font-size: 17px; line-height: 1.65; color: var(--ink); padding-left: 20px; }
.page-prose li { margin-bottom: 6px; }

/* A legal page is a wall by nature. Numbered sections and a little air are the
   only honest improvement: nothing here should look like marketing. */
.legal-updated {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  margin: 0 0 6px;
  padding: 5px 12px 5px 10px;
  border-radius: 999px;
  background: var(--bg-soft);
  border: 1px solid var(--line);
  color: var(--muted);
  font-size: 14px;
}

/* ---- the applicant list ---- */

.applicant { border-bottom: 1px solid var(--line); padding: 18px 10px; margin: 0 -10px; }
.applicant-head { gap: 12px; flex-wrap: wrap; }
.applicant-facts { margin: 6px 0 0; }

.applicant-facts .ic { color: var(--muted); vertical-align: -3px; margin-right: 5px; }

/* ---- badges ---- */

/*
 * A badge labels a thing (Featured, AI work, Verified); a .state says where
 * something got to. They take the same pill shape, and there the resemblance
 * stops.
 *
 * SMALL, UPPERCASE, AND ON THE TEXT BASELINE. A badge sits inside a 17.5px bold
 * role title or beside a 15px company name, so it has to annotate that line
 * rather than join it. Title case at near-title size read as a second word;
 * a glyph beside it made the pill taller than the title it was labelling; and
 * inline-flex took the baseline from the flex line rather than the text, which
 * is what left them all sitting slightly wrong.
 */
.badge {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  line-height: 1.6;
  vertical-align: 2px;
}

/* ---- a company row ---- */

.co-logo { border-radius: 8px; }

/* ---- the listing detail ---- */

.facts {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin: 18px 0;
  padding: 0;
  /* The old rule ruled a line above and below the whole block. Now that each
     fact carries its own border, those two hairlines have nothing to separate
     and sit flush against the top and bottom of the row, reading as a stray
     line crossing the cards. */
  border: 0;
}

/* 200px, not 140: a pay range is the longest thing in here and the widest
   ("$1,000-$1,200/mo") was breaking across two lines mid-word. It was 185 when
   the value was set at 15px; the type went up a rung, so this had to follow. */
.facts > div {
  flex: 1 1 200px;
  min-width: 0;
  padding: 11px 13px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--bg-soft);
}

.facts dt { display: flex; align-items: center; gap: 6px; }
.facts dt .ic { color: var(--muted); }
.facts dd { font-weight: 600; overflow-wrap: break-word; }
.facts .pay { color: var(--pay); }

/* ---- tables that stay tables (admin) ---- */

.tbl th { padding-left: 10px; }
.tbl td { padding-left: 10px; }
.tbl tbody tr:hover { background: var(--bg-soft); }

@media (max-width: 640px) {
  .facts > div { flex: 1 1 100%; }
  .page-prose h2 { font-size: 17.5px; }
}

/* ---- the last few pieces ---- */

/* A fact on an applicant, glyph first, so school, course and year are told
   apart without reading the separators. */
.applicant-fact { display: inline-flex; align-items: center; gap: 5px; margin-right: 14px; white-space: nowrap; }
.applicant-fact .ic { color: var(--muted); flex: 0 0 16px; }

/* Shortlist is the yes. It should not look the same as the no sitting beside
   it, and a green outline says so without being a second primary button. */
.btn.ghost.yes { color: var(--leaf); border-color: var(--good-br); }
.btn.ghost.yes:hover { background: var(--leaf-bg); border-color: var(--leaf); }
.btn .ic { vertical-align: -3px; margin-right: 4px; }

/* The note under the two buttons is about both of them, so it goes under both
   rather than trailing off the end of the row. */
.applicant-buttons .hint { flex: 1 1 100%; margin: 2px 0 0; }

/* The admin tiles, on the same tinted-number footing as everything else. */
.kpis > div { border-radius: 10px; background: var(--bg-soft); }
.kpis span { letter-spacing: -0.02em; }

/* The pay box on a company page is the one number nobody else in Singapore
   publishes, so it gets the green the pay figures use everywhere. */
.paybox { border-radius: 12px; }
.paybox h2 { display: flex; align-items: center; gap: 8px; }
.paybox h2 .ic { color: var(--pay); }
.paybox-main { color: var(--pay); }

/* ------------------------------------------------- the profile button, header */

/*
 * "Finish your profile" on a desktop, "Finish profile" on a phone. Both ship
 * in the one link and CSS shows one, the same trade the theme toggle makes:
 * the long label does not fit a 360px bar, and the short one undersells it on
 * a wide screen. A screen reader hears whichever is showing.
 */
.cta-short { display: none; }

/* Post a job steps down to an outline beside the profile button, and only on
   a desktop, the one width where the two sit side by side. The filled button
   has no border, so the outline pays for its 1px out of the padding, or the
   two sit apart in height. 9/15 and not 6/13: .btn-sm is declared above .btn,
   so on a desktop the filled button actually wears .btn's 10/16. .btn:hover
   would otherwise fill the outline with the brand blue under ink text, which
   reads as a broken button. */
@media (min-width: 641px) {
  .head-links .btn.ghost.btn-sm { padding: 9px 15px; }
  .head-links .btn.ghost.btn-sm:hover { background: var(--bg-soft); border-color: var(--line-hover); }
}

@media (max-width: 640px) {
  .cta-long { display: none; }
  .cta-short { display: inline; }

  /* One button in a phone bar. Signed out it is Post a job, filled again
     because there is no profile button beside it to step down for, and Get
     found by companies leads the menu. A student with a profile to finish
     keeps that button, and Post a job leads their menu. */
  .cta-join .head-cta,
  .cta-finish .head-post { display: none; }
  .cta-join .head-post { background: var(--btn-bg); color: var(--btn-fg); border: 0; }
  .cta-join .head-post:hover { background: var(--btn-bg-hover); }
}

/* ------------------------------------------------------------- profile v2 */

/*
 * THE CV CARD. The one boxed thing on the page, because it is the one thing
 * that answers most of the rest: upload, check, save. The tint stays on the
 * glyph like every other section; the box is the soft surface, not a colour.
 */
.cv-start {
  margin: 18px 0 0;
  padding: 16px 18px 18px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--bg-soft);
}

.cv-start h2 { margin: 0; }
.cv-start-line { margin: 6px 0 0; color: var(--muted); font-size: 15px; line-height: 1.5; }
.cv-start .file-row,
.form-page .cv-start input[type=file] { background: var(--surface); }
.cv-start-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 16px; margin-top: 14px; }
.cv-skip { font-size: 15px; }
.cv-start .btn:disabled { opacity: .7; cursor: progress; }

/* "If you're studying: ...", on every part that belongs to one kind of person.
   Only a page with no script needs them, because there every section is on
   screen at once; profile.js sets .js and shows each part only when it
   applies. */
.js .for-who { display: none; }
.sec-for { font-weight: 400; color: var(--muted); font-size: 15px; letter-spacing: 0; }

.stage-hint { margin: 10px 0 0; font-size: 14px; color: var(--muted); }

/*
 * THE STICKY SECTION BAR. It keeps the posting form's .form-jump behaviour
 * (sticky under the site header, one jump per section) but is drawn as the
 * site's tabs, text with an underline, and not as pills: the page is full of
 * pill-shaped answers, and a row of pills at the top read as seven more
 * questions rather than as where you are. One row that scrolls sideways, an
 * amber dot for a section with something still missing, and the underline
 * under the section on screen.
 */
.profile-jump {
  flex-wrap: nowrap;
  gap: 2px;
  overflow-x: auto;
  margin-top: 22px;
  padding: 0;
  scrollbar-width: none;
}

.profile-jump::-webkit-scrollbar { display: none; }

.profile-jump a {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-bottom: -1px;
  padding: 12px 12px 10px;
  border: 0;
  border-bottom: 2px solid transparent;
  border-radius: 0;
  background: none;
  color: var(--muted);
  font-size: 14.5px;
  font-weight: 500;
}

.profile-jump a:hover { border-color: transparent; border-bottom-color: var(--line-hover); background: none; color: var(--ink); }
.profile-jump a.on { border-bottom-color: var(--brand); color: var(--ink); font-weight: 600; }

.jump-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--warn); flex: 0 0 7px; }

/* Below the site header and the chips, both sticky, so a jump lands on the
   section's heading rather than under the two bars. */
#profile-form fieldset[id] { scroll-margin-top: 128px; }

/* What a match still needs. Amber, not red: nothing was refused, the profile
   saved, and this is what is left rather than a list of mistakes. A count and
   a row of pills, each one a way to where it is asked. */
.need-card {
  margin: 14px 0 0;
  padding: 12px 14px 14px;
  border: 1px solid var(--warn-br);
  border-radius: 12px;
  background: var(--warn-bg);
  color: var(--warn-ink);
}

.need-card-head { display: flex; align-items: flex-start; gap: 8px; margin: 0; font-size: 15px; line-height: 1.45; }
.need-card-head .ic { margin-top: 2px; flex: 0 0 16px; }
.need-pills { display: flex; flex-wrap: wrap; gap: 6px; margin: 10px 0 0 24px; }

.need-pills a {
  padding: 4px 11px;
  border: 1px solid var(--warn-br);
  border-radius: 999px;
  background: var(--surface);
  color: var(--warn-ink);
  font-size: 13.5px;
  font-weight: 500;
}

.need-pills a:hover { border-color: var(--warn); text-decoration: none; }

/* Arrived from Apply: the one line under the heading that says why the form is
   worth finishing. Indented to sit under the words, not the glyph. */
.need-card-line { margin: 4px 0 0 24px; font-size: 14.5px; line-height: 1.5; }

/* And once it is enough to apply with, the way back to the role. The button
   drops under the sentence on a phone rather than squeezing it. */
.apply-ready {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 10px 16px;
  margin: 14px 0 0;
}

.apply-ready p { margin: 0; }

.field-need {
  display: flex;
  align-items: center;
  gap: 5px;
  margin: 8px 0 0;
  font-size: 13.5px;
  font-weight: 500;
  color: var(--warn);
}

.field-need .ic { width: 14px; height: 14px; vertical-align: 0; }

/* An answer read from the CV and not saved yet. */
.from-cv {
  display: inline-block;
  margin-left: 8px;
  padding: 1px 8px;
  border-radius: 999px;
  background: var(--sky-bg);
  color: var(--sky);
  font-size: 12px;
  font-weight: 600;
  vertical-align: 1px;
}

.count-hint { margin-left: 6px; font-size: 13px; font-weight: 400; color: var(--muted); letter-spacing: 0; }

/* Past the limit, the rest of the chips say so before the tap. */
.chip-btn input:disabled + span { opacity: .45; cursor: not-allowed; }
.chip-btn input:disabled + span:hover { border-color: var(--line); background: var(--surface); }

/* Folded extras: "More preferences" and "More skills". The same quiet link
   style the posting form uses for its optional detail. */
.form-page .more > summary { margin-top: 6px; }
.form-page .more > .field-group:first-of-type { margin-top: 4px; }

/* Skills: the person's own field open, the rest in one fold. Inside the fold
   each group gets a small heading; in the open row the section says it. */
.skill-set + .skill-set { margin-top: 14px; }
.skill-set-h { margin: 0; font-size: 13.5px; font-weight: 600; color: var(--muted); }
#skills-main .skill-set-h { display: none; }
#skills-main .chip-row { margin-top: 12px; }
.skills-chosen { margin: 10px 0 0; font-size: 14px; color: var(--muted); }
.skills-chosen span { color: var(--ink); font-weight: 500; }

/* The visibility choice is one of two, so its box is round. */
.form-page .opt-row.radio > span::before { border-radius: 50%; }
.form-page .opt-row > span > span { flex: 1; min-width: 0; line-height: 1.45; }
.opt-sub { display: block; margin-top: 2px; font-size: 14px; color: var(--muted); }

/* The email box sits under the two visibility choices but is not a third one,
   so it stands apart from them. */
.form-page .opt-row.opt-apart { margin-top: 22px; }
