/* Premo ERP — the Loom interface.
 *
 * Built for two rooms that do not agree. The weaving shed reads a screen at three metres
 * with cotton dust in the air; the accounts desk reads the same register at arm's length
 * under an office light. So the look is one structure with two grounds — daylight and
 * dark — and a marking colour the person chooses, held in the browser rather than the
 * database because a terminal belongs to a room, not to a login.
 *
 * Two rules decide every colour below.
 *
 *   1. The status set never moves. Pending is ochre, posted is green, cancelled is red,
 *      on every ground and under every marking. A document's state is not decoration and
 *      must not change meaning because somebody liked a different blue.
 *   2. The marking may not live in a hue the status set owns. That is why the six accents
 *      are violets, blues and neutrals, and why there is no green or orange one.
 *
 * Structure — mono for anything the mill counts, sans for anything it reads; square
 * corners; 2px rules around blocks of figures; density over decoration.
 */

/* ========================================================== TOKENS: STRUCTURE */
:root {
  --mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
  --sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;

  /* The shell's height is a token because the weaving grid's sticky header has to know
     where the furniture ends. Getting this wrong hides the first loom behind the nav. */
  --band-h: 2.6rem;
  --modes-h: 2.2rem;
  --shell-h: calc(var(--band-h) + var(--modes-h));

  --pad: 1rem;
  --radius: 2px;
}

/* ========================================================== TOKENS: GROUNDS */

/* Daylight — the office desk, and the shed in the morning. */
html, html[data-ground="daylight"] {
  --bg:      #ffffff;
  --surface: #ffffff;
  --row:     #f4f5f7;
  --sunk:    #f7f8fa;
  --ink:     #101420;
  --muted:   #5b6272;
  --line:    #d6d9e0;
  --strong:  #101420;
  --shadow:  rgba(16, 20, 32, .10);

  --pending: #8a5400;
  --posted:  #0d6f5c;
  --void:    #b3341f;
  --draft:   #6b7280;

  --pending-bg: #fdf3e0;
  --posted-bg:  #e2f1eb;
  --void-bg:    #fae9e5;
  --draft-bg:   #eceef2;

  --mark:     var(--a-l);
  --mark-ink: var(--a-li);
  --signal:   var(--a-ls);
  --row-tint: var(--row);
  --cursor:   #eef0f4;
}

/* Dark — the terminal bolted to a pillar in the shed. This is the original Loom. */
html[data-ground="dark"] {
  --bg:      #0c0f13;
  --surface: #131820;
  --row:     #10151c;
  --sunk:    #0a0d12;
  --ink:     #e8edf4;
  --muted:   #8496a9;
  --line:    #242e3a;
  --strong:  #3b4756;
  --shadow:  rgba(0, 0, 0, .55);

  --pending: #f0a92c;
  --posted:  #35c2a5;
  --void:    #e0604b;
  --draft:   #8496a9;

  --pending-bg: #34260c;
  --posted-bg:  #0d2a25;
  --void-bg:    #351511;
  --draft-bg:   #1a212a;

  --mark:     var(--a-d);
  --mark-ink: var(--a-di);
  --signal:   var(--a-ds);
  --row-tint: var(--row);
  --cursor:   #1c2734;
}

/* ========================================================== TOKENS: ACCENTS
 * Each accent carries six values, not one: a fill and its text colour and a text form of
 * its own, per ground. A single hex cannot do this — #201B51 is handsome on white and
 * invisible on #0c0f13, and amber is the reverse. */

/* Vat — indigo taken down into the dye vat. rgb(32, 27, 81). */
html, html[data-accent="vat"] {
  --a-d: #8f86e8; --a-di: #12102b; --a-ds: #a79ff0;
  --a-l: #201b51; --a-li: #ffffff; --a-ls: #201b51;
}
/* Indigo — the dye as it comes out on the cloth. */
html[data-accent="indigo"] {
  --a-d: #7f9cff; --a-di: #0c1330; --a-ds: #93aaff;
  --a-l: #2f3f77; --a-li: #ffffff; --a-ls: #2f3f77;
}
/* Steel — the finishing range's stainless. */
html[data-accent="steel"] {
  --a-d: #6fb3cc; --a-di: #06171f; --a-ds: #86c3d9;
  --a-l: #33566a; --a-li: #ffffff; --a-ls: #2f5063;
}
/* Amber — shed signage, black on yellow. Its text form has to darken right down on a
   white ground: #f0a92c on white is about 1.8:1, which is not a colour you can read. */
html[data-accent="amber"] {
  --a-d: #f0a92c; --a-di: #0c0f13; --a-ds: #f0a92c;
  --a-l: #f0a92c; --a-li: #0f1216; --a-ls: #8a5400;
}
/* Greige — cloth before any dye touches it. No accent at all: the marking is ink, so
   every colour left on the screen is a status and nothing else. */
html[data-accent="greige"] {
  --a-d: #cfc6ac; --a-di: #16140e; --a-ds: #d8d0ba;
  --a-l: #1c1a14; --a-li: #faf9f6; --a-ls: #4a453a;
}
/* Selvedge — the red line down the edge of the cloth. Kept because people ask for it;
   it is the one accent that sits in a hue the status set owns, so cancelled documents
   and the mill's own marking argue with each other. Choose it knowing that. */
