:root {
  --color-bg: #f7f7f5;
  --color-surface: #ffffff;
  --color-border: #d9d9d4;
  --color-text: #1f1f1d;
  --color-muted: #6b6b64;
  --color-primary: #2f5233;
  --color-primary-hover: #244127;
  --color-danger: #a4322a;
  --color-warning: #8a5a1c;
  --color-info: #2f5582;
  --radius: 6px;
  /* Chart series colors, kept separate from the status/badge palette above.
     Validated (see dataviz skill's validate_palette.js) as a categorical
     set against both the light and dark surfaces this app renders on — used
     for the multi-series workflow-type spend trend and, singly, as the
     sequential hue for the volunteer/item ranking bars. The light-mode aqua
     slot (series-3) sits under 3:1 contrast on white (2.82:1) — the
     validator's relief rule for this: direct end-labels (in text color, not
     the series hue) plus the "View as table" fallback already present on
     every chart, so the WARN never leaves identity resting on hue alone. */
  --chart-series-1: #2a78d6;
  --chart-series-2: #eb6834;
  --chart-series-3: #1baf7a;
  --chart-gridline: #e1e0d9;
}

@media (prefers-color-scheme: dark) {
  :root {
    --color-bg: #17181a;
    --color-surface: #212225;
    --color-border: #35363a;
    --color-text: #e8e8e6;
    --color-muted: #a0a09a;
    --color-primary: #6fae76;
    --color-primary-hover: #86c08d;
    --color-danger: #e08279;
    --color-warning: #d9a05b;
    --color-info: #7aa8d9;
    --chart-series-1: #3987e5;
    --chart-series-2: #d95926;
    --chart-series-3: #199e70;
    --chart-gridline: #35363a;
  }
}

* { box-sizing: border-box; }

body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  background: var(--color-bg);
  color: var(--color-text);
}

.container {
  max-width: 1100px;
  margin: 0 auto;
  padding: 1.5rem;
}

/* Opt-in for pages whose content genuinely needs more room (e.g. a wide
   diagnostic table) — deliberately per-page rather than raising the default
   .container width, so ordinary pages keep their narrower, more readable
   measure. */
.container-wide {
  max-width: none;
}

.login-container {
  max-width: 360px;
  margin-top: 4rem;
}

.topnav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.75rem 1.5rem;
  background: var(--color-surface);
  border-bottom: 1px solid var(--color-border);
  flex-wrap: wrap;
}

.topnav-brand { font-weight: 600; }

.topnav-links { display: flex; gap: 1rem; }
.topnav-links a { color: var(--color-text); text-decoration: none; }
.topnav-links a:hover { color: var(--color-primary); }

.topnav-user { display: flex; align-items: center; gap: 0.75rem; color: var(--color-muted); font-size: 0.9rem; }

.link-button {
  background: none;
  border: none;
  color: var(--color-primary);
  cursor: pointer;
  padding: 0;
  font: inherit;
  text-decoration: underline;
}

.flash {
  padding: 0.6rem 1rem;
  border-radius: var(--radius);
  margin-bottom: 1rem;
}
.flash-success { background: rgba(47,82,51,0.15); color: var(--color-primary); }
.flash-error { background: rgba(164,50,42,0.15); color: var(--color-danger); }
.flash-warning { background: rgba(138,90,28,0.15); color: var(--color-warning); }

h1 { margin-top: 0; }

.form { display: flex; flex-direction: column; gap: 0.9rem; max-width: 480px; }
/* Also matches .form-row on its own (some forms, e.g. the order "Add item"
   row, use .form-row directly on the <form> with no .form ancestor) —
   without this, a label whose control has an explicit narrow width (like
   the Quantity input) never wraps onto its own line the way a full-width
   Item/Size <select> does, so its label text and control stay inline on one
   row while the other fields' controls sit a line below — an inconsistent,
   misaligned row instead of every field starting at the same vertical
   position. */
.form label, .form-row label { display: flex; flex-direction: column; gap: 0.3rem; font-size: 0.9rem; color: var(--color-muted); }
/* Unscoped (not just inside .form): a <select>'s natural width is driven by
   its longest option text, so without an explicit width it can overflow its
   flex column and visually sit on top of the next field — clicks there land
   on the sibling element instead of the select underneath it. */
