:root {
  --bg: #0f1115;
  --panel: #171a21;
  --panel-2: #1e222b;
  --line: #2a2f3a;
  --text: #e6e8ec;
  --muted: #9aa3b2;
  --accent: #4c8dff;
  --ok: #2fbf71;
  --warn: #e0a52b;
  --err: #e05b5b;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font: 15px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}

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

code {
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: 1px 5px;
  font-size: 13px;
}

.topbar {
  display: flex;
  align-items: center;
  gap: 20px;
  padding: 14px 24px;
  background: var(--panel);
  border-bottom: 1px solid var(--line);
}

.brand { font-weight: 600; color: var(--text); }

.nav { display: flex; align-items: center; gap: 16px; }
.nav form { margin: 0; }

.whoami { margin-left: auto; color: var(--muted); font-size: 13px; }

.linkish {
  background: none;
  border: 0;
  color: var(--accent);
  cursor: pointer;
  font: inherit;
  padding: 0;
  margin: 0;
}
.linkish:hover { text-decoration: underline; }

.wrap {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: 1000px;
  margin: 0 auto;
  padding: 28px 24px 60px;
}

.foot {
  max-width: 1000px;
  width: 100%;
  margin: 0 auto;
  padding: 0 24px 32px;
  color: var(--muted);
  font-size: 12px;
  text-align: center;
}

h1 { font-size: 24px; margin: 0 0 6px; }
h2 { font-size: 16px; margin: 0 0 14px; }
p { margin: 0 0 14px; }

.muted { color: var(--muted); }
.small { font-size: 12px; }
.hint { color: var(--muted); font-size: 12px; font-weight: 400; }

.card {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 22px;
  margin: 0 0 20px;
}
.card.narrow { max-width: 400px; margin: auto; width: 100%; }

label {
  display: block;
  font-size: 13px;
  color: var(--muted);
  margin: 14px 0 5px;
}

input, select {
  width: 100%;
  padding: 9px 11px;
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: 7px;
  color: var(--text);
  font: inherit;
}
input:focus, select:focus {
  outline: none;
  border-color: var(--accent);
}

button {
  margin-top: 20px;
  padding: 10px 16px;
  background: var(--accent);
  border: 0;
  border-radius: 7px;
  color: #fff;
  font: inherit;
  font-weight: 600;
  cursor: pointer;
}
button:hover { filter: brightness(1.08); }
button.small { margin: 0; padding: 5px 10px; font-size: 12px; font-weight: 500; }
button.small.ghost {
  background: transparent;
  border: 1px solid var(--line);
  color: var(--muted);
}
button.small.ghost:hover { color: var(--text); border-color: var(--accent); filter: none; }
button.small.danger {
  background: transparent;
  border: 1px solid var(--line);
  color: #ffb9b9;
}
button.small.danger:hover { border-color: var(--err); background: rgba(224, 91, 91, 0.12); filter: none; }

/* The delete confirmation's submit is the one control that destroys an account,
   so it carries the error colour at full size rather than the accent. */
button.danger { background: var(--err); }

/* A link standing in for a button, used where a control leads to a confirmation
   page instead of acting on the spot. */
a.small.ghost {
  display: inline-block;
  padding: 5px 10px;
  border: 1px solid var(--line);
  border-radius: 7px;
  color: var(--muted);
  font-size: 12px;
  white-space: nowrap;
}
a.small.ghost:hover {
  color: var(--text);
  border-color: var(--accent);
  text-decoration: none;
}

form.row { display: flex; gap: 14px; align-items: flex-end; flex-wrap: wrap; }
form.row > div { flex: 1 1 160px; }
form.row label { margin-top: 0; }
form.row .row-action { flex: 0 0 auto; }
form.row .row-action button { margin-top: 0; }

.alert {
  border-radius: 8px;
  padding: 11px 13px;
  margin: 0 0 16px;
  font-size: 14px;
  border: 1px solid;
}
.alert.error { background: rgba(224, 91, 91, 0.12); border-color: var(--err); color: #ffc9c9; }
.alert.warn  { background: rgba(224, 165, 43, 0.12); border-color: var(--warn); color: #ffe4ae; }
.alert.info  { background: rgba(76, 141, 255, 0.12); border-color: var(--accent); color: #cfe0ff; }

.token {
  margin-top: 10px;
  padding: 10px;
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 6px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12px;
  word-break: break-all;
  color: var(--text);
}

.grid { display: flex; gap: 16px; flex-wrap: wrap; margin-bottom: 20px; }

.stat {
  flex: 1 1 150px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 16px 18px;
}
.stat-num { font-size: 26px; font-weight: 600; }
/* The bidding-window tile says the state in words ("Expiring in 21 days") rather
   than the "21d" the number style was sized for. At 26px that phrase wraps to
   three or four lines and towers over the tiles beside it, and the longest word
   is too wide for a 390px card, so this tile steps down a size and tightens its
   leading instead of overflowing or setting the row's height. */
.stat-num.phrase { font-size: 17px; line-height: 1.3; }
.stat-label { color: var(--muted); font-size: 12px; text-transform: uppercase; letter-spacing: 0.04em; }

table { width: 100%; border-collapse: collapse; font-size: 14px; }
th {
  text-align: left;
  color: var(--muted);
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  padding: 0 10px 8px 0;
  border-bottom: 1px solid var(--line);
}
td { padding: 9px 10px 9px 0; border-bottom: 1px solid var(--line); vertical-align: middle; }
table form { margin: 0; }

td.actions, th.actions-head { text-align: right; padding-right: 0; }
td.actions { display: flex; gap: 8px; justify-content: flex-end; white-space: nowrap; }
td.actions form { display: inline-flex; }

.pill {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 20px;
  font-size: 12px;
}
.pill.ok { background: rgba(47, 191, 113, 0.15); color: #8ce0b0; }
.pill.off { background: rgba(154, 163, 178, 0.15); color: var(--muted); }

.checklist { list-style: none; padding: 0; margin: 0; }
.checklist li { padding: 6px 0 6px 26px; position: relative; border-bottom: 1px solid var(--line); }
.checklist li:last-child { border-bottom: 0; }
.checklist li.done::before { content: "\2713"; position: absolute; left: 4px; color: var(--ok); }
.checklist li.todo::before { content: "\25CB"; position: absolute; left: 4px; color: var(--muted); }
.checklist li.todo { color: var(--muted); }

/* E-Rate views -------------------------------------------------------- */

.pill.urgent  { background: rgba(224, 91, 91, 0.18); color: #ffb4b4; }
.pill.soon    { background: rgba(224, 165, 43, 0.18); color: #ffdfa0; }
.pill.open    { background: rgba(76, 141, 255, 0.16); color: #bcd4ff; }
.pill.expired,
.pill.unknown,
.pill.err     { background: rgba(154, 163, 178, 0.14); color: var(--muted); }
.pill.err     { background: rgba(224, 91, 91, 0.18); color: #ffb4b4; }

/* A deadline cell states two things at once - the state, in the pill, and the
   date the window runs to. Stacked, the date cost the row a second line at
   every width, and once the pill's words grew from "6d" to "Expiring in 6 days"
   the column could no longer hold its own text on one line either. Held to one
   nowrap line, the cell's min-content width becomes the whole phrase, which is
   what widens the column in an auto-laid-out table; the extra comes out of the
   wide descriptive columns, which have slack, rather than the narrow ones.
   Scoped to this cell rather than to `.pill`, because a long category or
   manufacturer pill elsewhere may legitimately wrap. On a phone the cell
   returns to wrapping (see the stack rules below), so the pill carries its own
   nowrap to stay whole even there. */
td.due { white-space: nowrap; }
td.due .pill { white-space: nowrap; }

.kv {
  display: grid;
  grid-template-columns: 190px 1fr;
  gap: 7px 16px;
  margin: 0;
  font-size: 14px;
}
.kv dt {
  color: var(--muted);
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  padding-top: 2px;
}
.kv dd { margin: 0; word-break: break-word; }

.desc {
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 12px 14px;
  margin: 0 0 12px;
  white-space: pre-wrap;
  font-size: 14px;
}

.attachments { list-style: none; padding: 0; margin: 0; }
.attachments li { padding: 9px 0; border-bottom: 1px solid var(--line); font-size: 14px; }
.attachments li:last-child { border-bottom: 0; }
.attachments .when { color: var(--muted); font-size: 12px; }

.timeline { list-style: none; padding: 0; margin: 0; }
.timeline li {
  border-left: 2px solid var(--line);
  margin-left: 4px;
  padding: 0 0 16px 16px;
  position: relative;
}
.timeline li:last-child { padding-bottom: 0; }
.timeline li::before {
  content: "";
  position: absolute;
  left: -5px;
  top: 6px;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--accent);
}
.timeline .when { color: var(--muted); font-size: 12px; }

.delta {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12px;
  margin-top: 4px;
}
.delta .was { color: #ffb4b4; text-decoration: line-through; }
.delta .now { color: #8ce0b0; }

/* Money and dates read as columns in the FRN table, so they align on the right and
   use tabular figures; proportional digits make two seven-figure sums look the same
   length when they are not. */
th.num, td.num { text-align: right; padding-right: 16px; white-space: nowrap; }
.num { font-variant-numeric: tabular-nums; }

/* The invoice deadline is the one date on an FRN that decides whether money moves. */
.deadline { color: #ffdfa0; }

.pagination {
  display: flex;
  align-items: center;
  gap: 14px;
  justify-content: center;
  margin-top: 18px;
  padding-top: 14px;
  border-top: 1px solid var(--line);
}
.pagination a.pill {
  padding: 6px 14px;
  background: rgba(76, 141, 255, 0.16);
  color: var(--accent);
}
.pagination a.pill:hover {
  background: rgba(76, 141, 255, 0.24);
  text-decoration: none;
}

.back { display: inline-block; margin-bottom: 14px; font-size: 13px; }
.section-note { color: var(--muted); font-size: 12px; margin: 12px 0 0; }

.btn {
  display: inline-block;
  padding: 9px 14px;
  background: var(--accent);
  color: #fff;
  border-radius: 7px;
  font-weight: 600;
}
.btn:hover { text-decoration: none; filter: brightness(1.08); }

/* Phone widths ---------------------------------------------------------
   A 470 gets looked at from a phone as often as from a desk, and at 390px the
   layout above has nowhere to put its topbar or an eight-column table: the page
   is three screens wide and everything useful is off the right edge. Everything
   in this block is inside one media query, so the desk layout at 900px and up is
   exactly what it was. */

@media (max-width: 899px) {
  /* `anywhere` rather than `break-word` because it also shrinks an element's
     min-content width, which is what keeps a flex row - the topbar, a filter
     row, the stat grid - from forcing the page wider than the screen. It only
     bites on a word that cannot fit, so ordinary prose is untouched. */
  body { overflow-wrap: anywhere; }

  .topbar {
    flex-wrap: wrap;
    gap: 8px 16px;
    padding: 10px 16px;
  }
  .brand { flex: 1 1 auto; }
  /* The nav takes a line of its own and the identity a third, so nothing has to
     share a row with the brand. */
  .nav {
    order: 2;
    flex: 1 1 100%;
    flex-wrap: wrap;
    gap: 4px 16px;
  }
  .whoami { order: 3; flex: 1 1 100%; margin-left: 0; }

  /* Fingers, not cursors. Sign-out is one of these, so it is reachable with a
     thumb rather than a stylus. */
  .nav a,
  .nav button.linkish,
  button.small,
  a.small.ghost,
  .pagination a.pill {
    display: inline-flex;
    align-items: center;
    min-height: 40px;
  }
  button.small { margin: 0; padding: 8px 12px; }
  a.small.ghost { padding: 8px 12px; }

  .wrap { padding: 20px 16px 48px; }
  .foot { padding: 0 16px 24px; }
  .card { padding: 16px; }
  .pagination { flex-wrap: wrap; }

  /* One field per row: two 160px columns in a 326px card leaves every select
     too narrow to show the value it is set to. */
  form.row > div { flex: 1 1 100%; }

  /* Label above value, rather than a 190px gutter that would leave the value
     narrower than the label describing it. */
  .kv { grid-template-columns: 1fr; gap: 2px 0; }
  .kv dd { margin-bottom: 10px; }

  /* A wide list table becomes one block per row. The header row is dropped and
     each cell carries its own column name, so a number is never separated from
     what it counts - which is what scrolling a nine-column table sideways does
     to it. */
  table.stack, table.stack tbody, table.stack tr, table.stack td {
    display: block;
    width: auto;
  }
  table.stack thead { display: none; }
  table.stack tr {
    padding: 10px 0;
    border-bottom: 1px solid var(--line);
  }
  table.stack tr:last-child { border-bottom: 0; }
  /* `white-space: normal` undoes the nowrap that `td.num` uses to keep a money
     column from wrapping: there is no column left to keep aligned. */
  table.stack td {
    padding: 4px 0;
    border-bottom: 0;
    white-space: normal;
  }
  /* The label gutter is a measured width, not a preference: the longest label
     in the app - MANUFACTURER, at 12px uppercase with 0.04em tracking - needs
     about 101px, so a pinned 100px track sits narrower than the word it has to
     hold and the body's `anywhere` then breaks it mid-word on every row.
     `max-content` is the load-bearing half of the pair: a column name added
     later widens its own track instead of splitting, while the 112px floor
     keeps every row's labels lined up down the page rather than sizing each
     row to its own longest cell. */
  table.stack td[data-label] {
    display: grid;
    grid-template-columns: minmax(112px, max-content) 1fr;
    gap: 4px 10px;
    align-items: start;
  }
  table.stack td[data-label]::before {
    content: attr(data-label);
    color: var(--muted);
    font-size: 12px;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    padding-top: 2px;
  }
  /* Right alignment existed to line numbers up in a column; there is no column
     now, so they read as ordinary values. */
  table.stack td.num { text-align: left; padding-right: 0; }

  /* A row's buttons become a wrapping row of their own below its values, still
     carrying the column name that said what they act on. */
  table.stack td.actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: flex-start;
    gap: 8px;
    padding-top: 8px;
  }
  /* Same gutter as the labels above, or the buttons step out of line with the
     column names they sit under. */
  table.stack td.actions::before { flex: 0 0 112px; }

  /* --------------------------------------------------------- a lead is a card

     Eight rows of "LABEL / value" give a lead's name and its deadline exactly
     the weight of its manufacturer and its line count, which is what turns
     twenty leads into one grey wall. A lead is four things, not eight: a title
     (the applicant), a body (what they are buying), a state (the deadline) and
     a line of codes. Same cells, given an order and a weight. The desk table
     above is untouched, and this only reaches the lead tables - the short
     reference table at the foot of each page keeps its stacked layout. */
  table.stack.leads tbody tr {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 3px 10px;
    padding: 14px 14px 12px;
    /* Wider than the padding inside the card on purpose. Grouping is read from
       proximity, and at 10px against 14px of padding the gap between two cards
       was tighter than the space around the lines within one, so the boundary
       between cards was the quieter edge of the two - the eye grouped across it
       instead of stopping at it. The gap has to be the larger number. */
    margin-bottom: 16px;
    /* The card behind is --panel, so the row is a step lighter and this border
       draws the boundary the old border-bottom used to. */
    background: var(--panel-2);
    border: 1px solid var(--line);
    border-radius: 10px;
  }
  table.stack.leads tbody tr:last-child { margin-bottom: 0; }

  table.stack.leads tbody td {
    padding: 0;
    border-bottom: 0;
    font-size: 14px;
    /* Not the 112px label grid the generic rule sets: the label column is
       exactly what made each lead read as eight rows. */
    display: block;
  }
  /* With the label inline ahead of its value rather than in a column of its
     own, it needs the space the grid track used to give it. */
  table.stack.leads tbody td[data-label]::before { margin-right: 6px; }

  /* The title: the one thing on the card that names it, so the one thing at
     body weight. It takes the slack and is allowed to shrink, so a long name
     wraps rather than shoving the state off the card. `text-wrap: pretty` lets
     the browser move a word up to avoid a stub line: the buying line ends in a
     "+N" counter, and one character alone on the last line of a card reads as a
     rendering fault rather than as a count. It only rebalances where it can, so
     the lines it leaves alone are unchanged. */
  table.stack.leads td[data-label="Applicant"] {
    order: 1;
    flex: 1 1 auto;
    min-width: 0;
    font-size: 16px;
    font-weight: 600;
    line-height: 1.35;
    text-wrap: pretty;
  }
  table.stack.leads td[data-label="Applicant"]::before { content: none; }
  table.stack.leads td[data-label="Applicant"] .small { font-weight: 400; }

  /* The state: the deadline, or the 471's status, or the closed table's how
     long ago. It used to be pulled to the top right beside the name, which only
     worked while it fit there: measured on a 390px card it fit on one card in
     twenty-one, and on the other twenty flex-wrap dropped it to a line of its
     own where `margin-left: auto` left it hard against the right edge under a
     long empty gap. So the line it keeps landing on is the one it is given
     instead, full width and reading from the same left edge as the name, the
     body and the codes above and below it. The pill already says what it is, so
     its column label would be noise. */
  table.stack.leads td[data-label="Deadline"],
  table.stack.leads td[data-label="Status"],
  table.stack.leads td[data-label="Closed"] {
    order: 2;
    flex: 1 1 100%;
    display: inline-flex;
    align-items: baseline;
    gap: 6px;
  }
  table.stack.leads td[data-label="Deadline"]::before,
  table.stack.leads td[data-label="Status"]::before,
  table.stack.leads td[data-label="Closed"]::before { content: none; }

  /* The body: what they are buying, on a line of its own under the title.
     `text-wrap: pretty` for the same reason as the title - the "+N" counter that
     ends this line is worth keeping off a line of its own. */
  table.stack.leads td[data-label="Buying"] {
    order: 3;
    flex: 1 1 100%;
    color: var(--text);
    text-wrap: pretty;
  }

  /* Everything else is a code - category, line count, RFP and application
     number on a 470; the FRN and its money and dates on a 471 - and they belong
     on one muted line at the foot of the card. Written by exclusion rather than
     by naming them, so a column added later joins the line instead of inventing
     a ninth row. `max-width` is the guard on the one value that can be long
     (a manufacturer list): without it the line's own no-shrink rule would push
     the card wider than the phone. */
  table.stack.leads td:not([data-label="Applicant"]):not([data-label="Buying"]):not([data-label="Deadline"]):not([data-label="Status"]):not([data-label="Closed"]) {
    order: 4;
    flex: 0 0 auto;
    max-width: 100%;
    font-size: 12px;
    color: var(--muted);
    /* Measured, this line wrapped on twenty cards in twenty-one and stood 39-60px
       tall - the largest block on the card, holding the values a reader cares
       about least. Some of that was the two long labels below and some of it was
       the leading: at the tight default two 12px rows pile into one grey block
       that is hard to tell from a paragraph. A little air between the rows costs
       nothing when they do wrap and is invisible when they do not. */
    line-height: 1.5;
  }
  /* The two labels long enough to buy a wrapped row on their own, shortened for
     the phone only. The value is untouched - the manufacturer matters
     commercially and the application number is how the posting is found - and on
     the desk table the column heading is spelled out in full, because there it
     has a column to be read down rather than a shared line to share. */
  table.stack.leads td[data-label="Manufacturer"]::before { content: "MFR"; }
  table.stack.leads td[data-label="Application"]::before { content: "APP"; }
  /* In this line the labels stay, inline, as the delimiters between values, and
     the chrome comes off anything that is not a status, so the line does not
     read as a row of disabled inputs. The state pill keeps its colour - it is
     the one status that matters. */
  table.stack.leads td:not([data-label="Deadline"]):not([data-label="Status"]):not([data-label="Closed"]) .pill,
  table.stack.leads td:not([data-label="Deadline"]):not([data-label="Status"]):not([data-label="Closed"]) code {
    background: none;
    border: 0;
    padding: 0;
    color: inherit;
    font-size: 12px;
  }
  /* Most 470s name no manufacturer, and an empty cell leaves a bare
     MANUFACTURER label with nothing after it. The template writes the cell
     genuinely empty when there is no value - no stray newline - because that is
     what :empty needs to see. */
  table.stack.leads td[data-label="Manufacturer"]:empty { display: none; }
}