html[data-accent="selvedge"] {
  --a-d: #ff7a63; --a-di: #2a0a06; --a-ds: #ff9481;
  --a-l: #b3241a; --a-li: #ffffff; --a-ls: #9d2018;
}

/* The zebra row and the cursor row take a few degrees of the marking, so a colourway
   reads as one thing rather than a grey table with a coloured hat. Behind @supports
   because a custom property holding an unparsed color-mix() fails at use, and a table
   with no row colour at all is worse than a neutral one. */
@supports (color: color-mix(in srgb, red 50%, blue)) {
  html, html[data-ground="daylight"] {
    --row-tint: color-mix(in srgb, var(--mark) 4%, var(--surface));
    --cursor:   color-mix(in srgb, var(--mark) 12%, var(--surface));
  }
  html[data-ground="dark"] {
    --row-tint: color-mix(in srgb, var(--mark) 5%, var(--surface));
    --cursor:   color-mix(in srgb, var(--mark) 18%, var(--surface));
  }
}

/* ========================================================== BASE */
* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font: 13px/1.5 var(--sans);
  /* The key bar is fixed to the bottom; without this the last row of every register
     sits underneath it and looks like the table was cut off. */
  padding-bottom: 2.6rem;
  -webkit-font-smoothing: antialiased;
}

a { color: var(--signal); }
a:focus-visible, button:focus-visible, summary:focus-visible,
input:focus-visible, select:focus-visible, textarea:focus-visible {
  outline: 2px solid var(--mark);
  outline-offset: 1px;
}

/* Anything the mill counts is set in mono and lines up by digit. A column of weights
   that does not align is a column nobody checks. */
.num, td.num, th.num, .tile-value, .big,
table.grid td, .doc-no, .keybar kbd, kbd {
  font-variant-numeric: tabular-nums;
}
td.num, th.num { text-align: right; font-family: var(--mono); }

/* ========================================================== SHELL: TOP BAND */
.topbar {
  position: sticky;
  top: 0;
  z-index: 40;
  height: var(--band-h);
  display: flex;
  align-items: center;
  gap: 1.1rem;
  padding: 0 var(--pad);
  background: var(--surface);
  border-bottom: 3px solid var(--mark);
  font-family: var(--mono);
  font-size: .74rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--muted);
}
.brand {
  font-weight: 700;
  letter-spacing: .28em;
  color: var(--ink);
  text-decoration: none;
}
/* ---------------------------------------------------- the workspace switch
 * The company name is also the door between the three applications, so it reads as one
 * thing — the mill above, where you are below — and opens a panel wide enough to say what
 * each application is for. */
.topbar .ws > summary {
  display: flex; flex-direction: column; align-items: flex-start; line-height: 1.25;
  padding: .15rem .5rem .15rem 0;
  color: var(--muted);
}
.topbar .ws > summary::after { position: absolute; right: -.2rem; top: .55rem; }
.topbar .ws > summary:hover .ws-now { color: var(--signal); }
.topbar .ws .ws-mill i { font-style: normal; color: var(--ink); font-weight: 600; }
.topbar .ws .ws-now {
  color: var(--mark); font-weight: 700; font-size: .68rem; letter-spacing: .16em;
}
.topbar .ws > .menu-list { min-width: 22rem; padding: .3rem 0; }
.topbar .ws > .menu-list a,
.topbar .ws > .menu-list .ws-later {
  display: flex; flex-direction: column; gap: .1rem;
  padding: .5rem .9rem;
  white-space: normal;
}
.topbar .ws > .menu-list strong {
  font-family: var(--mono); font-size: .74rem; letter-spacing: .14em;
  text-transform: uppercase; color: var(--ink);
}
.topbar .ws > .menu-list span {
  font-family: var(--sans); font-size: .78rem; letter-spacing: 0;
  text-transform: none; color: var(--muted);
}
.topbar .ws > .menu-list a.on strong { color: var(--mark); }
.topbar .ws > .menu-list a.on::before {
  content: "▸"; position: absolute; margin-left: -.65rem; color: var(--mark);
}
/* Named before it exists, and plainly not a door that opens. */
.topbar .ws > .menu-list .ws-later { cursor: default; opacity: .55; }
.topbar .ws > .menu-list .ws-later strong { color: var(--muted); }

.topbar .ctx { display: flex; gap: 1.1rem; }
.topbar .ctx i { font-style: normal; color: var(--ink); font-weight: 600; }
.topbar .who {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: .7rem;
  color: var(--muted);
}
.topbar .who .user { color: var(--ink); font-weight: 600; }

.who .signout { display: inline; }
.who .signout button {
  background: none; border: 0; padding: 0; font: inherit; cursor: pointer;
  color: var(--muted); text-transform: uppercase; letter-spacing: .1em;
}
.who .signout button:hover { color: var(--ink); }

/* ========================================================== SHELL: THEME PICKER
 * Sits beside the name because that is where a person looks for "things about me". */
