/*
 * Thrivian Care console.
 *
 * The whole identity lives at the top of this file, as tokens. Nothing below
 * names a colour, a size or a face directly, so changing how the console looks
 * is changing this one block rather than hunting through two thousand lines.
 *
 * The old portal grew a rule per page until its stylesheet was 2,143 lines. The
 * way that is avoided here is not discipline, it is that every page is made of
 * the same handful of parts, and those parts are defined once.
 */

/* ------------------------------------------------------------------ *
 * The two faces
 * ------------------------------------------------------------------ */

/*
 * Montserrat carries everything that is read to be used: labels, tables,
 * buttons, numbers. Cormorant is the rare one, kept for the few places where a
 * page should feel addressed to somebody rather than operated — a company name,
 * a page title. Used everywhere it would stop being special; used nowhere it
 * would be an admin panel that could belong to anyone.
 */
@font-face {
  font-family: "Montserrat Console";
  font-style: normal;
  font-weight: 300 800;
  font-display: swap;
  src: url("fonts/montserrat-latin.woff2") format("woff2");
}

@font-face {
  font-family: "Cormorant Console";
  font-style: normal;
  font-weight: 300 700;
  font-display: swap;
  src: url("fonts/cormorant-garamond-latin.woff2") format("woff2");
}

@font-face {
  font-family: "Cormorant Console";
  font-style: italic;
  font-weight: 300 700;
  font-display: swap;
  src: url("fonts/cormorant-garamond-latin-italic.woff2") format("woff2");
}

/* ------------------------------------------------------------------ *
 * The identity
 * ------------------------------------------------------------------ */

:root {
  /* Surfaces, from the ground up. */
  --canvas: #faf9f5;
  --sheet: #fffefa;
  --subtle: #f3f0e8;
  --stage: #071d18;
  --onstage: #f7f6f1;

  /* Ink. */
  --heading: #102d25;
  --text: #27372f;
  --muted: #69736d;
  --anchor: #123d32;

  /* Lines. */
  --line: #e2ded3;
  --line-strong: #c8c1b2;

  /* What a reading means. Never used for decoration. */
  --good: #315f50;
  --warn: #76531f;
  --bad: #a04640;
  --gold: #c7a96b;
  --gold-ink: #76531f;
  --good-face: #eaf0ec;
  --warn-face: #f6efdf;
  --bad-face: #f7eae9;
  /* Not a verdict. Grey, and cooler than the warning brown, because not knowing
   * is an absence of information rather than a mild alarm. */
  --unknown: #b4bcb7;

  /* Type. */
  --face: "Montserrat Console", system-ui, -apple-system, "Segoe UI", sans-serif;
  --face-display: "Cormorant Console", Georgia, "Times New Roman", serif;
  --t-hero: 2rem;
  --t-title: 1.4rem;
  --t-lead: 1.1rem;
  --t-body: 0.95rem;
  --t-label: 0.875rem;
  --t-micro: 0.78rem;

  /* Space, on one ladder so nothing is invented per page. */
  --s1: 0.35rem;
  --s2: 0.6rem;
  --s3: 1rem;
  --s4: 1.5rem;
  --s5: 2.25rem;

  --round: 0.65rem;
  --round-lg: 0.9rem;
  --shadow: 0 1px 2px rgb(16 45 37 / 4%), 0 8px 24px rgb(16 45 37 / 5%);

  --rail: 15rem;
  --rail-tight: 3.75rem;
  --lane: 68rem;
  /* Collapsing the rail is only worth doing if the page actually gets the
   * space, so the lane's own cap rises with it. Prose stays readable because
   * `.pageNote` keeps its own 60ch limit; what widens is tables and cards. */
  --lane-wide: 82rem;
}

/* ------------------------------------------------------------------ *
 * Ground
 * ------------------------------------------------------------------ */

*,
*::before,
*::after { box-sizing: border-box; }

body {
  background: var(--canvas);
  color: var(--text);
  font-family: var(--face);
  font-size: var(--t-body);
  line-height: 1.55;
  margin: 0;
  -webkit-font-smoothing: antialiased;
}

a { color: var(--anchor); }

:focus-visible {
  border-radius: var(--round);
  outline: 2.5px solid var(--gold);
  outline-offset: 2px;
}

/*
 * Hidden from sight, kept for screen readers, and deliberately NOT absolutely
 * positioned. An absolute element with no positioned ancestor is laid out
 * against the initial containing block, which takes it outside any scrolling
 * wrapper it happens to sit in. One of these inside a wide table escaped its
 * `overflow-x: auto` container and stretched the document to 794px on a 375px
 * screen: the whole page scrolled sideways because of a label nobody can see.
 *
 * One clipped pixel in normal flow does the same job and cannot escape.
 */
.sr-only {
  block-size: 1px;
  clip-path: inset(50%);
  display: inline-block;
  inline-size: 1px;
  overflow: hidden;
  white-space: nowrap;
}

/* ------------------------------------------------------------------ *
 * The frame: a rail and a lane
 * ------------------------------------------------------------------ */

.shell {
  display: grid;
  grid-template-columns: var(--rail) minmax(0, 1fr);
  min-block-size: 100dvh;
}

.rail {
  background: var(--stage);
  color: var(--onstage);
  display: flex;
  flex-direction: column;
  gap: var(--s4);
  padding: var(--s4) var(--s3);
  position: sticky;
  inset-block-start: 0;
  block-size: 100dvh;
}

.brand {
  align-items: center;
  display: flex;
  gap: var(--s2);
  min-inline-size: 0;
}

/* The real mark, not a gold letter standing in for one. It is already a circle
 * on its own cream ground, so it needs no frame from us. */
.brandMark {
  block-size: 2rem;
  border-radius: 50%;
  flex: 0 0 auto;
  inline-size: 2rem;
  object-fit: contain;
}

.brandName {
  display: grid;
  min-inline-size: 0;
}

.brandName b {
  font-size: var(--t-label);
  font-weight: 600;
  letter-spacing: 0.01em;
}