input, select, textarea {
  width: 100%;
  padding: 0.5rem 0.6rem;
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  background: var(--color-surface);
  color: var(--color-text);
  font: inherit;
}
.form-row { display: flex; gap: 1rem; flex-wrap: wrap; }
.form-row > label { flex: 1; min-width: 160px; }

button, .button {
  display: inline-block;
  padding: 0.5rem 1rem;
  background: var(--color-primary);
  color: #fff;
  border: none;
  border-radius: var(--radius);
  cursor: pointer;
  font: inherit;
  text-decoration: none;
}
button:hover, .button:hover { background: var(--color-primary-hover); }
.button-secondary { background: var(--color-surface); color: var(--color-text); border: 1px solid var(--color-border); }
.button-danger { background: var(--color-danger); }
.button-sm { padding: 0.25rem 0.6rem; font-size: 0.82rem; }
.button-stack { display: flex; flex-direction: column; gap: 0.3rem; align-items: flex-start; }
/* Each action form (ref field, quantity, button) prefers one line, but is
   allowed to wrap rather than force the table wider than its container. On a
   roomy screen nothing wraps; on a narrow one the row grows taller instead of
   pushing the last button out of sight behind a horizontal scrollbar. */
.button-stack form { flex-wrap: wrap; }
.refs-cell { font-size: 0.85rem; line-height: 1.4; }
/* Order, delivery and booking references are three separate things about the
   line, so they're spaced apart rather than run together as one paragraph of
   small text. Within a group the lines stay tight, which is what makes the
   grouping read. */
.ref-group + .ref-group { margin-top: 0.7rem; }
/* Separates a count from the reference it follows without letting it drift —
   the two belong to each other and share the line. */
.ref-count { margin-left: 0.6rem; }

.table-wrap { overflow-x: auto; }
table { width: 100%; border-collapse: collapse; background: var(--color-surface); border-radius: var(--radius); overflow: hidden; }
th, td { text-align: left; padding: 0.5rem 0.7rem; border-bottom: 1px solid var(--color-border); font-size: 0.92rem; }
th { color: var(--color-muted); font-weight: 600; background: rgba(0,0,0,0.02); }
td.nowrap { white-space: nowrap; }
tbody tr:hover { background: rgba(0,0,0,0.02); }
/* nowrap keeps a heading on one line by default; headings that are genuinely
   too long carry their own <br> and opt back in via normal wrapping, so they
   break where they read best rather than wherever the column runs out. */
.sortable-th a { color: inherit; text-decoration: none; white-space: nowrap; }
.sortable-th a:has(br) { white-space: normal; }
.th-sub { font-weight: 400; opacity: 0.85; }
.sortable-th a:hover { text-decoration: underline; }

/* Money fields are always typed, never stepped — the arrows just make the
   box narrower and invite mis-clicks next to the digits. Stepping by 0.01
   was never a realistic way to reach a price anyway. Quantity inputs keep
   theirs, where stepping genuinely is useful. */
/* Document attachment lives inside a dense table cell, so the control is a
   single character until opened — otherwise every reference would carry a
   file input. <details> keeps that behaviour without any JavaScript. */
/* display:inline keeps the toggle sitting alongside the reference rather
   than forcing it onto its own line. The catch is that overriding display on
   <details> also defeats the browser's own hiding of a closed element's
   content, so the panel has to be hidden explicitly -- without this rule
   every reference permanently shows its file input, Attach button, filename
   and uploader, which is what made the References column so hard to read.
   The panel is also a block box, so it broke the line despite the cell's
   white-space:nowrap and left each "x1" stranded under its reference. */
.doc-attach { display: inline; }
.doc-attach:not([open]) > .doc-attach-body { display: none; }
.doc-attach > summary { display: inline; cursor: pointer; list-style: none; font-size: 0.8rem; opacity: 0.6; }
.doc-attach > summary::-webkit-details-marker { display: none; }
.doc-attach > summary:hover { opacity: 1; }
.doc-attach-body { padding: 0.3rem 0 0.2rem; font-size: 0.8rem; }

/* Flags paperwork whose filename disagrees with the reference it's attached
   to, or that's been borrowed from a reference of another type sharing the
   same number. Advisory only — the naming is a habit, not a rule, so this
   points rather than blocks. */