.theme { position: relative; }
.theme-btn {
  display: flex; align-items: center; gap: .4rem;
  background: none; border: 1px solid var(--line); border-radius: var(--radius);
  padding: .15rem .45rem; cursor: pointer;
  font: inherit; letter-spacing: .1em; text-transform: uppercase; color: var(--muted);
}
.theme-btn:hover { color: var(--ink); border-color: var(--strong); }
.theme-btn .swatch {
  width: .7rem; height: .7rem; background: var(--mark);
  border: 1px solid var(--line); display: block;
}
.theme-panel {
  position: absolute; top: calc(100% + .5rem); right: 0; z-index: 60;
  min-width: 15rem;
  background: var(--surface);
  border: 1px solid var(--strong);
  box-shadow: 0 10px 30px var(--shadow);
  padding: .7rem;
  display: flex; flex-direction: column; gap: .6rem;
  text-transform: none; letter-spacing: 0;
}
.theme-panel[hidden] { display: none; }
.theme-panel h3 {
  margin: 0; font-size: .6rem; letter-spacing: .16em; text-transform: uppercase;
  color: var(--muted); font-weight: 600;
}
.ground-toggle { display: grid; grid-template-columns: 1fr 1fr; gap: .3rem; }
.ground-toggle button,
.accent-grid button {
  font: inherit; cursor: pointer; background: var(--sunk); color: var(--ink);
  border: 1px solid var(--line); border-radius: var(--radius); padding: .35rem .4rem;
  font-size: .72rem;
}
.ground-toggle button[aria-pressed="true"],
.accent-grid button[aria-pressed="true"] {
  border-color: var(--mark);
  box-shadow: inset 0 0 0 1px var(--mark);
  font-weight: 700;
}
.accent-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: .3rem; }
.accent-grid button { display: flex; align-items: center; gap: .35rem; }
.accent-grid .swatch {
  width: .8rem; height: .8rem; border: 1px solid var(--line); flex: none;
}

/* ========================================================== SHELL: MODE STRIP */
.modes {
  position: sticky;
  top: var(--band-h);
  z-index: 39;
  display: flex;
  flex-wrap: wrap;
  background: var(--sunk);
  border-bottom: 1px solid var(--line);
  min-height: var(--modes-h);
}
.modes > a, .modes .menu > summary {
  display: flex; align-items: center;
  padding: .35rem .9rem;
  font-family: var(--mono);
  font-size: .72rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--muted);
  text-decoration: none;
  border-right: 1px solid var(--line);
  cursor: pointer;
  white-space: nowrap;
}
.modes > a:hover, .modes .menu > summary:hover { color: var(--ink); background: var(--row-tint); }
.modes > a.on, .modes .menu.on > summary {
  background: var(--mark);
  color: var(--mark-ink);
  font-weight: 700;
}
/* A dropdown belongs to `.menu`, not to the strip it happens to sit in.
 *
 * Every one of these rules used to be written `.modes .menu ...`, so the mechanics — the
 * panel lifting out of the flow, the marker, the frame — existed only inside the mode
 * strip. The workspace switch was then put in the top bar and inherited none of it: the
 * panel laid itself out inline and its three entries ran across the header as one line of
 * text, over the date and the user's name. The behaviour is the component's; where it is
 * placed is the caller's business. */
.menu { position: relative; }
.menu > summary { list-style: none; cursor: pointer; }
.menu > summary::-webkit-details-marker { display: none; }
.menu > summary::after { content: " ▾"; margin-left: .35rem; font-size: .8em; }
.menu > .menu-list {
  position: absolute; top: 100%; left: 0; z-index: 50;
  display: flex; flex-direction: column; min-width: 14rem;
  background: var(--surface);
  border: 1px solid var(--strong);
  box-shadow: 0 10px 30px var(--shadow);
  padding: .25rem 0;
}
.menu > .menu-list a {
  padding: .4rem .9rem;
  color: var(--ink);
  text-decoration: none;
  font-size: .8rem;
  font-family: var(--sans);
  letter-spacing: 0;
  text-transform: none;
  white-space: nowrap;
}
.menu > .menu-list a:hover { background: var(--row-tint); color: var(--signal); }

/* ========================================================== MAIN + PAGE HEAD
 * Full width. A nine-column register read on a mill office monitor should not be boxed
 * into the middle third of it — that was the old 1200px column. Prose is still capped,
 * because a line of explanation 200 characters wide is not read. */
main {
  padding: 1.3rem 1.3rem 2rem;
  display: flex;
  flex-direction: column;
  gap: 0;
}

/* A register is scanned and a document is read, and they do not want the same width.
   The register keeps the whole monitor — nine columns of receipts boxed into the middle
   third was the old 1200px column, and the reason this never looked like an ERP. A
   document and an entry form get a bounded column instead: at 1600px an unbounded
   key/value table puts its label three hundred pixels from its value, which is not
   symmetry, it is two tables that happen to share a row. */
main > .card, main > form.card { max-width: 82rem; }

/* Every screen opens the same way: title, one line of explanation, then the actions —
   set once here so all 64 screens share a rhythm without each one being edited. */