.brandName span {
  color: rgb(247 246 241 / 55%);
  font-size: var(--t-micro);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.railNav {
  display: grid;
  gap: 0.15rem;
}

.railLink {
  align-items: center;
  border-radius: var(--round);
  color: rgb(247 246 241 / 70%);
  display: flex;
  gap: var(--s2);
  padding: var(--s2) var(--s2);
  text-decoration: none;
  transition: background 140ms ease, color 140ms ease;
}

.railLink:hover { background: rgb(247 246 241 / 7%); color: var(--onstage); }

.railLink[aria-current="page"] {
  background: rgb(247 246 241 / 11%);
  color: var(--onstage);
  font-weight: 600;
}

.railFoot { margin-block-start: auto; }

.lane {
  display: grid;
  align-content: start;
  gap: var(--s4);
  margin-inline: auto;
  max-inline-size: var(--lane);
  padding: var(--s5) var(--s4) var(--s5);
  inline-size: 100%;
}

/*
 * A grid item's default `min-width: auto` lets its widest child push the track
 * open. Without this a table inside a panel widens the lane past the viewport,
 * and the whole page scrolls sideways instead of the table scrolling inside its
 * own wrapper. It was doing exactly that on Karyawan and Pemeriksaan at 375px.
 */
.lane > *,
.stack > *,
.split > * { min-inline-size: 0; }

.panel { min-inline-size: 0; }

/* ------------------------------------------------------------------ *
 * Part one of a page: who and what
 * ------------------------------------------------------------------ */

.pageHead { display: grid; gap: var(--s1); }

.eyebrow {
  color: var(--gold-ink);
  font-size: var(--t-micro);
  font-weight: 600;
  letter-spacing: 0.14em;
  margin: 0;
  text-transform: uppercase;
}

/*
 * The one place the rare face is allowed on an ordinary page. A page title is
 * the only line addressed to the person reading rather than describing the data
 * below it.
 */
.pageTitle {
  color: var(--heading);
  font-family: var(--face-display);
  font-size: var(--t-hero);
  font-weight: 500;
  letter-spacing: 0.005em;
  line-height: 1.15;
  margin: 0;
}

.pageNote {
  color: var(--muted);
  margin: 0;
  max-inline-size: 60ch;
}

/* ------------------------------------------------------------------ *
 * Part two: tabs within an area
 * ------------------------------------------------------------------ */

.tabs {
  border-block-end: 1px solid var(--line);
  display: flex;
  gap: var(--s3);
  overflow-x: auto;
  scrollbar-width: none;
}

.tabs::-webkit-scrollbar { display: none; }

.tab {
  border-block-end: 2px solid transparent;
  color: var(--muted);
  font-size: var(--t-label);
  font-weight: 500;
  padding-block: var(--s2);
  text-decoration: none;
  white-space: nowrap;
}

.tab[aria-current="page"] {
  border-block-end-color: var(--gold);
  color: var(--heading);
  font-weight: 600;
}

/* ------------------------------------------------------------------ *
 * Part three: what can be done here
 * ------------------------------------------------------------------ */

.actions { display: flex; flex-wrap: wrap; gap: var(--s2); }

.button {
  align-items: center;
  background: transparent;
  border: 1px solid var(--line-strong);
  border-radius: 999px;
  color: var(--anchor);
  cursor: pointer;
  display: inline-flex;
  font: inherit;
  font-size: var(--t-label);
  font-weight: 600;
  gap: var(--s1);
  min-block-size: 2.5rem;
  padding-inline: var(--s3);
  text-decoration: none;
  transition: background 140ms ease, border-color 140ms ease;
}

.button:hover { background: var(--good-face); border-color: var(--anchor); }

.buttonLead {
  background: var(--anchor);
  border-color: var(--anchor);
  color: var(--onstage);
}

.buttonLead:hover { background: var(--stage); border-color: var(--stage); }

.buttonQuiet { border-color: transparent; color: var(--muted); }

.buttonQuiet:hover { background: var(--subtle); border-color: transparent; color: var(--anchor); }

/* ------------------------------------------------------------------ *
 * Part four: the counts
 * ------------------------------------------------------------------ */

.counts {
  display: grid;
  gap: var(--s3);
  grid-template-columns: repeat(auto-fit, minmax(min(11rem, 100%), 1fr));
}

.count {
  background: var(--sheet);
  border: 1px solid var(--line);
  border-radius: var(--round-lg);
  box-shadow: var(--shadow);
  display: grid;
  gap: var(--s1);
  padding: var(--s3);
}

.countLabel {
  color: var(--muted);
  font-size: var(--t-micro);
  letter-spacing: 0.08em;
  margin: 0;
  text-transform: uppercase;
}

.countValue {
  color: var(--heading);
  font-size: var(--t-title);
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  letter-spacing: -0.01em;
  line-height: 1;
  margin: 0;
}

/*
 * The period a count covers, said on the count itself.
 *
 * The old portal showed "Pemeriksaan periode ini" as 20 on one page and 69 on
 * another because the pages used different windows and neither said so. A count
 * with a window has to carry it.
 */
.countWhen {
  color: var(--muted);
  font-size: var(--t-micro);
  margin: 0;
}

.countWarn { border-color: color-mix(in srgb, var(--warn) 30%, var(--line)); }
.countWarn .countValue { color: var(--warn); }

/* ------------------------------------------------------------------ *
 * Part five: the panel everything else sits in
 * ------------------------------------------------------------------ */

.panel {
  background: var(--sheet);
  border: 1px solid var(--line);
  border-radius: var(--round-lg);
  box-shadow: var(--shadow);
  display: grid;
  gap: var(--s3);
  padding: var(--s4);
}

.panelHead {
  align-items: start;
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2) var(--s3);
  justify-content: space-between;
}

.panelTitle {
  color: var(--heading);
  font-size: var(--t-lead);
  font-weight: 600;
  margin: 0;
}

.panelNote { color: var(--muted); font-size: var(--t-label); margin: 0; }

.panelTools { display: flex; flex-wrap: wrap; gap: var(--s2); }

.field {
  background: var(--canvas);
  border: 1px solid var(--line);
  border-radius: var(--round);
  color: var(--text);
  font: inherit;
  font-size: var(--t-label);
  min-block-size: 2.5rem;
  padding-inline: var(--s3);
}

.field:focus { border-color: var(--anchor); }

/* ------------------------------------------------------------------ *
 * Tables
 * ------------------------------------------------------------------ */

/* Positioned, so anything absolute inside stays inside rather than being laid
 * out against the page and dragging the document wider than the screen. */
.tableWrap {
  margin-inline: calc(var(--s4) * -1);
  overflow-x: auto;
  position: relative;
}

.table {
  border-collapse: collapse;
  inline-size: 100%;
  /*
   * No width floor. An earlier version set one so a name and a timestamp would
   * each keep one line, which was right for a table spanning the page and wrong
   * the moment the same table sat in a narrow column: it scrolled sideways with
   * room to spare. The thing that actually had to stay on one line was the
   * timestamp, and `.when` says so directly.
   */
}

.table th {
  color: var(--muted);
  font-size: var(--t-micro);
  font-weight: 600;
  letter-spacing: 0.08em;
  padding: var(--s2) var(--s3);
  text-align: start;
  text-transform: uppercase;
  white-space: nowrap;
}

.table td {
  border-block-start: 1px solid var(--line);
  padding: var(--s3);
  vertical-align: middle;
}

/* The outer columns line up with the panel's own edge rather than sitting a
 * third of a centimetre inside it. */
.table th:first-child,
.table td:first-child { padding-inline-start: var(--s4); }

.table th:last-child,
.table td:last-child { padding-inline-end: var(--s4); text-align: end; }

/* A last column that is a sentence rather than a number or a control. The rule
 * above is right for counts and buttons, which line up better against the right
 * edge, and wrong for prose: right-aligned text has a ragged left edge, and a
 * reader has to hunt for the start of every line. */
.table th.prose,
.table td.prose { text-align: start; }

/* A timestamp is one thing, so it stays on one line. */
.when {
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.table tbody tr:hover { background: var(--subtle); }

.who { display: grid; gap: 0.1rem; min-inline-size: 0; }
.who b { color: var(--heading); font-weight: 600; }
.who span { color: var(--muted); font-size: var(--t-micro); letter-spacing: 0.04em; }

.quiet { color: var(--muted); }

/* ------------------------------------------------------------------ *
 * Badges
 * ------------------------------------------------------------------ */

.badge {
  border: 1px solid currentcolor;
  border-radius: 999px;
  display: inline-block;
  font-size: var(--t-micro);
  font-weight: 600;
  padding: 0.1rem 0.55rem;
  white-space: nowrap;
}

.badgeGood { color: var(--good); }
.badgeWarn { color: var(--warn); }
.badgeBad { color: var(--bad); }
.badgeQuiet { color: var(--muted); }

/* ------------------------------------------------------------------ *
 * Things that need doing
 * ------------------------------------------------------------------ */

.todo { display: grid; gap: var(--s2); }

.todoItem {
  align-items: center;
  background: var(--canvas);
  border: 1px solid var(--line);
  border-radius: var(--round);
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2) var(--s3);
  padding: var(--s3);
}

.todoItem p { flex: 1 1 14rem; margin: 0; min-inline-size: 0; }