.doc-misfiled { color: #b45309; font-size: 0.78rem; cursor: help; }
/* Advisory-only findings — a size written differently, an unverified older
   upload. Deliberately quieter than the amber warning: if everything shouts,
   the things that matter stop being visible. */
.doc-note { color: var(--color-muted); font-size: 0.78rem; cursor: help; }

/* Same amber-advisory treatment as .doc-misfiled, reused for a different
   thing: an order inside its data-retention warning window or overdue for
   purging. Non-blocking, explains itself via the title tooltip. */
.retention-warning { color: #b45309; font-size: 0.78rem; cursor: help; }

/* Year paging sits inline with the heading so it reads as part of it. The
   arrow at either end of the available range stays in place, greyed, rather
   than disappearing — a control that vanishes is harder to trust than one
   that's visibly unavailable. */
.year-nav { display: flex; align-items: baseline; gap: 0.6rem; }
.year-nav h2 { margin: 0; }
.year-nav-arrow { font-size: 1.2rem; text-decoration: none; line-height: 1; }
.year-nav .muted-disabled { color: var(--color-border); cursor: default; }
.year-nav-today { font-size: 0.82rem; margin-left: 0.4rem; }

/* Budget overview laid out as worked sums: a narrow operator column, a rule
   above each result, and a gap between blocks so the three calculations read
   as separate questions rather than one running list. */
/* Sized to its content and centred, rather than stretched across the card.
   Full width drove the labels and the figures to opposite edges, so reading
   a line meant crossing a void — the eye had to travel much further than the
   information warranted. Narrow and centred keeps each label next to its own
   number.

   The operator sits immediately left of the amount so each line reads as the
   sum it is, rather than as a symbol stranded out in the margin. */
.sum-table { width: auto; margin: 0 auto; min-width: min(28rem, 100%); }
.sum-table .sum-op { text-align: right; padding-right: 0.35rem; padding-left: 2rem; white-space: nowrap; color: var(--color-muted); }
/* Ruled like a hand-worked sum: nothing between the operands, and the result
   closed off above and below. The row borders that come with every other
   table chopped each calculation into three unrelated-looking bands, which
   is the opposite of what these blocks are for. --color-muted rather than
   --color-border so the rules read as part of the arithmetic instead of as
   incidental table furniture, and it carries its own dark-theme value. */
.sum-table td,
.breakdown-table td { border-bottom: none; }
.sum-table td { padding-top: 0.28rem; padding-bottom: 0.28rem; }
/* The result keeps a little more room so the rules don't crowd the figure. */
.sum-table .sum-total > td,
.breakdown-table .sum-total > td {
  border-top: 2px solid var(--color-muted);
  border-bottom: 2px solid var(--color-muted);
}
.sum-table .sum-total > td { padding-top: 0.42rem; padding-bottom: 0.42rem; }
.sum-table .sum-gap > td { padding-top: 1.4rem; }

/* The order comment is a block; a line comment sits in a row spanning the
   table beneath the line it annotates, so the ten-column table doesn't have
   to grow an eleventh and start overflowing again. The row is deliberately
   quiet — it's an annotation, not a figure. */
.comment-form { display: flex; flex-direction: column; gap: 0.4rem; align-items: flex-start; max-width: 640px; }
.comment-form label { font-size: 0.9rem; color: var(--color-muted); }
.comment-form textarea { width: 100%; }
.comment-form-inline { display: flex; gap: 0.5rem; align-items: center; }
.comment-form-inline input { max-width: 32rem; font-size: 0.85rem; padding: 0.25rem 0.45rem; }
.comment-row > td { padding-top: 0; padding-bottom: 0.6rem; }
.comment-row:hover { background: none; }

/* "or read it from the document" — folded away like the attachment panel, so
   the typed field stays the obvious primary action and the alternative is
   there without competing with it. */
.from-doc > summary { font-size: 0.75rem; opacity: 0.75; }
.from-doc-form { display: flex; gap: 0.3rem; align-items: center; flex-wrap: wrap; margin-top: 0.25rem; }
.from-doc-form input[type="file"] { max-width: 150px; font-size: 0.78rem; }

/* Group heading inside a supplier section. Quieter than the section title
   above it so the hierarchy reads supplier > group > rows. */
.group-heading { font-size: 0.95rem; margin: 1.2rem 0 0.5rem; font-weight: 600; }
.group-heading:first-of-type { margin-top: 0.25rem; }
.group-heading .muted { font-weight: 400; font-size: 0.8rem; margin-left: 0.4rem; }

.no-spinner { -moz-appearance: textfield; appearance: textfield; }
.no-spinner::-webkit-outer-spin-button,
.no-spinner::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }

.card { background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--radius); padding: 1rem; margin-bottom: 1rem; }