main > h1 {
  font-family: var(--mono);
  font-size: 1.1rem;
  font-weight: 700;
  letter-spacing: .16em;
  text-transform: uppercase;
  margin: 0 0 .3rem;
  display: flex; align-items: center; gap: .6rem; flex-wrap: wrap;
}
main > p.sub, .sub {
  color: var(--muted);
  font-size: .84rem;
  line-height: 1.55;
  max-width: 88ch;
  margin: 0 0 1rem;
}
h2 {
  font-family: var(--mono);
  font-size: .78rem;
  font-weight: 700;
  letter-spacing: .14em;
  text-transform: uppercase;
  margin: 1.5rem 0 .5rem;
}

/* ========================================================== CARDS */
.card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  margin-bottom: 1rem;
}
.card > h2 {
  margin: 0;
  padding: .5rem .8rem;
  border-bottom: 1px solid var(--line);
  background: var(--sunk);
  font-size: .7rem;
}
.card-body { padding: .8rem; }

/* ========================================================== TABLES */
.wrap {
  overflow-x: auto;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  margin-bottom: 1rem;
}
/* Inside a card the card is already the frame. Both drawing one left a hairline box with
   a strip of card showing underneath it, which read as an empty last row. */
.card .wrap { border: 0; border-radius: 0; margin-bottom: 0; }
table { width: 100%; border-collapse: collapse; background: var(--surface); }
th, td {
  text-align: left;
  padding: .34rem .7rem;
  border-bottom: 1px solid var(--line);
  vertical-align: top;
}
thead th {
  font-family: var(--mono);
  font-size: .64rem;
  text-transform: uppercase;
  letter-spacing: .12em;
  color: var(--muted);
  font-weight: 600;
  background: var(--sunk);
  border-bottom: 2px solid var(--strong);
  position: relative;
  vertical-align: bottom;
}
/* Striping belongs to a register — a long list read across, where losing your row costs
   you the line. A key/value table is read down, and banding it just adds noise, so the
   zebra is scoped to the tables that sit directly on the page rather than inside a card. */
main > .wrap table tbody tr:nth-child(even) { background: var(--row-tint); }
tbody tr:hover { background: var(--cursor); }

/* ---------- a register you can type into
   The client list turns editable for a calling round. Inputs in a table are ordinarily a
   mess of borders — a box inside a cell inside a row, three lines deep — so these are drawn
   as the cell itself and only show their edges under the cursor. The row you are working on
   stays lit, because the one thing that goes wrong here is typing into the wrong line. */
table.editing td { padding: .12rem .3rem; }
table.editing input, table.editing select, table.editing textarea {
  width: 100%; min-width: 5.5rem; box-sizing: border-box;
  padding: .22rem .35rem;
  font: inherit; color: var(--ink);
  background: transparent; border: 1px solid transparent; border-radius: var(--radius);
}
table.editing textarea { resize: vertical; min-width: 12rem; line-height: 1.35; }
/* A figure column keeps its right edge. Stretched to the cell, a rating select put its "3"
   at the far left of a heading aligned to the far right. */
table.editing td.num select { width: auto; min-width: 4rem; }
table.editing input[type="date"] { min-width: 8.5rem; }
table.editing tr:hover input,
table.editing tr:hover select,
table.editing tr:hover textarea { border-color: var(--line); background: var(--surface); }
table.editing input:focus, table.editing select:focus, table.editing textarea:focus {
  outline: none; border-color: var(--mark); background: var(--surface);
  box-shadow: 0 0 0 2px var(--cursor);
}
table.editing tr:focus-within { background: var(--cursor); }
/* Thirty clients is more than one screen, and a Save at the bottom of it is a Save nobody
   is sure is still there. Held clear of the key bar, which is fixed to the same edge — at
   `bottom: 0` the button sat underneath it and the last row of the list could not be saved
   without scrolling past the thing that was covering the button. */
.sticky-save {
  position: sticky; bottom: 2.6rem; z-index: 2;
  padding: .6rem .7rem; margin-bottom: 0;
  background: var(--sunk); border: 1px solid var(--line); border-radius: var(--radius);
}
.filters label.strong { color: var(--mark); font-weight: 700; }
td .note { color: var(--muted); }

/* Figures are grouped into equal columns on the right rather than being spread across
   whatever room is left. A four-column register on a wide monitor otherwise puts its
   three numbers a hand's width apart, which is the shape that reads as "web page". */
th.num, td.num { width: 9rem; }

/* A key/value table — the shape every document detail screen uses. The labels take a
   fixed column so that a row of three pairs lines its values up with the row of two
   above it. They used to hug their text, which meant every row set its own column
   positions and no two rows agreed. */
tbody th {
  width: 11.5rem;
  font-family: var(--mono);
  font-size: .66rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  font-weight: 600;
  color: var(--muted);
  vertical-align: top;
}
table.kv th { width: 11.5rem; }
/* A running account needs its subtotals to read as subtotals — the rows above are terms,
   these are what the terms come to. */
table.kv tr.total th, table.kv tr.total td {
  border-top: 2px solid var(--strong);
  font-weight: 700;
  color: var(--ink);
}
table.rows td { padding: .25rem .35rem; }
table.rows input, table.rows select { width: 100%; min-width: 7rem; }

.empty {
  padding: 1.6rem;
  text-align: center;
  color: var(--muted);
  font-size: .85rem;
}
tr.warn, tr.bad { background: var(--pending-bg) !important; }
tr.bad { background: var(--void-bg) !important; }