.todoWarn { background: var(--warn-face); border-color: color-mix(in srgb, var(--warn) 26%, transparent); }
.todoBad { background: color-mix(in srgb, var(--bad) 7%, var(--canvas)); border-color: color-mix(in srgb, var(--bad) 26%, transparent); }

/* ------------------------------------------------------------------ *
 * Nothing here yet
 * ------------------------------------------------------------------ */

.empty {
  display: grid;
  gap: var(--s1);
  justify-items: center;
  padding: var(--s5) var(--s3);
  text-align: center;
}

.emptyTitle { color: var(--heading); font-weight: 600; margin: 0; }
.emptyNote { color: var(--muted); margin: 0; max-inline-size: 40ch; }

/* ------------------------------------------------------------------ *
 * Icons
 * ------------------------------------------------------------------ */

.icon {
  block-size: 1.05rem;
  fill: none;
  flex: 0 0 auto;
  inline-size: 1.05rem;
  stroke: currentcolor;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-width: 1.8;
}

/* ------------------------------------------------------------------ *
 * Narrow
 * ------------------------------------------------------------------ */

@media (max-width: 60rem) {
  .shell { grid-template-columns: minmax(0, 1fr); }

  .rail {
    block-size: auto;
    flex-direction: row;
    align-items: center;
    gap: var(--s3);
    overflow-x: auto;
    position: static;
    padding: var(--s2) var(--s3);
    scrollbar-width: none;
  }

  .rail::-webkit-scrollbar { display: none; }
  .railNav { display: flex; gap: var(--s1); }
  .railLink span { white-space: nowrap; }
  .railFoot { margin-block-start: 0; }
  .lane { padding: var(--s4) var(--s3); }
  .tableWrap { margin-inline: calc(var(--s4) * -1); }
}

/* ------------------------------------------------------------------ *
 * Two columns, and the parts a Beranda is made of
 * ------------------------------------------------------------------ */

/*
 * The main column carries what has to be acted on, the side column what only
 * has to be glanced at. Below the breakpoint they stack in that same order,
 * so the phone gets the work first and the reassurance second.
 */
.split {
  display: grid;
  gap: var(--s4);
  grid-template-columns: minmax(0, 1.8fr) minmax(0, 1fr);
}

.stack { display: grid; align-content: start; gap: var(--s4); }

@media (max-width: 60rem) {
  .split { grid-template-columns: minmax(0, 1fr); }
}

/*
 * The one chart on the page. A single bar rather than a ring, because the
 * question is what share of people sit in each band, and a bar answers that
 * without anyone having to judge an angle.
 */
.meter {
  display: flex;
  block-size: 0.85rem;
  border-radius: 999px;
  overflow: hidden;
}

.meterPart { display: block; flex-basis: 0; }

.meterKey {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2) var(--s4);
  list-style: none;
  margin: var(--s3) 0 0;
  padding: 0;
}

.meterKey li { align-items: baseline; display: flex; gap: 0.35rem; }
.meterKey b {
  color: var(--heading);
  font-size: var(--t-lead);
  font-variant-numeric: tabular-nums;
}
.meterKey span { color: var(--muted); font-size: var(--t-label); }
.meterKey i { color: var(--muted); font-size: var(--t-micro); font-style: normal; }

.dot { block-size: 0.55rem; border-radius: 2px; flex: 0 0 auto; inline-size: 0.55rem; }

/*
 * The four bands, defined once, and named exactly as `console/rules.py` names
 * them. They did not always agree: the stylesheet said warn/bad/none while the
 * rules said watch/urgent/unknown, so three of the four bands rendered fully
 * transparent and the condition bar showed one green sliver against nothing.
 * `tests_style.py` now fails if the two ever drift apart again.
 *
 * "unknown" is grey on purpose, and deliberately cooler than the warning brown
 * beside it: not knowing is an absence of information, not a mild warning.
 */
.band-good    { background: var(--good); }
.band-watch   { background: var(--warn); }
.band-urgent  { background: var(--bad); }
.band-unknown { background: var(--unknown); }

/* A job carries its own window on a second line, quieter than the count. */
.jobBar { block-size: 1.6rem; border-radius: 999px; flex: 0 0 auto; inline-size: 3px; }
.todoItem p i {
  color: var(--muted);
  display: block;
  font-size: var(--t-micro);
  font-style: normal;
}
.todoItem p b { font-size: var(--t-lead); font-variant-numeric: tabular-nums; }

/* ------------------------------------------------------------------ *
 * Phone
 * ------------------------------------------------------------------ */

/*
 * On a phone the rail cannot be a rail: 15rem of a 375px screen is two thirds
 * of it. It becomes a thin top strip carrying only the brand, and the
 * destinations move to a bar at the bottom where a thumb reaches them.
 */
.tabbar { display: none; }

@media (max-width: 60rem) {
  .shell { grid-template-columns: minmax(0, 1fr); }

  .rail {
    block-size: auto;
    flex-direction: row;
    align-items: center;
    gap: var(--s3);
    padding: var(--s2) var(--s3);
    position: sticky;
    inset-block-start: 0;
    z-index: 5;
  }

  /* The rail's own links and its sign-out foot belong to the bottom bar now. */
  .rail .railNav,
  .rail .railFoot { display: none; }

  .lane {
    padding: var(--s4) var(--s3);
    /* Room for the bottom bar, plus whatever the phone's own chrome takes. */
    padding-block-end: calc(4.5rem + env(safe-area-inset-bottom, 0px));
  }

  .tabbar {
    background: var(--sheet);
    border-block-start: 1px solid var(--line);
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    inset-block-end: 0;
    inset-inline: 0;
    padding-block: var(--s2);
    padding-block-end: calc(var(--s2) + env(safe-area-inset-bottom, 0px));
    position: fixed;
    z-index: 10;
  }

  .tab-item {
    color: var(--muted);
    display: grid;
    font-size: var(--t-micro);
    gap: 0.15rem;
    justify-items: center;
    text-decoration: none;
  }

  .tab-item[aria-current="page"] { color: var(--heading); font-weight: 600; }

  /* Two columns of counts is one column too many at this width. */
  .counts { grid-template-columns: minmax(0, 1fr); }
}

/* ------------------------------------------------------------------ *
 * The rail's submenu
 * ------------------------------------------------------------------ */

/*
 * Only the area you are standing in opens. Everything else stays one line, so
 * the rail answers "where am I and what can I do here" without ever becoming a
 * tree somebody has to navigate.
 */
.railSub {
  display: grid;
  gap: 0.05rem;
  margin: 0.2rem 0 0.5rem 0.75rem;
}

.railSubLink {
  align-items: center;
  border-inline-start: 1px solid rgb(247 246 241 / 16%);
  color: rgb(247 246 241 / 58%);
  display: flex;
  font-size: var(--t-micro);
  gap: 0.3rem;
  padding: 0.22rem 0.5rem;
  text-decoration: none;
  transition: color 140ms ease;
}

.railSubLink:hover { color: var(--onstage); }

.railSubText { min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* An action is not a destination. Marking it stops a reader clicking one and
 * waiting for a page that is never going to load. */
.railSubDo { color: rgb(247 246 241 / 72%); }
.railSubPlus { color: var(--gold); font-weight: 700; }

/* The tally makes the rail itself a status board: before reading a word, the
 * reader can see that seven things are waiting. */
.railTally {
  color: rgb(247 246 241 / 80%);
  font-variant-numeric: tabular-nums;
  margin-inline-start: auto;
}

/* ------------------------------------------------------------------ *
 * Chips, notices, and the parts pages share
 * ------------------------------------------------------------------ */

.chips {
  align-items: center;
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2);
}

.chip {
  border: 1px solid var(--line);
  border-radius: 999px;
  color: var(--text);
  font-size: var(--t-label);
  padding: 0.22rem 0.7rem;
  text-decoration: none;
  white-space: nowrap;
}