.badge { display: inline-block; padding: 0.15rem 0.5rem; border-radius: 999px; font-size: 0.78rem; background: rgba(0,0,0,0.08); }
/* Colour follows the line's real-world progress: grey (not started) ->
   blue (with the supplier) -> yellow (goods in, not yet handed over) ->
   green (issued/complete). Partial states use a lighter tint of their
   target colour rather than a separate hue, so "partially received" reads
   as "on the way to received" rather than as its own unrelated status. */
.badge-pending, .badge-pending_demand { background: rgba(107,107,100,0.15); color: var(--color-muted); }
.badge-ordered { background: rgba(47,85,130,0.15); color: var(--color-info); }
.badge-partially_received { background: rgba(138,90,28,0.15); color: var(--color-warning); }
.badge-received { background: rgba(138,90,28,0.3); color: var(--color-warning); }
.badge-partially_issued { background: rgba(47,82,51,0.15); color: var(--color-primary); }
.badge-issued { background: rgba(47,82,51,0.3); color: var(--color-primary); }
/* Returned keeps the danger palette but no line-through: the item really was
   ordered, received and paid for, so it's real history rather than something
   that never happened (which is what cancelled's strike-through conveys). */
.badge-returned { background: rgba(164,50,42,0.28); color: var(--color-danger); }
.badge-cancelled { background: rgba(164,50,42,0.12); color: var(--color-danger); text-decoration: line-through; }

.field-highlight { outline: 2px solid var(--color-warning); outline-offset: 2px; border-radius: var(--radius); }

.toolbar { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem; flex-wrap: wrap; gap: 0.75rem; }

.muted { color: var(--color-muted); }
.text-right { text-align: right; }
.hidden { display: none !important; }

/* --- Statistics page --- */

.stat-tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 1rem; margin-bottom: 1.5rem; }
.stat-tile { background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--radius); padding: 1rem; }
.stat-tile-label { color: var(--color-muted); font-size: 0.85rem; }
/* Proportional (not tabular) figures at display size — tabular-nums is for
   columns that must align vertically, which a lone stat tile never does. */
.stat-tile-value { font-size: 1.9rem; font-weight: 600; margin-top: 0.2rem; }

.chart-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(420px, 1fr)); gap: 1rem; }
.chart-card h2 { margin: 0 0 0.9rem; font-size: 1.05rem; }
.chart-svg { width: 100%; height: auto; overflow: visible; font-family: inherit; }
.chart-axis-label { fill: var(--color-muted); font-size: 11px; }
.chart-gridline { stroke: var(--chart-gridline); stroke-width: 1; }
.chart-value-label { fill: var(--color-text); font-size: 11px; }
.chart-category-label { fill: var(--color-text); font-size: 12px; }
.chart-legend { display: flex; gap: 1rem; flex-wrap: wrap; margin-top: 0.7rem; font-size: 0.82rem; color: var(--color-muted); }
.chart-legend-item { display: flex; align-items: center; gap: 0.4rem; }
.chart-legend-swatch { width: 10px; height: 10px; border-radius: 2px; display: inline-block; }
.chart-empty { color: var(--color-muted); font-size: 0.9rem; }

.chart-table-toggle { margin-top: 0.6rem; }
.chart-table-toggle > summary { font-size: 0.8rem; opacity: 0.75; cursor: pointer; }
.chart-table-toggle > summary:hover { opacity: 1; }
.chart-table-toggle table { margin-top: 0.5rem; }

/* Same status -> hue mapping as the .badge-* rules above, as solid fills
   for the status breakdown bar chart. partially_* reuses its full status's
   hue at reduced opacity, mirroring how the badges tell "on the way to X"
   from "at X" apart. */
.bar-status-pending { fill: var(--color-muted); }
.bar-status-ordered { fill: var(--color-info); }
.bar-status-partially_received { fill: var(--color-warning); opacity: 0.55; }
.bar-status-received { fill: var(--color-warning); }
.bar-status-partially_issued { fill: var(--color-primary); opacity: 0.55; }
.bar-status-issued { fill: var(--color-primary); }
.bar-status-returned { fill: var(--color-danger); }
.bar-status-cancelled { fill: var(--color-muted); opacity: 0.5; }
.bar-default { fill: var(--chart-series-1); }