/* The row the keyboard is on. Registers are driven with the arrow keys, and a cursor you
   cannot see is a cursor that makes people reach for the mouse. */
tbody tr.cursor {
  background: var(--cursor) !important;
  box-shadow: inset 3px 0 0 var(--mark);
}

/* ========================================================== STATUS */
.pill {
  display: inline-block;
  padding: .08rem .45rem;
  border-radius: var(--radius);
  font-family: var(--mono);
  font-size: .64rem;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  white-space: nowrap;
}
.pill.draft     { background: var(--draft-bg);   color: var(--draft); }
.pill.pending   { background: var(--pending-bg); color: var(--pending); }
.pill.submitted { background: var(--posted-bg);  color: var(--posted); }
.pill.cancelled { background: var(--void-bg);    color: var(--void); }
.pill.ok, .pill.good { background: var(--posted-bg); color: var(--posted); }
.pill.bad            { background: var(--void-bg);   color: var(--void); }
.pill.sev-high   { background: var(--void-bg);    color: var(--void); }
.pill.sev-medium { background: var(--pending-bg); color: var(--pending); }
.pill.sev-low    { background: var(--draft-bg);   color: var(--draft); }
/* Where a figure came from, said on the heading rather than left to be inferred. */
.pill.entered    { background: var(--draft-bg); color: var(--muted); }
.pill.calculated { background: var(--posted-bg); color: var(--posted); }

/* ========================================================== BUTTONS */
.btn {
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  background: var(--mark);
  color: var(--mark-ink);
  border: 1px solid var(--mark);
  padding: .35rem .85rem;
  border-radius: var(--radius);
  text-decoration: none;
  font-family: var(--mono);
  font-size: .72rem;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  cursor: pointer;
  white-space: nowrap;
}
.btn:hover { filter: brightness(1.08); }
.btn.secondary {
  background: transparent;
  color: var(--signal);
  border-color: var(--line);
}
.btn.secondary:hover { border-color: var(--mark); filter: none; }
.btn.danger { background: var(--void); border-color: var(--void); color: #fff; }
.btn.add-row { font-size: .68rem; padding: .25rem .6rem; }

/* The action bar. One place, one alignment, on every screen that has one. */
.actions {
  display: flex;
  gap: .5rem;
  align-items: center;
  flex-wrap: wrap;
  margin: 0 0 1rem;
}
.actions label.inline {
  display: flex; align-items: center; gap: .4rem;
  font-family: var(--mono); font-size: .66rem;
  letter-spacing: .1em; text-transform: uppercase; color: var(--muted);
}

/* ========================================================== FORMS
 * The symmetry fix. These were flex rows with `flex: 1`, so a last row holding two
 * fields stretched them across the width of a row that held three — every form in the
 * mill had ragged, unaligned columns. A grid with a fixed track keeps column two under
 * column two whatever the field count is. */
form .row {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr));
  gap: .3rem .9rem;
  align-items: start;
}
form .field {
  display: flex;
  flex-direction: column;
  margin-bottom: .7rem;
  min-width: 0;
}
form label {
  font-family: var(--mono);
  font-size: .62rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: .25rem;
}
form input, form select, form textarea {
  padding: .4rem .5rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  font: inherit;
  background: var(--surface);
  color: var(--ink);
  min-width: 0;
}
form input:focus, form select:focus, form textarea:focus {
  border-color: var(--mark);
  background: var(--cursor);
}
form input[type="number"], form input[type="date"] {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
}
form .errorlist {
  color: var(--void);
  font-size: .76rem;
  margin: .2rem 0 0;
  padding-left: 1rem;
  list-style: none;
}
/* The required mark. `abbr` rather than a bare asterisk so a screen reader says
   "Required" instead of "star". */
form label .req {
  color: var(--void); margin-left: .2rem; font-weight: 700;
  text-decoration: none; border: 0; cursor: help;
}
/* Said out loud, because a star on some fields and nothing on the others reads as "the
   rest have not been decided yet". */
form label .opt {
  margin-left: .35rem; font-size: .9em; font-weight: 400; letter-spacing: .04em;
  color: var(--muted); opacity: .65; text-transform: lowercase;
}
.help {
  font-family: var(--sans);
  font-size: .76rem;
  color: var(--muted);
  margin-top: .2rem;
  letter-spacing: 0;
  text-transform: none;
}
.field.bad input, .field.bad select, .field.bad textarea { border-color: var(--void); }

/* A required list with nothing in it — marked rather than left as an empty select the
   person clicks at, because "which screen fills this?" is not something to have to guess. */
form .field.unfillable select { border-color: var(--pending); background: var(--pending-bg); }
.hint {
  font-size: .76rem;
  color: var(--pending);
  margin-top: .3rem;
  background: var(--pending-bg);
  border-radius: var(--radius);
  padding: .35rem .5rem;
  line-height: 1.4;
  letter-spacing: 0;
  text-transform: none;
  font-family: var(--sans);
}
.hint a { color: var(--pending); font-weight: 700; white-space: nowrap; }
/* A hint that answers a question people kept asking is not a footnote. Bigger, and marked
   with an accent edge so the eye lands on it before the form below — this is the sentence
   that says which client field the screen in front of you actually changes. */