.chip:hover { border-color: var(--line-strong); }

.chipOn {
  background: var(--stage);
  border-color: var(--stage);
  color: var(--onstage);
  font-weight: 600;
}

.chipSearch { margin-inline-start: auto; min-inline-size: 12rem; }
.chipSearch .field { inline-size: 100%; }

.notice {
  border-radius: var(--round);
  padding: var(--s3);
}
.noticeWarn { background: var(--warn-face); color: var(--warn); }
.noticeQuiet { background: var(--subtle); color: var(--muted); }

.footNote { color: var(--muted); font-size: var(--t-label); margin: var(--s3) 0 0; }

/* A coloured bar at the start of a row. It carries the same meaning as a status
 * word but is read at a glance, and it survives a narrow screen where a whole
 * column would not. */
.lead { align-items: center; display: flex; gap: var(--s2); min-inline-size: 0; }
.leadBar { block-size: 1.6rem; border-radius: 999px; flex: 0 0 auto; inline-size: 3px; }

.badge-good { background: var(--good-face); color: var(--good); }
.badge-watch { background: var(--warn-face); color: var(--warn); }
.badge-urgent { background: var(--bad-face); color: var(--bad); }
.badge-unknown { background: var(--subtle); color: var(--muted); }

.hintThin { color: var(--muted); display: block; font-size: var(--t-micro); font-style: normal; }

.reachPrivate { color: var(--heading); font-weight: 600; }

.panelBad { border-color: var(--bad); }

.cards { display: grid; gap: var(--s4); grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr)); }

.emptyGood .emptyTitle { color: var(--good); }

/* ------------------------------------------------------------------ *
 * A job on Beranda: announced and finished in the same place
 * ------------------------------------------------------------------ */

.job { border-block-start: 1px solid var(--line); }
.job:first-of-type { border-block-start: 0; }

.jobHead {
  align-items: center;
  cursor: pointer;
  display: flex;
  gap: var(--s3);
  list-style: none;
  padding-block: var(--s3);
}
.jobHead::-webkit-details-marker { display: none; }

.jobSay { flex: 1; min-inline-size: 0; }
.jobSay b { color: var(--heading); font-size: var(--t-lead); font-variant-numeric: tabular-nums; }
.jobSay i { color: var(--muted); display: block; font-size: var(--t-micro); font-style: normal; }

.jobOpen {
  border: 1px solid var(--line);
  border-radius: var(--round);
  color: var(--anchor);
  font-size: var(--t-label);
  font-weight: 600;
  padding: 0.22rem 0.7rem;
}

.jobRows { display: grid; gap: 0.15rem; padding-block-end: var(--s3); }

.jobRow {
  align-items: center;
  background: var(--subtle);
  border-radius: var(--round);
  color: inherit;
  display: flex;
  gap: var(--s3);
  justify-content: space-between;
  padding: var(--s2) var(--s3);
  text-decoration: none;
}
.jobRow b { color: var(--heading); }
.jobRow i { color: var(--muted); display: block; font-size: var(--t-micro); font-style: normal; }

/* ------------------------------------------------------------------ *
 * The steps between being added and being able to use a tablet
 * ------------------------------------------------------------------ */

.steps { display: grid; gap: var(--s2); list-style: none; margin: 0; padding: 0; }

.step { align-items: center; color: var(--muted); display: flex; gap: var(--s2); }

.stepMark {
  align-items: center;
  background: var(--subtle);
  block-size: 1.45rem;
  border-radius: 50%;
  color: var(--muted);
  display: flex;
  flex: 0 0 auto;
  font-size: var(--t-micro);
  font-weight: 700;
  inline-size: 1.45rem;
  justify-content: center;
}

.stepDone { color: var(--text); }
.stepDone .stepMark { background: var(--good-face); color: var(--good); }

/* The step somebody is actually stuck on. Naming it is the difference between
 * knowing they are not ready and knowing what to press. */
.stepNow { color: var(--heading); font-weight: 600; }
.stepNow .stepMark { background: var(--warn-face); color: var(--warn); }

/* ------------------------------------------------------------------ *
 * The phone's "Lainnya"
 * ------------------------------------------------------------------ */

.moreSheet { display: none; }

@media (max-width: 60rem) {
  .moreSheet:target {
    background: var(--canvas);
    display: block;
    inset: 0;
    overflow-y: auto;
    padding: var(--s4) var(--s3) 6rem;
    position: fixed;
    z-index: 20;
  }

  .moreTitle {
    color: var(--heading);
    font-family: var(--face-display);
    font-size: var(--t-hero);
    font-weight: 500;
    margin: 0 0 var(--s4);
  }

  .moreList { display: grid; gap: 0.15rem; }

  .moreList a {
    align-items: center;
    background: var(--sheet);
    border: 1px solid var(--line);
    border-radius: var(--round);
    color: var(--heading);
    display: flex;
    gap: var(--s3);
    padding: var(--s3);
    text-decoration: none;
  }

  .chipSearch { margin-inline-start: 0; inline-size: 100%; }
}

/* ------------------------------------------------------------------ *
 * Tables become cards on a phone
 * ------------------------------------------------------------------ */

/*
 * A five-column table on a 375px screen leaves each column 75 pixels, and no
 * amount of scrolling makes that readable: the reader loses the header before
 * they reach the value. Below this width each row becomes a small card, each
 * cell becomes a labelled line, and the header row is dropped because every
 * cell now carries its own label.
 *
 * `data-label` on the cell is what makes it work. A cell without one renders as
 * a plain line, which is right for the first cell of a row: that one is the
 * name, and naming it "Karyawan" above the name would be noise.
 */
@media (max-width: 48rem) {
  .tableWrap { margin-inline: 0; overflow-x: visible; }

  /*
   * A panel that exists only to hold a table stops drawing itself, because each
   * row is now its own card and a card inside a card reads as a mistake. The
   * panel keeps its heading; it just loses the box around it.
   */
  .panel:has(> .tableWrap) {
    background: none;
    border: 0;
    box-shadow: none;
    padding: 0;
  }

  .table { display: block; }
  .table thead { display: none; }
  .table tbody { display: grid; gap: var(--s2); }

  .table tr {
    background: var(--sheet);
    border: 1px solid var(--line);
    border-radius: var(--round);
    display: grid;
    gap: 0.2rem;
    padding: var(--s3);
  }

  .table td {
    border: 0;
    display: flex;
    gap: var(--s3);
    justify-content: space-between;
    padding: 0;
  }

  /* The label is drawn from the attribute rather than repeated in the markup,
   * so a column renamed in the header cannot disagree with its phone label. */
  .table td[data-label]::before {
    color: var(--muted);
    content: attr(data-label);
    flex: 0 0 auto;
    font-size: var(--t-micro);
    letter-spacing: 0.06em;
    text-transform: uppercase;
  }

  /*
   * A sentence does not sit beside its label. On a phone that leaves it about
   * eighteen characters wide, which breaks four words into five lines; below
   * the label it gets the whole card.
   */
  .table td.prose {
    display: block;
    text-align: start;
  }

  .table td.prose[data-label]::before {
    display: block;
    margin-block-end: 0.15rem;
  }

  /* The desktop rule insets the outer columns to line up with the panel edge.
   * Inside a card that inset is just a gap nobody asked for. */
  .table td:first-child,
  .table th:first-child {
    border-block-end: 1px solid var(--line);
    margin-block-end: 0.35rem;
    padding-block-end: 0.35rem;
    padding-inline-start: 0;
  }

  .table td:last-child,
  .table th:last-child {
    justify-content: flex-start;
    padding-inline-end: 0;
    text-align: start;
  }

  /* The colour bar keeps its meaning here and does the work the condition
   * column did on a wide screen. */
  .lead { align-items: flex-start; }
  .leadBar { block-size: 2.2rem; }
}

/* ------------------------------------------------------------------ *
 * Actions, as a few short steps
 * ------------------------------------------------------------------ */

/* Alpine sets this until it has taken over, so a step never flashes all its
 * siblings on the way in. Without Alpine the attribute is never applied and
 * every step renders stacked, which is the intended fallback. */
[x-cloak] { display: none !important; }

.sheetBack {
  align-items: flex-end;
  background: rgb(7 29 24 / 34%);
  display: flex;
  inset: 0;
  justify-content: center;
  padding: var(--s3);
  position: fixed;
  z-index: 40;
}

/* On a page rather than over one, it is just a panel. */
#sheetPage .sheetBack {
  background: none;
  display: block;
  padding: 0;
  position: static;
}

.sheetBox {
  background: var(--sheet);
  border: 1px solid var(--line);
  border-radius: var(--round-lg);
  box-shadow: var(--shadow);
  display: grid;
  gap: var(--s3);
  max-block-size: calc(100dvh - 2 * var(--s3));
  max-inline-size: 30rem;
  overflow-y: auto;
  padding: var(--s4);
  inline-size: 100%;
}

@media (min-width: 40rem) {
  .sheetBack { align-items: center; }
}

.sheetHead { display: grid; gap: var(--s1); }

.sheetTitle {
  color: var(--heading);
  font-family: var(--face-display);
  font-size: var(--t-title);
  font-weight: 500;
  line-height: 1.2;
  margin: 0;
}

.sheetSteps { color: var(--muted); font-size: var(--t-micro); margin: 0; }

/* Where you are, without making anyone count. */
.sheetRail { display: flex; gap: 0.25rem; }

.sheetPip {
  background: var(--line);
  block-size: 3px;
  border-radius: 999px;
  flex: 1;
  transition: background 160ms ease;
}

.sheetPipOn { background: var(--gold); }

.sheetForm { display: grid; gap: var(--s3); }

.sheetStep { border: 0; display: grid; gap: var(--s3); margin: 0; padding: 0; }

.sheetStepTitle {
  color: var(--heading);
  font-size: var(--t-lead);
  font-weight: 700;
  padding: 0;
}

.sheetStepNote { color: var(--muted); font-size: var(--t-label); margin: 0; max-inline-size: 46ch; }

.sheetBody { margin: 0; }

.sheetList {
  color: var(--text);
  display: grid;
  gap: var(--s2);
  font-size: var(--t-label);
  margin: 0;
  padding-inline-start: 1.1rem;
}

.sheetSummary {
  background: var(--subtle);
  border-radius: var(--round);
  color: var(--muted);
  font-size: var(--t-label);
  margin: 0;
  padding: var(--s3);
}

.sheetFoot {
  align-items: center;
  border-block-start: 1px solid var(--line);
  display: flex;
  gap: var(--s2);
  justify-content: flex-end;
  padding-block-start: var(--s3);
}

/* Cancel first, so the safe choice is where a hand lands, and it stays left
 * however many step buttons appear beside it. */
.sheetFoot > .button:first-child { margin-inline-end: auto; }

/* ------------------------------------------------------------------ *
 * Fields, and saying what is wrong where it is wrong
 * ------------------------------------------------------------------ */

.field-row { display: grid; gap: 0.3rem; }

.field-row label {
  color: var(--heading);
  font-size: var(--t-label);
  font-weight: 600;
}

/* Not a red star with no explanation: it carries a title, and every optional
 * field is simply unmarked. */
.field-must { color: var(--bad); margin-inline-start: 0.15rem; }

.field-row input,
.field-row select,
.field-row textarea {
  background: var(--sheet);
  border: 1px solid var(--line-strong);
  border-radius: var(--round);
  color: var(--text);
  font: inherit;
  padding: 0.5rem 0.65rem;
  inline-size: 100%;
}

.field-row input:focus-visible,
.field-row select:focus-visible,
.field-row textarea:focus-visible { border-color: var(--gold); }

/*
 * `:user-invalid`, not `:invalid`.
 *
 * `:invalid` matches an empty required field the instant it renders, so every
 * form opened with three red boxes telling somebody off for not yet having
 * typed. `:user-invalid` waits until they have actually answered and moved on,
 * which is the only moment the complaint is true.
 */
.field-row input:user-invalid,
.field-row select:user-invalid,
.field-row textarea:user-invalid { border-color: var(--bad); }

.field-bad input,
.field-bad select,
.field-bad textarea { border-color: var(--bad); }

.field-help { color: var(--muted); font-size: var(--t-micro); margin: 0; }
.field-error { color: var(--bad); font-size: var(--t-micro); font-weight: 600; margin: 0; }

/* ------------------------------------------------------------------ *
 * Buttons that cannot be pressed, and buttons that should be read first
 * ------------------------------------------------------------------ */

/* A disabled control has to look disabled, or somebody presses it repeatedly
 * and concludes the system is broken. */
.button:disabled,
.button[disabled] {
  cursor: not-allowed;
  opacity: 0.45;
}

.buttonDanger {
  background: var(--bad);
  border-color: var(--bad);
  color: #fff;
}

.buttonDanger:disabled { background: var(--bad); }

/* A step that is not finished yet. Quieter than the confirm colour, because
 * nothing is wrong: the reader simply has not reached the end of it. */
.buttonWaiting {
  background: var(--sheet);
  border-color: var(--line-strong);
  color: var(--muted);
}

/* Rendered as a whole page, the lane heading already carries the title and the
 * note, so both are hidden inside the sheet. The title stays in the markup
 * because the dialog's label points at it. */
#sheetPage .sheetHead .sheetStepNote { display: none; }

#sheetPage .sheetTitle {
  block-size: 1px;
  clip-path: inset(50%);
  display: inline-block;
  inline-size: 1px;
  overflow: hidden;
  white-space: nowrap;
}

/* ------------------------------------------------------------------ *
 * A rail that folds away
 * ------------------------------------------------------------------ */

.railItem { position: relative; }

.railGlyph { display: inline-flex; flex: 0 0 auto; position: relative; }

/* Folded to icons there is no room for a count, but "something is waiting"
 * still has to reach the reader, so it becomes a dot on the icon. */
.railSpot {
  background: var(--gold);
  block-size: 0.45rem;
  border: 2px solid var(--stage);
  border-radius: 50%;
  inline-size: 0.45rem;
  inset-block-start: -0.15rem;
  inset-inline-end: -0.2rem;
  position: absolute;
}

.railToggle {
  background: none;
  border: 0;
  border-radius: var(--round);
  color: rgb(247 246 241 / 55%);
  cursor: pointer;
  display: grid;
  margin-inline-start: auto;
  padding: 0.25rem;
  place-items: center;
}

.railToggle:hover { background: rgb(247 246 241 / 10%); color: var(--onstage); }

/* The flyout only exists while the rail is folded. */
.railFly { display: none; }