.hint.callout {
  font-size: .9rem;
  border-left: 3px solid var(--mark);
  padding: .6rem .8rem;
  margin: .6rem 0 1rem;
}
.hint.callout strong { color: var(--ink); }

/* The sort's own wastage stages open on the tick. Shown and hidden by the `hidden`
   attribute, not by a rule in here, because a stylesheet the browser has cached is
   indistinguishable from one that does not work. */
.wastage-stages[hidden] { display: none; }

/* ========================================================== FLASHES */
.flash {
  padding: .5rem .8rem;
  border-radius: var(--radius);
  margin-bottom: .8rem;
  font-size: .85rem;
  border-left: 3px solid currentColor;
}
.flash.success { background: var(--posted-bg);  color: var(--posted); }
.flash.error   { background: var(--void-bg);    color: var(--void); }
.flash.warning { background: var(--pending-bg); color: var(--pending); }
p.warn {
  color: var(--pending);
  background: var(--pending-bg);
  padding: .45rem .7rem;
  border-left: 3px solid currentColor;
  font-size: .84rem;
  margin: 0 0 1rem;
}

/* ========================================================== SUBNAV / FILTERS / CHIPS */
.subnav {
  display: flex;
  flex-wrap: wrap;
  gap: .25rem;
  margin: 0 0 1rem;
  padding: .3rem;
  background: var(--sunk);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}
.subnav a {
  padding: .3rem .7rem;
  border-radius: var(--radius);
  font-family: var(--mono);
  font-size: .68rem;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--muted);
  text-decoration: none;
  white-space: nowrap;
}
.subnav a:hover { background: var(--cursor); color: var(--ink); }
.subnav a.on { background: var(--mark); color: var(--mark-ink); font-weight: 700; }
/* The grid is an action, not one more tab, so it is marked differently. It used to be
   pushed right with `margin-left: auto`, which on a row of ten tabs left it stranded
   alone on a second line looking like something had broken. */
.subnav a.grid-link {
  color: var(--posted);
  border: 1px solid var(--posted);
  font-weight: 700;
}
.subnav a.grid-link:hover { background: var(--posted-bg); }

.filters {
  display: flex;
  gap: .8rem;
  align-items: flex-end;
  flex-wrap: wrap;
  margin-bottom: 1rem;
  padding: .6rem .7rem;
  background: var(--sunk);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}
.filters label {
  display: flex; flex-direction: column; gap: .25rem;
  font-family: var(--mono); font-size: .62rem;
  letter-spacing: .12em; text-transform: uppercase; color: var(--muted);
}
.filters input, .filters select {
  padding: .3rem .45rem; border: 1px solid var(--line);
  border-radius: var(--radius); font: inherit;
  background: var(--surface); color: var(--ink);
}

.chips { display: flex; flex-wrap: wrap; gap: .35rem; margin-bottom: 1rem; }
.chip {
  display: inline-block;
  padding: .25rem .65rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  font-size: .78rem;
  text-decoration: none;
  color: var(--ink);
  background: var(--surface);
  white-space: nowrap;
}
.chip:hover { border-color: var(--mark); }
.chip.on { background: var(--mark); border-color: var(--mark); color: var(--mark-ink); font-weight: 600; }
.chip em { font-style: normal; opacity: .6; margin-left: .3rem; }
.chip.on em { opacity: .85; }

/* ========================================================== TILES / CARDS GRID */
.tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
  gap: .6rem;
  margin-bottom: 1.2rem;
}
.tile {
  display: flex;
  flex-direction: column;
  gap: .2rem;
  padding: .7rem .8rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  text-decoration: none;
  color: inherit;
  /* Equal height whatever the note says — a row of tiles with ragged bottoms is the
     first thing the eye finds on a dashboard, and it is never the useful thing. */
  min-height: 5.4rem;
}
.tile:hover { border-color: var(--mark); box-shadow: 0 2px 10px var(--shadow); }
.tile-label {
  font-family: var(--mono); font-size: .62rem; color: var(--muted);
  text-transform: uppercase; letter-spacing: .14em;
}
.tile-value {
  font-family: var(--mono); font-size: 1.5rem; font-weight: 700; line-height: 1.15;
}
.tile-value small { font-size: .75rem; font-weight: 400; color: var(--muted); }
.tile-note { font-size: .72rem; color: var(--muted); line-height: 1.35; margin-top: auto; }
/* Warn, not error: defect cloth and written-off yarn are facts to look at, not faults. */
.tile.warn { border-color: var(--pending); }
.tile.warn .tile-value { color: var(--pending); }

.cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(19rem, 1fr));
  gap: .7rem;
}
.linkcard {
  display: flex; flex-direction: column; gap: .3rem;
  text-decoration: none; color: inherit; padding: .9rem 1rem;
  min-height: 6rem;
}
.linkcard:hover { border-color: var(--mark); box-shadow: 0 2px 10px var(--shadow); }
/* The card's own padding already sets this heading in; giving it the card-header strip as
   well drew a bar floating inside the box with a margin all round it. */