@media (min-width: 60rem) {
  [data-rail="tight"] .shell {
    grid-template-columns: var(--rail-tight) minmax(0, 1fr);
  }

  [data-rail="tight"] .lane { max-inline-size: var(--lane-wide); }

  [data-rail="tight"] .rail { padding-inline: 0.5rem; }

  [data-rail="tight"] .brand { justify-content: center; }
  [data-rail="tight"] .brandName { display: none; }
  [data-rail="tight"] .railToggle { margin-inline-start: 0; }

  [data-rail="tight"] .railLink { justify-content: center; padding-inline: 0; }
  [data-rail="tight"] .railWord { display: none; }

  /* The submenu goes with the labels. Nothing is lost: the flyout below
   * carries the same entries, at the same two clicks. */
  [data-rail="tight"] .railSub { display: none; }

  [data-rail="tight"] .railItem:hover .railFly,
  [data-rail="tight"] .railItem:focus-within .railFly {
    display: grid;
  }

  [data-rail="tight"] .railFly {
    background: var(--stage);
    border: 1px solid rgb(247 246 241 / 14%);
    border-radius: var(--round);
    box-shadow: 0 10px 30px rgb(7 29 24 / 35%);
    gap: 0.05rem;
    inset-block-start: 0;
    inset-inline-start: calc(100% + 0.4rem);
    min-inline-size: 11rem;
    padding: var(--s2);
    position: absolute;
    z-index: 30;
  }

  .railFly b {
    color: var(--onstage);
    font-size: var(--t-label);
    padding: 0.2rem 0.4rem 0.35rem;
  }

  .railFly a {
    align-items: center;
    border-radius: 0.28rem;
    color: rgb(247 246 241 / 66%);
    display: flex;
    font-size: var(--t-micro);
    gap: 0.3rem;
    padding: 0.24rem 0.4rem;
    text-decoration: none;
    white-space: nowrap;
  }

  .railFly a:hover { background: rgb(247 246 241 / 10%); color: var(--onstage); }
}

/* On a phone the rail is already a top strip and the bottom bar does the
 * navigating, so folding it would mean nothing. The button goes away rather
 * than sitting there doing nothing. */
@media (max-width: 60rem) {
  .railToggle { display: none; }
}

/*
 * Folded, the rail is 60px wide and its padding leaves 44 usable. The mark is
 * 32 and the toggle about 24, so side by side they overflowed and the button
 * sat on top of the logo. Stacked, both fit and the toggle stays where the hand
 * already learned to find it.
 */
@media (min-width: 60rem) {
  [data-rail="tight"] .brand {
    flex-direction: column;
    gap: 0.35rem;
  }
}

/* ------------------------------------------------------------------ *
 * Headings you can click
 * ------------------------------------------------------------------ */

.sortLink {
  align-items: center;
  color: inherit;
  display: inline-flex;
  gap: 0.3rem;
  text-decoration: none;
  white-space: nowrap;
}

.sortLink:hover { color: var(--heading); }
.sortOn { color: var(--heading); }

/* Dim until the column is the one in use, so the table is not a row of arrows
 * competing with the words beside them. */
.sortArrow { font-size: 0.75em; opacity: 0.35; }
.sortOn .sortArrow { color: var(--gold-ink); opacity: 1; }

/* ------------------------------------------------------------------ *
 * A number you can open
 * ------------------------------------------------------------------ */

/*
 * A figure nobody can follow is a number you have to take on trust. Underlined
 * rather than coloured, because the colours in this console already mean
 * something about health and must not start meaning "clickable" as well.
 */
.figure {
  color: inherit;
  text-decoration: underline;
  text-decoration-color: var(--line-strong);
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
}

.figure:hover { text-decoration-color: var(--gold); }

a.badge { text-decoration: none; }
a.badge:hover { outline: 1px solid var(--line-strong); }

/* ------------------------------------------------------------------ *
 * The people behind a number
 * ------------------------------------------------------------------ */

.sheetWide { max-inline-size: 42rem; }

.whoList {
  display: grid;
  gap: 0.15rem;
  max-block-size: 26rem;
  overflow-y: auto;
}

.whoRow {
  align-items: center;
  border-radius: var(--round);
  color: inherit;
  display: grid;
  gap: var(--s3);
  grid-template-columns: 3px minmax(0, 1fr) auto;
  padding: var(--s2) var(--s3);
  text-decoration: none;
}

.whoRow:hover { background: var(--subtle); }

.whoFacts { align-items: center; display: flex; gap: var(--s3); }

/* ------------------------------------------------------------------ *
 * One person's readings, drawn
 * ------------------------------------------------------------------ */

.charts { display: grid; gap: var(--s4); max-block-size: 30rem; overflow-y: auto; }

.chart {
  border: 1px solid var(--line);
  border-radius: var(--round);
  display: grid;
  gap: var(--s2);
  margin: 0;
  padding: var(--s3);
}

/* The one the reader arrived for. */
.chartOpen { border-color: var(--gold); }

.chartHead {
  align-items: center;
  display: flex;
  flex-wrap: wrap;
  gap: var(--s3);
  justify-content: space-between;
}

.chartFacts { align-items: center; display: flex; gap: var(--s2); }

/* Matched to the 110-unit viewBox so nothing is scaled down: a dot drawn at
 * radius four should reach the reader as radius four, not as two and a half. */


/*
 * One bar per checkup.
 *
 * Drawn with the page's own spacing and colours rather than as a chart widget
 * with a second set of its own, so it reads as part of the card it sits in.
 * Heights are relative to the readings, never to zero, and `.chartFoot` says so
 * underneath: without that a reader takes a tall bar for a large number.
 */
.bars {
  align-items: end;
  display: grid;
  gap: var(--s2);
  grid-auto-columns: minmax(0, 1fr);
  grid-auto-flow: column;
}

.bar { display: grid; gap: 0.3rem; justify-items: center; }

.barValue {
  color: var(--muted);
  font-size: var(--t-micro);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* The reading somebody actually came to see. */
.barValueLast { color: var(--heading); font-weight: 700; }

.barTrack {
  align-items: end;
  background: var(--subtle);
  block-size: 4rem;
  border-radius: 0.3rem;
  display: flex;
  inline-size: 100%;
  overflow: hidden;
}

.barFill {
  border-radius: 0.3rem;
  display: block;
  inline-size: 100%;
  min-block-size: 0.35rem;
}

.barDate {
  color: var(--muted);
  font-size: var(--t-micro);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.chartFoot {
  color: var(--muted);
  font-size: var(--t-micro);
  margin: 0;
}



@media (max-width: 48rem) {
  .charts,
  .whoList { max-block-size: none; }
}

/* The whole set behind a job, for when five rows are not the five wanted. */
.jobAll { font-size: var(--t-label); margin: 0 0 var(--s3); }

/* ------------------------------------------------------------------ *
 * Something shown once
 * ------------------------------------------------------------------ */

.resultLines {
  background: var(--subtle);
  border-radius: var(--round);
  display: grid;
  gap: 0.2rem;
  justify-items: center;
  padding: var(--s4) var(--s3);
  text-align: center;
}

/* Large, spaced, and in tabular figures: this is read off a screen and typed
 * into a tablet across the room, so the digits have to be unmistakable and
 * evenly grouped. */
.resultBig {
  color: var(--heading);
  font-size: 2.6rem;
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  letter-spacing: 0.22em;
  line-height: 1.1;
  margin: 0;
  /* The tracking pushes the last digit off centre; this pulls it back. */
  text-indent: 0.22em;
}

.resultSmall { color: var(--muted); font-size: var(--t-label); margin: 0; }

/* ------------------------------------------------------------------ *
 * One person's conditions, as a list
 * ------------------------------------------------------------------ */

/*
 * A table here scrolled sideways inside its column and pushed the condition
 * name off the left edge. A list wraps instead, and the reading keeps its place
 * at the end of the row where the eye already goes for a number.
 */
.readings { display: grid; gap: 0.15rem; }

.reading {
  align-items: center;
  border-radius: var(--round);
  color: inherit;
  display: grid;
  gap: var(--s3);
  grid-template-columns: 3px minmax(0, 1fr) auto auto;
  padding: var(--s2) var(--s3);
  text-decoration: none;
}

.reading:hover { background: var(--subtle); }

.readingWhat { display: grid; gap: 0.1rem; min-inline-size: 0; }
.readingWhat b { color: var(--heading); font-weight: 600; }
.readingWhat span { color: var(--muted); font-size: var(--t-micro); }

.readingNow { display: grid; gap: 0.1rem; justify-items: end; text-align: end; }
.readingNow b {
  color: var(--heading);
  font-size: var(--t-lead);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.readingNow span { color: var(--muted); font-size: var(--t-micro); }

/* Quieter and smaller than the figure it belongs to: the unit is context, not
 * the measurement. */
.readingUnit {
  color: var(--muted);
  font-size: var(--t-micro);
  font-weight: 400;
  margin-inline-start: 0.2rem;
}

@media (max-width: 48rem) {
  .reading { grid-template-columns: 3px minmax(0, 1fr) auto; }
  .reading .badge { grid-column: 2 / -1; justify-self: start; }
}

/* How far along a recovery is. Quiet, because it is encouragement rather than
 * an alert: the person is still on the list, and moving. */
.readingProgress,
.chartProgress {
  color: var(--good);
  font-size: var(--t-micro);
  margin: 0;
}

/* Something worth looking at twice. Deliberately not red and deliberately not
 * called fraud: a reading moves a long way for honest reasons all the time, and
 * this only says the number is unlike this person's others. */
.readingLook,
.chartLook {
  color: var(--warn);
  display: block;
  font-size: var(--t-micro);
}

/* ------------------------------------------------------------------ *
 * The written report, as a sheet of paper
 * ------------------------------------------------------------------ */

.paper {
  background: var(--sheet);
  border: 1px solid var(--line);
  border-radius: var(--round-lg);
  box-shadow: var(--shadow);
  display: grid;
  gap: var(--s4);
  padding: var(--s5);
}

.paperHead { border-block-end: 2px solid var(--stage); padding-block-end: var(--s3); }

.paperWho {
  color: var(--gold-ink);
  font-size: var(--t-micro);
  font-weight: 600;
  letter-spacing: 0.14em;
  margin: 0 0 var(--s2);
  text-transform: uppercase;
}

/* The one line somebody reads if they read nothing else, so it gets the display
 * face and room to be a sentence rather than a label. */
.paperTitle {
  color: var(--heading);
  font-family: var(--face-display);
  font-size: 1.9rem;
  font-weight: 500;
  line-height: 1.2;
  margin: 0;
  max-inline-size: 28ch;
  text-wrap: balance;
}

.paperWhen { color: var(--muted); font-size: var(--t-label); margin: var(--s2) 0 0; }

/* The prose and the picture side by side: a reader who doubts a sentence should
 * be able to check it without scrolling. */
.paperTop {
  display: grid;
  gap: var(--s4);
  grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr);
}

.paperLead { font-size: var(--t-lead); line-height: 1.6; margin: 0; }

.paperFigure {
  background: var(--subtle);
  border-radius: var(--round);
  display: grid;
  gap: var(--s2);
  margin: 0;
  padding: var(--s3);
}

.paperFigure figcaption {
  color: var(--heading);
  font-size: var(--t-label);
  font-weight: 600;
}

.paperPart { display: grid; gap: var(--s3); }

.paperPart h3 {
  border-block-end: 1px solid var(--line);
  color: var(--heading);
  font-size: var(--t-lead);
  margin: 0;
  padding-block-end: var(--s2);
}

.paperCards { display: grid; gap: var(--s3); grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr)); }

.paperCard {
  border-inline-start: 3px solid var(--line-strong);
  display: grid;
  gap: 0.2rem;
  padding-inline-start: var(--s3);
}

.paperCard b { color: var(--heading); }
.paperCard p { color: var(--muted); font-size: var(--t-label); margin: 0; }

/* The model returns one of three tones. Anything else keeps the neutral rule,
 * so an unexpected word never leaves a card with no left border at all. */
.paperCard-baik { border-inline-start-color: var(--good); }
.paperCard-perhatian { border-inline-start-color: var(--warn); }
.paperCard-mendesak { border-inline-start-color: var(--bad); }

.paperList { display: grid; gap: var(--s3); margin: 0; padding-inline-start: 1.2rem; }
.paperList b { color: var(--heading); display: block; }
.paperList span { display: block; }
.paperList i { color: var(--muted); font-size: var(--t-micro); font-style: normal; }

.paperFoot {
  background: var(--warn-face);
  border-radius: var(--round);
  display: grid;
  gap: 0.2rem;
  padding: var(--s3);
}

.paperFoot b { color: var(--warn); }
.paperFoot p { margin: 0; }

.paperSmall { color: var(--muted); font-size: var(--t-micro); margin: 0; }

@media (max-width: 60rem) {
  .paperTop { grid-template-columns: minmax(0, 1fr); }
  .paper { padding: var(--s4); }
  .paperTitle { font-size: 1.5rem; }
}

/* ------------------------------------------------------------------ *
 * Printing, which is how this is exported
 * ------------------------------------------------------------------ *
 *
 * A browser prints this layout to PDF better than a server could rebuild it in
 * a drawing library, and it stays in step with the page automatically. What
 * print has to remove is everything that is a control rather than content.
 */
@media print {
  .rail,
  .tabbar,
  .chips,
  .moreSheet,
  .pageHead,
  .counts { display: none !important; }

  .shell { display: block; }
  .lane { max-inline-size: none; padding: 0; }

  .paper {
    border: 0;
    box-shadow: none;
    padding: 0;
  }

  /* A heading and the section under it should not be split across a page. */
  .paperPart,
  .paperCard,
  .paperFoot { break-inside: avoid; }

  .paperHead { break-after: avoid; }

  /* Print drops backgrounds by default, which would erase the one chart on the
   * page. These ask for them back and degrade to outlines if the reader has
   * background printing switched off. */
  .meterPart,
  .dot {
    -webkit-print-color-adjust: exact;
    print-color-adjust: exact;
  }

  .meter { border: 1px solid var(--line-strong); }

  a[href]::after { content: ""; }
}

/* ------------------------------------------------------------------ *
 * The door
 * ------------------------------------------------------------------ *
 *
 * The only page in the console with no rail, because a rail is a list of
 * places somebody cannot go yet. What is left has to hold the page on its own,
 * so it sits on the dark ground the rail normally occupies and the card is the
 * one lit thing on it.
 */

.doorBody {
  background: var(--stage);
  min-block-size: 100svh;
}

.door {
  align-items: center;
  display: grid;
  min-block-size: 100svh;
  padding: var(--s4);
  place-items: center;
}

.doorCard {
  background: var(--sheet);
  border-radius: var(--round-lg);
  box-shadow: 0 2px 4px rgb(0 0 0 / 12%), 0 24px 60px rgb(0 0 0 / 24%);
  display: grid;
  gap: var(--s3);
  inline-size: min(100%, 24rem);
  padding: var(--s5) var(--s4) var(--s4);
}

.doorHead { display: grid; justify-items: center; text-align: center; }

.doorMark {
  block-size: 3.25rem;
  inline-size: 3.25rem;
  object-fit: contain;
}

.doorWho {
  color: var(--gold-ink);
  font-size: var(--t-micro);
  font-weight: 600;
  letter-spacing: 0.16em;
  margin: var(--s2) 0 0;
  text-transform: uppercase;
}

.doorTitle {
  color: var(--heading);
  font-family: var(--face-display);
  font-size: var(--t-hero);
  font-weight: 500;
  line-height: 1.1;
  margin: var(--s1) 0 0;
}

.doorField { display: grid; gap: var(--s1); }

.doorField label {
  color: var(--heading);
  font-size: var(--t-label);
  font-weight: 600;
}

.doorField input {
  background: var(--canvas);
  border: 1px solid var(--line-strong);
  border-radius: var(--round);
  color: var(--text);
  font: inherit;
  inline-size: 100%;
  padding: 0.7rem var(--s3);
}