.linkcard > h2 {
  margin: 0; padding: 0; font-size: .8rem; color: var(--signal);
  background: none; border-bottom: 0;
}
.linkcard p { margin: 0; font-size: .8rem; color: var(--muted); line-height: 1.45; }

.big {
  font-family: var(--mono);
  font-size: 1.9rem;
  font-weight: 700;
  margin: .3rem 0 1rem;
}
.big .help { font-size: .82rem; font-weight: 400; }
.stage-bar { height: 5px; background: var(--mark); border-radius: var(--radius); }

/* ========================================================== THE WEAVING GRID
 * A keyboard surface first: dense rows, big targets on the one number that actually gets
 * typed, and no styling that costs a tab stop. */
table.grid { font-size: .8rem; }
table.grid th {
  position: sticky;
  top: var(--shell-h);
  z-index: 2;
  background: var(--sunk);
}
table.grid td { padding: .15rem .3rem; }
table.grid td.loom {
  font-family: var(--mono);
  font-weight: 700;
}
.grid-input {
  width: 6.5rem;
  padding: .25rem .4rem;
  text-align: right;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  background: var(--surface);
  color: var(--ink);
}
.grid-input.narrow { width: 4.5rem; }
.grid-input:focus {
  outline: 2px solid var(--mark);
  outline-offset: -1px;
  background: var(--cursor);
}
tr:has(.grid-input:focus) { background: var(--cursor); }

/* ========================================================== KEY BAR
 * What this screen answers to, along the bottom, the way the mill's older software did.
 * Written by keys.js from the controls it actually bound, so it cannot promise a key
 * that does nothing. */
.keybar {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  z-index: 45;
  display: flex;
  align-items: stretch;
  background: var(--sunk);
  border-top: 3px solid var(--mark);
  font-family: var(--mono);
  font-size: .68rem;
  overflow-x: auto;
}
.keybar:empty { display: none; }
.keybar span {
  display: flex; align-items: center; gap: .4rem;
  padding: .3rem .8rem;
  border-right: 1px solid var(--line);
  color: var(--muted);
  letter-spacing: .06em;
  white-space: nowrap;
}
.keybar kbd, kbd {
  font-family: var(--mono);
  background: var(--mark);
  color: var(--mark-ink);
  padding: .05rem .32rem;
  border-radius: var(--radius);
  font-weight: 700;
  font-size: .95em;
}
.keybar .hintkey { margin-left: auto; border-right: 0; }

/* A badge on the control itself, so the key and the button it presses are never two
   separate things to learn. */
.keytag {
  font-family: var(--mono);
  font-size: .62rem;
  font-weight: 700;
  padding: .02rem .28rem;
  border: 1px solid currentColor;
  border-radius: var(--radius);
  opacity: .75;
  letter-spacing: .04em;
}

/* ========================================================== HELP OVERLAY */
.keyhelp {
  position: fixed; inset: 0; z-index: 80;
  background: color-mix(in srgb, var(--bg) 82%, transparent);
  display: flex; align-items: center; justify-content: center;
  padding: 1.5rem;
}
.keyhelp[hidden] { display: none; }
.keyhelp-panel {
  background: var(--surface);
  border: 1px solid var(--strong);
  border-top: 3px solid var(--mark);
  box-shadow: 0 20px 60px var(--shadow);
  max-width: 46rem; width: 100%;
  max-height: 80vh; overflow-y: auto;
  padding: 1.1rem 1.2rem;
}
.keyhelp-panel h2 { margin: 0 0 .2rem; }
.keyhelp-panel .sub { margin-bottom: .9rem; }
.keyhelp-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(17rem, 1fr));
  gap: .3rem .9rem;
}
.keyhelp-grid div {
  display: flex; align-items: baseline; gap: .5rem;
  padding: .25rem 0;
  border-bottom: 1px solid var(--line);
  font-size: .8rem;
}
.keyhelp-grid div b { margin-left: auto; color: var(--muted); font-weight: 400; text-align: right; }

/* ========================================================== THEME SWATCHES
 * Each swatch shows the marking as it will actually appear on the ground currently in
 * use. Showing the daylight hex while the screen is dark would be a colour chart that
 * lies about its own colours. */