.doorField input:focus-visible {
  border-color: var(--anchor);
  outline: 2px solid var(--gold);
  outline-offset: 1px;
}

/* One message for both failures, so the page never confirms that an address
 * exists here to somebody who is guessing at them. */
.doorWrong {
  background: var(--bad-face);
  border-radius: var(--round);
  color: var(--bad);
  font-size: var(--t-label);
  margin: 0;
  padding: var(--s2) var(--s3);
}

.doorGo {
  inline-size: 100%;
  justify-content: center;
  margin-block-start: var(--s1);
  padding-block: 0.7rem;
}

.doorFoot {
  border-block-start: 1px solid var(--line);
  color: var(--muted);
  font-size: var(--t-micro);
  margin: 0;
  padding-block-start: var(--s3);
  text-align: center;
}

/* Leaving is a form rather than a link, so the button has to be talked out of
 * looking like one. It keeps the rail link's own shape; only the browser's
 * default chrome is removed. */
.railOut,
.moreOut {
  background: none;
  border: 0;
  color: inherit;
  cursor: pointer;
  font: inherit;
  inline-size: 100%;
  text-align: start;
}

.moreOut {
  align-items: center;
  display: flex;
  gap: var(--s3);
  padding: var(--s3) var(--s4);
}

/* ------------------------------------------------------------------ *
 * Something is happening
 * ------------------------------------------------------------------ *
 *
 * Deliberately not a percentage. Nothing on this side knows how far along a
 * model is, and a bar that filled up at a made-up rate would be a lie that
 * looks like information. This one only says that work is under way, which is
 * the actual question somebody has after pressing a button.
 */

.crawl {
  background: var(--line);
  block-size: 3px;
  border-radius: 999px;
  display: block;
  inline-size: min(100%, 11rem);
  margin-block-start: 0.4rem;
  overflow: hidden;
}

.crawl > span {
  animation: crawl 1.6s ease-in-out infinite;
  background: var(--gold);
  block-size: 100%;
  display: block;
  inline-size: 40%;
}

@keyframes crawl {
  0% { transform: translateX(-100%); }
  100% { transform: translateX(250%); }
}

/* Motion that repeats forever is the kind this setting exists for. The bar
 * stays, and says the same thing by sitting still and filled. */
@media (prefers-reduced-motion: reduce) {
  .crawl > span {
    animation: none;
    inline-size: 100%;
    opacity: 0.55;
  }
}

/* ------------------------------------------------------------------ *
 * The things you may do
 * ------------------------------------------------------------------ *
 *
 * One shape, wherever actions appear: at the end of a table row, in a panel
 * head, or beside the step somebody is stuck on. Six pages used to write this
 * out themselves, which is six places for the spacing to drift apart.
 */

.rowActions {
  display: inline-flex;
  flex-wrap: wrap;
  gap: var(--s2);
  justify-content: flex-end;
}

/* Everything that can be done for one person, gathered rather than scattered
 * through the page as links to hunt for. */
.jobs { display: flex; flex-wrap: wrap; gap: var(--s2); padding: var(--s3) var(--s4) var(--s4); }
.jobs .rowActions { justify-content: flex-start; }

/* The step in front of somebody carries the button that finishes it, so the
 * label takes the room that is left and the button keeps its own. */
.stepWhat { flex: 1 1 auto; min-inline-size: 0; }
.stepGo { flex: 0 0 auto; }

/* A filter row that also carries an action. On a phone the action drops under
 * the chips rather than squeezing them. */
.toolbarRow {
  align-items: center;
  display: flex;
  flex-wrap: wrap;
  gap: var(--s3);
  justify-content: space-between;
}

@media (max-width: 48rem) {
  /* Inside a row-as-card the actions belong under the values, full width, not
   * crushed against the right edge of a label. */
  .table td .rowActions { justify-content: flex-start; inline-size: 100%; }
}

/* A date is one thing. Broken over three lines in a narrow column it reads as
 * three, and the eye has to reassemble it on every row. */
.oneLine { white-space: nowrap; }

/* ------------------------------------------------------------------ *
 * The jobs somebody came here to do
 * ------------------------------------------------------------------ *
 *
 * On Beranda rather than in the rail, because the rail is the part of the
 * frame a phone folds away, and these are what somebody reaches for while
 * standing next to a tablet. Big enough to hit with a thumb, and wrapping
 * rather than scrolling, so none of them hides off the edge.
 */

.quick { padding: 0 var(--s4) var(--s4); }

.quick .rowActions {
  gap: var(--s2);
  inline-size: 100%;
  justify-content: flex-start;
}

.quick .button {
  padding-block: 0.6rem;
  padding-inline: var(--s4);
}

@media (max-width: 40rem) {
  /* One per line on a phone. Two half-width buttons side by side is where a
   * thumb lands between them. */
  .quick .rowActions { flex-direction: column; align-items: stretch; }
  .quick .button { justify-content: center; }
}

/* A mark inside a button sits with the word rather than beside it, and never
 * grows with the label. Used only on the jobs somebody scans a page for; a
 * table full of them is harder to read, not easier. */
.button .icon {
  block-size: 1.05rem;
  flex: 0 0 auto;
  inline-size: 1.05rem;
  opacity: 0.85;
}

.buttonLead .icon { opacity: 1; }

/* Keyboard focus has to be visible on every control, not only the ones that
 * happen to be inputs. */
.button:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
  .button { transition: none; }
}

/* ------------------------------------------------------------------ *
 * A secondary action still has to look like one
 * ------------------------------------------------------------------ *
 *
 * `.buttonQuiet` drops its edge on purpose: three bordered pills at the end of
 * every row in a table of ninety is noise, and the words are enough there
 * because the row around them is the context.
 *
 * Standing alone they are not. In the panel of jobs on Beranda and beside a
 * person's name, these are the controls somebody came to press, and with no
 * edge and grey text they read as a sentence rather than as something to press.
 * Same component, different surroundings, different weight.
 */

.quick .buttonQuiet,
.jobs .buttonQuiet {
  border-color: var(--line-strong);
  color: var(--anchor);
}

.quick .buttonQuiet:hover,
.jobs .buttonQuiet:hover {
  background: var(--good-face);
  border-color: var(--anchor);
}

/* ------------------------------------------------------------------ *
 * Saying there is more to the right
 * ------------------------------------------------------------------ *
 *
 * A wide table scrolls inside its own box rather than dragging the page
 * sideways, which is right, and gives no sign that it does, which is not: a
 * column ends mid-word at the edge and reads as broken rather than as
 * continued.
 *
 * Four layers, no JavaScript. Two of them are painted on the box and stay put;
 * two scroll with the content and cover the first pair when there is nothing
 * more that way. So the shade appears exactly when there is something behind
 * it, and goes when there is not.
 */

.tableWrap {
  background-image:
    linear-gradient(to right, var(--sheet) 30%, rgb(255 254 250 / 0%)),
    linear-gradient(to left, var(--sheet) 30%, rgb(255 254 250 / 0%)),
    linear-gradient(to right, rgb(16 45 37 / 10%), rgb(16 45 37 / 0%)),
    linear-gradient(to left, rgb(16 45 37 / 10%), rgb(16 45 37 / 0%));
  background-attachment: local, local, scroll, scroll;
  background-position: 0 0, 100% 0, 0 0, 100% 0;
  background-repeat: no-repeat;
  background-size: 2.5rem 100%, 2.5rem 100%, 0.9rem 100%, 0.9rem 100%;
}

/* Inside a card on a phone the table is not a table any more, so the shade
 * would be a smudge down the side of a list. */
@media (max-width: 48rem) {
  .tableWrap { background-image: none; }
}