.accent-grid .swatch[data-a="vat"]      { background: #201b51; }
.accent-grid .swatch[data-a="indigo"]   { background: #2f3f77; }
.accent-grid .swatch[data-a="steel"]    { background: #33566a; }
.accent-grid .swatch[data-a="amber"]    { background: #f0a92c; }
.accent-grid .swatch[data-a="greige"]   { background: #1c1a14; }
.accent-grid .swatch[data-a="selvedge"] { background: #b3241a; }
html[data-ground="dark"] .accent-grid .swatch[data-a="vat"]      { background: #8f86e8; }
html[data-ground="dark"] .accent-grid .swatch[data-a="indigo"]   { background: #7f9cff; }
html[data-ground="dark"] .accent-grid .swatch[data-a="steel"]    { background: #6fb3cc; }
html[data-ground="dark"] .accent-grid .swatch[data-a="amber"]    { background: #f0a92c; }
html[data-ground="dark"] .accent-grid .swatch[data-a="greige"]   { background: #cfc6ac; }
html[data-ground="dark"] .accent-grid .swatch[data-a="selvedge"] { background: #ff7a63; }

/* ========================================================== LOGIN
 * The one screen with nothing to navigate to yet. The mode strip is hidden rather than
 * shown full of links that all bounce back here. */
body.auth .modes { display: none; }
body.auth main { max-width: 24rem; margin: 0 auto; width: 100%; }
body.auth .card { margin: 0; }

/* ========================================================== RESPONSIVE */
@media (max-width: 800px) {
  .topbar .ctx { display: none; }
  main { padding: .9rem .7rem 2rem; }
  form .row { grid-template-columns: 1fr; }
}

@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
}

/* Print: the register on paper is the mill's own record, so the furniture goes. */
@media print {
  .topbar, .modes, .keybar, .actions, .keyhelp, .subnav, .filters { display: none !important; }
  body { padding: 0; background: #fff; color: #000; }
  .wrap { border: 0; }
}

/* Saving the filters now on screen. Inline in the action row rather than a screen of its
   own, because naming a view is a two-second act attached to the list you are looking at. */
.actions .inline-save { display: flex; align-items: center; gap: .4rem; margin: 0; }
.actions .inline-save input[type="text"] {
  padding: .3rem .45rem; border: 1px solid var(--line); border-radius: var(--radius);
  font: inherit; background: var(--surface); color: var(--ink); min-width: 12rem;
}

/* A line that has to land. Used where a screen is answering a question somebody has already
   asked more than once, and a grey sub-line has demonstrably not been enough. */
.note-strong { color: var(--ink); border-left: 3px solid var(--mark); padding-left: .6rem; }

/* A section heading inside a screen that carries two registers — overdue and upcoming —
   so the eye can tell which table it is reading without checking the column headings. */
h2.section {
  font-family: var(--mono); font-size: .78rem; letter-spacing: .14em;
  text-transform: uppercase; color: var(--ink); margin: 1.4rem 0 .5rem;
}
h2.section .sub { font-weight: 400; letter-spacing: 0; text-transform: none; }
/* Today's row in the upcoming list. Not late, but not "later" either. */
tr.due-today > td { background: var(--pending-bg); }

/* The follow-up peek: a date you can act on rather than read. The link is the date itself,
   and the card explains it — whether somebody promised a call back or it is the standing
   chase date, and how far off. Attached to <body> by the script, because the register's
   own `overflow-x: auto` clips anything positioned inside it. */
a.peek { text-decoration: none; border-bottom: 1px dotted var(--line); }
a.peek:hover { border-bottom-color: var(--signal); }
a.peek.none { color: var(--muted); }
.peek-card {
  display: none; position: fixed; z-index: 60;
  max-width: 22rem; padding: .45rem .6rem;
  background: var(--surface); color: var(--ink);
  border: 1px solid var(--line); border-left: 3px solid var(--mark);
  border-radius: var(--radius); box-shadow: 0 4px 14px var(--shadow);
  font-size: .78rem; line-height: 1.4; pointer-events: none;
}
.peek-card strong { display: block; font-size: .8rem; }
.peek-card span { color: var(--muted); }
/* ------------------------------------------------------------------ the office

   The voucher form's difference line. Three states, told by colour the way the pills
   are: balanced reads as posted-green, a difference as cancelled-red, and `insist` is
   the flash given when Ctrl+A is pressed on an unbalanced entry — the answer to "why
   did accept do nothing" delivered where the reason is. */
#line-totals th { border-top: 2px solid var(--mark); }
#line-totals.balanced .t-diff { color: var(--posted); font-weight: 600; }
#line-totals.off .t-diff      { color: var(--void); font-weight: 600; }
#line-totals.insist .t-diff   { text-decoration: underline; }

/* The peek panel: a small read-only answer beside the screen you are on, never over the
   field you are typing in. Fixed to the right so it cannot be clipped by a register's
   own `overflow-x: auto` — the same lesson the desk's follow-up card learned. */
.peek-panel {
  position: fixed; top: 4.5rem; right: 1rem; z-index: 70;
  width: min(22rem, calc(100vw - 2rem)); max-height: 70vh; overflow-y: auto;
  padding: .7rem .85rem;
  background: var(--surface); color: var(--ink);
  border: 1px solid var(--line); border-left: 3px solid var(--mark);
  border-radius: var(--radius); box-shadow: 0 6px 20px var(--shadow);
  font-size: .82rem;
}
.peek-panel h3 {
  margin: 0 0 .4rem; font-family: var(--mono); font-size: .74rem;
  letter-spacing: .12em; text-transform: uppercase;
}
.peek-panel .peek-head { margin: 0 0 .5rem; }
.peek-panel .peek-head.bad { color: var(--void); font-weight: 600; }
.peek-panel table.peek-rows { width: 100%; border-collapse: collapse; }
.peek-panel table.peek-rows td,
.peek-panel table.peek-rows th { padding: .18rem .1rem; border-bottom: 1px solid var(--line); }
.peek-panel tr.late td { color: var(--void); }
.peek-panel .peek-when { margin: .45rem 0 0; color: var(--muted); font-size: .72rem; }
.peek-panel .empty { color: var(--muted); }
