/* Archivepanel — interface stylesheet
   Dense, rule-based enterprise layout: muted palette, square corners,
   uppercase micro-labels and monospaced data columns. */

:root {
  --ink:          #0f1723;
  --ink-2:        #1c2531;
  --body:         #2b3542;
  --muted:        #5b6676;
  --faint:        #8b98a8;
  --line:         #dde2e9;
  --line-strong:  #c4cddb;
  --surface:      #ffffff;
  --surface-alt:  #f7f9fb;
  --canvas:       #eef1f5;
  --accent:       #14417a;
  --accent-hover: #0e2f59;
  --accent-soft:  #e8eef7;
  --ok:           #14663f;
  --ok-soft:      #e4f1ea;
  --warn:         #7d5100;
  --warn-soft:    #fbf1de;
  --err:          #9b2c2c;
  --err-soft:     #f8e9e9;
  --mono: "SFMono-Regular", ui-monospace, Menlo, Consolas, "Liberation Mono", monospace;
  --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
}

* { box-sizing: border-box; }

/* The scrollbar gutter is reserved whether or not the page scrolls, so a short
   page and a long one put the sidebar in the same place — and, more to the
   point, taking the scrollbar away to open a dialog cannot slide the layout
   sideways underneath it. app.js measures and pads for the browsers that do
   not honour this yet. */
html { -webkit-text-size-adjust: 100%; scrollbar-gutter: stable; }

body {
  margin: 0;
  font-family: var(--sans);
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--body);
  background: var(--canvas);
  -webkit-font-smoothing: antialiased;
}

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

h1, h2, h3 { margin: 0; font-weight: 600; color: var(--ink); letter-spacing: -0.01em; }
h1 { font-size: 19px; }
h2 { font-size: 15px; }
h3 { font-size: 13px; }

code, .mono, .num { font-family: var(--mono); font-size: 12px; font-variant-numeric: tabular-nums; }

/* ---------- masthead ---------- */

.masthead {
  background: var(--ink);
  border-bottom: 1px solid #000;
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 20px;
  height: 50px;
  position: sticky;
  top: 0;
  z-index: 40;
}

.wordmark { display: flex; align-items: center; gap: 10px; }

/* The glyph sits in a square badge so it reads as a mark rather than as
   another piece of text on the bar. Sizing is on the badge; the SVG just
   fills it and takes its ink from currentColor. */
.brandmark-badge {
  display: inline-flex; align-items: center; justify-content: center;
  width: 27px; height: 27px; flex: 0 0 27px;
  background: var(--ink-2); border: 1px solid #2c3746; color: #fff;
}
.brandmark-badge .brandmark { width: 17px; height: auto; display: block; }
.brandmark-badge.large {
  width: 46px; height: 46px; flex-basis: 46px; margin-bottom: 12px;
  background: var(--ink); border-color: var(--ink);
}
.brandmark-badge.large .brandmark { width: 29px; }
.wordmark .name {
  font-size: 14.5px; font-weight: 600; color: #fff;
  letter-spacing: 0.12em; text-transform: uppercase;
}
.wordmark .qualifier {
  font-size: 10px; color: #7d8b9d; letter-spacing: 0.16em; text-transform: uppercase;
  border-left: 1px solid #2c3746; padding-left: 10px;
}

.masthead-right { display: flex; align-items: center; gap: 16px; font-size: 12px; }
.masthead-user { color: #b7c1cd; }
.masthead-user strong { color: #fff; font-weight: 600; }
.masthead form { margin: 0; }
.linkbtn {
  background: none; border: 1px solid #37455a; color: #c3cdd9;
  font: inherit; font-size: 11.5px; padding: 4px 11px; cursor: pointer;
  letter-spacing: 0.04em;
}
.linkbtn:hover { background: #1b2534; color: #fff; border-color: #4a5b73; }

/* ---------- shell ---------- */

.shell { display: flex; min-height: calc(100vh - 50px); align-items: stretch; }

.sidebar {
  width: 216px; flex: 0 0 216px;
  background: var(--surface);
  border-right: 1px solid var(--line);
  padding: 18px 0;
}
.sidebar .group-label {
  font-size: 10px; text-transform: uppercase; letter-spacing: 0.13em;
  color: var(--faint); padding: 0 18px; margin: 0 0 8px;
}
.sidebar nav { display: flex; flex-direction: column; margin-bottom: 22px; }
.sidebar nav a {
  padding: 7px 18px; color: var(--body); font-size: 13px;
  border-left: 2px solid transparent;
}
.sidebar nav a:hover { background: var(--surface-alt); text-decoration: none; }
.sidebar nav a.active {
  background: var(--accent-soft); border-left-color: var(--accent);
  color: var(--accent); font-weight: 600;
}

.main { flex: 1 1 auto; min-width: 0; padding: 22px 26px 60px; }

.page-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 20px; margin-bottom: 18px; padding-bottom: 14px;
  border-bottom: 1px solid var(--line-strong);
}
.page-head .subtitle { color: var(--muted); font-size: 12.5px; margin-top: 3px; }
.page-head .actions { display: flex; gap: 8px; flex-shrink: 0; flex-wrap: wrap; }

.breadcrumb { font-size: 11px; color: var(--faint); letter-spacing: 0.05em;
  text-transform: uppercase; margin-bottom: 5px; }
.breadcrumb a { color: var(--muted); }

/* ---------- panels ---------- */

.panel { background: var(--surface); border: 1px solid var(--line); margin-bottom: 18px; }
.panel-head {
  display: flex; align-items: center; justify-content: space-between; gap: 14px;
  padding: 11px 16px; border-bottom: 1px solid var(--line); background: var(--surface-alt);
  flex-wrap: wrap;
}
.panel-head h2, .panel-head h3 {
  font-size: 11px; text-transform: uppercase; letter-spacing: 0.11em; color: var(--ink-2);
}
.panel-body { padding: 16px; }
/* A table wider than the panel scrolls inside it rather than widening the
   page: between the point where a column stops fitting and the point where
   the rows become records, sideways scrolling is what keeps the header row
   and the body in step. */
.panel-body.tight { padding: 0; overflow-x: auto; }
.panel-foot {
  padding: 10px 16px; border-top: 1px solid var(--line);
  background: var(--surface-alt); font-size: 12px; color: var(--muted);
  display: flex; justify-content: space-between; align-items: center; gap: 12px;
  flex-wrap: wrap;
}

.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; align-items: start; }
@media (max-width: 1080px) { .grid-2 { grid-template-columns: 1fr; } }

/* ---------- statistics ---------- */

.stat-row {
  display: grid; grid-template-columns: repeat(5, 1fr);
  border: 1px solid var(--line); background: var(--surface); margin-bottom: 18px;
}
.stat { padding: 14px 16px; border-right: 1px solid var(--line); }
.stat:last-child { border-right: 0; }
.stat .label {
  font-size: 10px; text-transform: uppercase; letter-spacing: 0.12em;
  color: var(--faint); margin-bottom: 6px;
}
.stat .value {
  font-size: 24px; font-weight: 600; color: var(--ink);
  font-variant-numeric: tabular-nums; line-height: 1.1;
}
.stat .value.sm { font-size: 17px; }
.stat .note { font-size: 11.5px; color: var(--muted); margin-top: 3px; }

/* Folded to two columns the row becomes a block of tiles, and the single rule
   between the old columns no longer separates them: the tile above needs a
   rule under it, and the tile at the new right-hand edge must stop drawing one
   beside the border it now sits against. An odd tile left over takes the whole
   last row rather than leaving a half-width hole. */
@media (max-width: 900px) {
  .stat-row { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .stat { border-bottom: 1px solid var(--line); }
  .stat:nth-child(2n) { border-right: 0; }
  .stat:last-child:nth-child(odd) { grid-column: 1 / -1; border-right: 0; }
  .stat:last-child, .stat:nth-last-child(2):nth-child(odd) { border-bottom: 0; }
}

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

table.grid { width: 100%; border-collapse: collapse; }
table.grid th {
  text-align: left; font-size: 10px; text-transform: uppercase; letter-spacing: 0.1em;
  color: var(--muted); font-weight: 600; padding: 9px 16px;
  background: var(--surface-alt); border-bottom: 1px solid var(--line-strong);
  white-space: nowrap;
}
table.grid td {
  padding: 10px 16px; border-bottom: 1px solid var(--line);
  vertical-align: top; font-size: 13px;
}
table.grid tbody tr:last-child td { border-bottom: 0; }
table.grid tbody tr:hover { background: #fafbfd; }
table.grid td.num, table.grid td.mono { font-family: var(--mono); font-size: 11.5px;
  color: var(--muted); white-space: nowrap; font-variant-numeric: tabular-nums; }
table.grid td.right, table.grid th.right { text-align: right; }
table.grid .primary { color: var(--ink); font-weight: 500; display: block; }
table.grid .secondary {
  color: var(--faint); font-size: 11.5px; font-family: var(--mono);
  display: block; margin-top: 2px; word-break: break-all;
}
/* The passage a site search found the query in. Set in the body face rather
   than the mono of .secondary above it: it is prose from the page, not an
   address, and it needs to read as a quotation of the document. */
table.grid .snippet {
  display: block; margin-top: 5px; padding-left: 8px;
  border-left: 2px solid var(--line-strong);
  color: var(--body); font-size: 11.5px; line-height: 1.5;
  max-width: 62ch;
}

/* ---------- danger zone ---------- */

/* A destructive option is a sentence and a button on one line: what it takes
   away on the left, the way to it on the right. The rows are separated by a
   rule rather than boxed, so the panel reads as a short list of consequences
   and not as two competing calls to action. */
.danger-row {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 16px; flex-wrap: wrap;
  padding: 14px 0; border-bottom: 1px solid var(--line);
}
.danger-row:first-child { padding-top: 0; }
.danger-row:last-child { padding-bottom: 0; border-bottom: 0; }
/* The text column is what yields when the row wraps; the button keeps its
   own width and drops underneath. 22ch is roughly the point below which the
   description stops being a sentence and starts being a column of words. */
.danger-row .danger-copy { flex: 1 1 22ch; min-width: 0; }
.danger-row .danger-copy strong { display: block; color: var(--ink-2); font-size: 12.5px; }
.danger-row .danger-copy .hint {
  font-size: 11.5px; color: var(--faint); margin: 4px 0 0; max-width: 62ch;
}
.danger-row .btn { flex: 0 0 auto; }

.row-actions { display: flex; gap: 6px; justify-content: flex-end; }
.row-actions form { margin: 0; }

.empty { padding: 40px 16px; text-align: center; color: var(--muted); }
.empty .headline { color: var(--ink-2); font-weight: 600; margin-bottom: 5px; }
.empty .detail { font-size: 12.5px; max-width: 420px; margin: 0 auto; }

/* ---------- usage meter ---------- */

.meter {
  height: 5px; background: var(--line); margin-top: 7px; overflow: hidden;
}
.meter-fill { display: block; height: 100%; background: var(--accent); }
.meter-fill.warning  { background: var(--warn); }
.meter-fill.critical { background: var(--err); }

/* ---------- previews ---------- */

.preview-body { padding: 12px; background: var(--surface-alt); }
.preview-link { display: block; border: 1px solid var(--line-strong); background: #fff; }
.preview-link:hover { border-color: var(--accent); }
/* The stored image is 1280x800; capping the displayed width keeps it
   downscaled rather than stretched, which is what keeps it sharp. */
.preview-image {
  display: block; width: 100%; height: auto;
  max-height: 300px; object-fit: cover; object-position: top center;
}
.thumb-col { width: 100px; }
.thumb {
  display: block; width: 88px; height: 55px; object-fit: cover; object-position: top center;
  border: 1px solid var(--line-strong); background: var(--surface-alt);
}
.thumb-empty { border-style: dashed; }

/* On the archive record the preview and the record spec are one pair, so they
   share a height: neither box stops short of the other. The row is sized by
   whichever has more content, and the preview image fills whatever height the
   pair settles on rather than keeping its own.
   Only while the two are actually side by side — below the breakpoint they
   stack, there is no pair to match, and the image keeps its own cap. */
@media (min-width: 1081px) {
  .grid-2-even { align-items: stretch; }
  .grid-2-even > .panel { display: flex; flex-direction: column; }
  .grid-2-even > .panel > .panel-body { flex: 1 1 auto; min-height: 0; }
  .grid-2-even .preview-body { display: flex; }
  .grid-2-even .preview-link { flex: 1 1 auto; min-height: 240px; overflow: hidden; }
  .grid-2-even .preview-image { height: 100%; max-height: none; }
}

/* A row's site is a link as well as a label: seeing one snapshot of a site is
   the moment you most often want the rest of them. */
.site-chip {
  display: inline-block; margin-right: 6px; padding: 0 5px;
  border: 1px solid var(--line-strong); background: var(--surface-alt);
  border-radius: 2px; font-family: var(--mono); font-size: 10.5px;
  color: var(--muted); text-decoration: none; vertical-align: 1px;
}
.site-chip:hover { color: var(--accent); border-color: var(--accent); text-decoration: none; }

/* ---------- text snapshot ---------- */

/* The stored text is already laid out — headings underlined, lists bulleted,
   tables drawn in ASCII — so it must not be re-wrapped by the browser. Long
   lines scroll inside the block rather than widening the page. */
.text-snapshot {
  margin: 0;
  padding: 16px 18px;
  max-height: 70vh;
  overflow: auto;
  font-family: var(--mono);
  font-size: 12px;
  line-height: 1.65;
  color: var(--ink-2);
  background: var(--surface-alt);
  white-space: pre;
  tab-size: 4;
}

/* Find-in-text. The controls sit in the panel head beside the heading, and
   shrink before the heading does: on a narrow screen the box is the part
   worth keeping usable. */
.text-search { display: flex; align-items: center; gap: 6px; flex: 1 1 260px; min-width: 0; }
/* The controls are markup-hidden until app.js takes charge of them, and a
   class setting display:flex would otherwise beat the browser's own rule for
   [hidden] and show a search box that cannot search. */
.text-search[hidden] { display: none; }
.text-search input[type=search] {
  flex: 1 1 auto; min-width: 0; width: auto;
  font: inherit; font-size: 12px; padding: 4px 9px;
  border: 1px solid var(--line-strong); background: #fff; color: var(--ink);
}
.text-search input[type=search]:focus {
  outline: none; border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-soft);
}
.text-search-count {
  font-size: 11px; color: var(--muted); white-space: nowrap;
  font-variant-numeric: tabular-nums; min-width: 62px; text-align: right;
}
.text-search .btn { padding: 2px 8px; line-height: 1.4; }

.text-snapshot mark.hit {
  background: #fdf0b5; color: inherit; padding: 0; border-radius: 1px;
}
/* The match being stepped through has to be findable at a glance in a page of
   identical highlights, so it changes colour rather than just gaining a
   border, which would shift the surrounding characters. */
.text-snapshot mark.hit.current {
  background: var(--accent); color: #fff;
}

/* ---------- status pills ---------- */

.pill {
  display: inline-block; font-size: 10px; font-weight: 600; letter-spacing: 0.09em;
  text-transform: uppercase; padding: 3px 7px; border: 1px solid; white-space: nowrap;
}
.pill.complete { color: var(--ok);   background: var(--ok-soft);   border-color: #bcdcc9; }
.pill.failed   { color: var(--err);  background: var(--err-soft);  border-color: #e6c4c4; }
.pill.queued   { color: var(--muted);background: #eef1f5;          border-color: var(--line-strong); }
.pill.running  { color: var(--warn); background: var(--warn-soft); border-color: #e6d3ac; }
.pill.active   { color: var(--ok);   background: var(--ok-soft);   border-color: #bcdcc9; }
.pill.paused   { color: var(--muted);background: #eef1f5;          border-color: var(--line-strong); }
.pill.neutral  { color: var(--muted);background: #eef1f5;          border-color: var(--line-strong); }
/* A stopped capture reads as neither success nor incident: it is greyed like a
   queued one, not reddened like a failure the account had nothing to do with. */
.pill.canceled { color: var(--muted);background: #eef1f5;          border-color: var(--line-strong); }

/* ---------- buttons ---------- */

.btn {
  display: inline-block; font: inherit; font-size: 12.5px; font-weight: 500;
  padding: 6px 14px; border: 1px solid var(--line-strong); background: var(--surface);
  color: var(--ink-2); cursor: pointer; text-align: center; white-space: nowrap;
}
.btn:hover { background: var(--surface-alt); border-color: var(--muted); text-decoration: none; }
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
.btn-primary { background: var(--accent); border-color: var(--accent); color: #fff; }
.btn-primary:hover { background: var(--accent-hover); border-color: var(--accent-hover); color: #fff; }
.btn-danger { color: var(--err); border-color: #e0c0c0; background: #fff; }
.btn-danger:hover { background: var(--err-soft); border-color: #cf9c9c; }
.btn-sm { padding: 3px 9px; font-size: 11.5px; }
/* btn-quiet is for an action that must be available without competing for
   attention — stopping a capture sits beside a progress bar the account is
   watching, and should not read as the point of the card. */
.btn-quiet { border-color: var(--line); color: var(--muted); background: transparent; }
.btn-quiet:hover { color: var(--err); border-color: #e0c0c0; background: var(--err-soft); }
.btn-block { display: block; width: 100%; }

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

.field { margin-bottom: 14px; }
/* Direct child only: the segmented options and the day and hour chips are
   labels nested inside a field too, and this rule is not about them. Left
   unscoped it hands each of them a 5px bottom margin, which shows up as dead
   space inside the bordered control, and blocks what should be inline-flex. */
.field > label {
  display: block; font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.1em;
  color: var(--muted); font-weight: 600; margin-bottom: 5px;
}
.field .hint { font-size: 11.5px; color: var(--faint); margin: 5px 0 0; }
input[type=text], input[type=url], input[type=email], input[type=password],
input[type=number], select {
  width: 100%; font: inherit; font-size: 13px; padding: 7px 10px;
  border: 1px solid var(--line-strong); background: #fff; color: var(--ink);
}
input:focus, select:focus {
  outline: none; border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-soft);
}
input::placeholder { color: #a9b4c2; }
select { appearance: none; background-image:
  linear-gradient(45deg, transparent 50%, var(--muted) 50%),
  linear-gradient(135deg, var(--muted) 50%, transparent 50%);
  background-position: calc(100% - 15px) 52%, calc(100% - 10px) 52%;
  background-size: 5px 5px, 5px 5px; background-repeat: no-repeat; padding-right: 30px; }

.form-row { display: flex; gap: 10px; align-items: flex-end; flex-wrap: wrap; }
.form-row .field { flex: 1 1 auto; margin-bottom: 0; }
.form-row .btn { flex: 0 0 auto; padding-top: 7px; padding-bottom: 7px; }
/* A control the current selection makes irrelevant is disabled and hidden by
   the script rather than left in the way. Disabled styling stays for the few
   controls that are dimmed in place. */
.field input:disabled, .field select:disabled {
  background: var(--surface-alt); color: var(--faint); cursor: not-allowed;
}
/* A field that only qualifies the choice above it is indented under it, so a
   sitemap's page budget reads as part of that answer rather than as the next
   question. */
.field-sub {
  margin-left: 14px; padding-left: 14px; border-left: 2px solid var(--line);
}
.field .narrow { width: 110px; }
/* An "optional" marker sits beside the label rather than in the hint below,
   where the answer to "must I fill this in?" arrives too late. */
.field > label .optional {
  margin-left: 6px; font-weight: 400; letter-spacing: 0.04em;
  text-transform: none; color: var(--faint); font-size: 10.5px;
}
.hint code {
  font-family: var(--mono); font-size: 11px; color: var(--muted);
}

/* A form long enough to need sections — what to capture, then when — gets a
   labelled rule rather than a second panel, which would separate settings that
   are saved by one button. */
.form-row + .form-row { margin-top: 12px; }
.form-divider {
  display: flex; align-items: center; gap: 10px;
  margin: 20px 0 16px; color: var(--muted);
  font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.1em; font-weight: 600;
}
.form-divider::after {
  content: ""; flex: 1 1 auto; height: 1px; background: var(--line);
}

/* The footer of a form: the confirming action on the right, the way out to its
   left, so the pair is in the same place in a dialog and on a page. */
.form-actions { justify-content: flex-end; gap: 8px; }

/* ---------- segmented choices ---------- */

/* A short, fixed set of answers — page or sitemap, hourly to monthly — is a
   row of options rather than a select: all of them are readable at once, and
   choosing one is a single click instead of open-scan-click. They are radios
   underneath, so the keyboard works and the form still posts with scripting
   off. */
/* Inline-level so the row of options hugs its content rather than stretching
   across the field, but aligned to the top of its line box: left on the
   baseline it would sit above the strut's descender, and that leaves a few
   pixels of dead space under a box whose border makes them visible. */
.segmented {
  display: inline-flex; flex-wrap: wrap; vertical-align: top;
  border: 1px solid var(--line-strong); background: #fff; max-width: 100%;
}
.segmented .seg {
  position: relative; display: inline-flex; align-items: center;
  padding: 7px 15px; font-size: 12.5px; color: var(--muted);
  border-right: 1px solid var(--line); cursor: pointer; user-select: none;
}
.segmented .seg:last-child { border-right: 0; }
.segmented .seg:hover { background: var(--surface-alt); color: var(--ink-2); }
/* The input carries the state and the focus ring; the label around it is what
   is seen. It is not display:none, which would take it out of the tab order. */
.segmented .seg input { position: absolute; opacity: 0; width: 1px; height: 1px; }
.segmented .seg:has(input:checked) {
  background: var(--accent); border-color: var(--accent); color: #fff; font-weight: 500;
}
.segmented .seg:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: -2px; }

/* ---------- day and hour grids ---------- */

/* Checkboxes rather than a typed list: "6,12,18" is a rule someone has to
   compose and the server has to reject, whereas a grid of hours is one the
   account picks out and can read back at a glance. They are laid out on a grid
   so the columns line up rather than wrapping into a ragged block. */
.chip-grid { display: grid; gap: 4px; }
.chip-grid.hours { grid-template-columns: repeat(12, minmax(0, 1fr)); }
.chip-grid.days  { grid-template-columns: repeat(8, minmax(0, 1fr)); }
.chip-grid.week  { grid-template-columns: repeat(7, minmax(0, 1fr)); }
.chip {
  position: relative;
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  padding: 6px 10px; border: 1px solid var(--line-strong); background: #fff;
  font-size: 12px; color: var(--ink-2); cursor: pointer; user-select: none;
}
.chip-grid .chip {
  padding: 5px 2px; font-size: 11.5px;
  font-family: var(--mono); font-variant-numeric: tabular-nums;
}
.chip-grid.week .chip { font-family: var(--sans); font-size: 12px; }
/* Hidden, not removed: the box still holds the value, takes focus and answers
   to the space bar, and the chip around it is the visible control. */
.chip-grid .chip input { position: absolute; opacity: 0; width: 1px; height: 1px; }
.chip:hover { border-color: var(--muted); }
.chip:has(input:checked) { border-color: var(--accent); background: var(--accent-soft); color: var(--ink); }
.chip:has(input:checked) span { font-weight: 600; }
.chip:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 1px; }
.chip:has(input:disabled) { background: var(--surface-alt); color: var(--faint); cursor: not-allowed; }

@media (max-width: 700px) {
  .chip-grid.hours { grid-template-columns: repeat(6, minmax(0, 1fr)); }
  .chip-grid.days  { grid-template-columns: repeat(7, minmax(0, 1fr)); }
}

/* The rule in words, written by the script from the controls above it, so the
   schedule can be read back before it is saved rather than after it has run. */
.rule-summary {
  margin: 18px 0 0; padding: 10px 12px;
  border: 1px solid var(--line); border-left: 3px solid var(--accent);
  background: var(--surface-alt); color: var(--ink-2); font-size: 12.5px;
}
.rule-summary strong { color: var(--ink); font-weight: 600; }
.rule-summary.incomplete { border-left-color: var(--warn); background: var(--warn-soft); color: var(--warn); }

/* ---------- dialogs ---------- */

/* A dialog is opened by the script with showModal(), which puts it in the top
   layer. Without scripting the trigger is an ordinary fragment link and the
   :target rule below opens the same element, so a form that lives in a dialog
   is never a form that cannot be reached.
   Both states are styled the same way — the element itself is the full-screen
   scrim — so the two paths cannot drift apart. ::backdrop is cleared for the
   same reason: one scrim, drawn in one place. */
dialog.modal {
  border: 0; padding: 0; margin: 0; background: transparent; color: inherit;
  max-width: none; max-height: none;
}
dialog.modal::backdrop { background: transparent; }
dialog.modal[open], dialog.modal:target {
  display: flex; align-items: flex-start; justify-content: center;
  position: fixed; inset: 0; z-index: 60;
  width: 100%; height: 100%;
  padding: 40px 16px; overflow: auto;
  background: rgba(15, 23, 35, 0.55);
}
/* The box is a column of head, body and footer, and only the body scrolls:
   a schedule is a long form, and the interval it is being given and the button
   that saves it should not have to be hunted for at either end of it. */
.modal-box {
  width: min(560px, 100%); margin: 0 auto;
  display: flex; flex-direction: column;
  max-height: calc(100vh - 80px);
  max-height: calc(100dvh - 80px);
  box-shadow: 0 18px 44px rgba(15, 23, 35, 0.28);
}
.modal-box > form { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; }
.modal-box > form > .panel-body { overflow-y: auto; min-height: 0; }
/* A schedule is a wider form than an address: its hour grid wants the room. */
dialog.modal.wide .modal-box { width: min(760px, 100%); }
/* The last panel in a dialog has no page below it to be spaced from. */
.modal-box.panel { margin-bottom: 0; }
.modal-x {
  font-size: 20px; line-height: 1; color: var(--faint);
  padding: 0 2px; text-decoration: none;
}
.modal-x:hover { color: var(--err); text-decoration: none; }
/* The page behind an open dialog must not scroll under it. Locking it removes
   the scrollbar, which widens the viewport; scrollbar-gutter above holds that
   width steady so nothing behind the scrim moves. */
body.modal-open { overflow: hidden; }

@media (max-width: 700px) {
  dialog.modal[open], dialog.modal:target { padding: 12px 8px; }
  .modal-box { max-height: calc(100vh - 24px); max-height: calc(100dvh - 24px); }
}

.code-input {
  font-family: var(--mono); font-size: 26px; letter-spacing: 0.5em;
  text-align: center; padding: 12px 10px; text-indent: 0.5em;
}

/* ---------- flashes & callouts ---------- */

.flash { padding: 10px 14px; border: 1px solid; margin-bottom: 16px; font-size: 12.5px; }
.flash.ok    { background: var(--ok-soft);   border-color: #bcdcc9; color: var(--ok); }
.flash.error { background: var(--err-soft);  border-color: #e6c4c4; color: var(--err); }
.flash.warn  { background: var(--warn-soft); border-color: #e6d3ac; color: var(--warn); }

.callout {
  border: 1px solid var(--line); border-left: 3px solid var(--muted);
  background: var(--surface-alt); padding: 11px 14px; font-size: 12.5px;
  color: var(--muted); margin-bottom: 16px;
}
.callout strong { color: var(--ink-2); }
.callout.error { border-left-color: var(--err); background: var(--err-soft); color: var(--err); }

/* ---------- definition lists ---------- */

dl.spec { margin: 0; display: grid; grid-template-columns: 132px 1fr; }
dl.spec.spec-wide { grid-template-columns: 152px 1fr; }
dl.spec dt {
  font-size: 10px; text-transform: uppercase; letter-spacing: 0.1em; color: var(--faint);
  padding: 9px 0; border-bottom: 1px solid var(--line);
}
dl.spec dd {
  margin: 0; padding: 9px 0; border-bottom: 1px solid var(--line);
  color: var(--ink-2); word-break: break-word;
}
dl.spec dt:last-of-type, dl.spec dd:last-of-type { border-bottom: 0; }

/* ---------- pagination & filters ---------- */

.toolbar {
  display: flex; gap: 10px; align-items: center; flex-wrap: wrap;
  padding: 11px 16px; border-bottom: 1px solid var(--line); background: var(--surface-alt);
}
.toolbar form { display: flex; gap: 8px; align-items: center; margin: 0; flex-wrap: wrap; }
.toolbar input[type=text] { width: 260px; padding: 5px 9px; font-size: 12.5px; }
.toolbar select { width: auto; padding: 5px 28px 5px 9px; font-size: 12.5px; }
/* Every control in a toolbar row — field, select, button, link-button — is
   pinned to one height. Left to their own padding the buttons come out a few
   pixels shorter than the fields beside them, which reads as a ragged row. */
.toolbar input[type=text], .toolbar select, .toolbar .btn { height: 28px; font-size: 12.5px; }
.toolbar .btn { display: inline-flex; align-items: center; padding: 0 11px; }
.toolbar .spacer { flex: 1 1 auto; }
.toolbar .count { font-size: 11.5px; color: var(--muted); font-variant-numeric: tabular-nums; }

.pager { display: flex; gap: 5px; align-items: center; }
.pager .page-link {
  border: 1px solid var(--line-strong); padding: 3px 9px; font-size: 11.5px;
  color: var(--ink-2); background: #fff; font-variant-numeric: tabular-nums;
}
.pager .page-link:hover { background: var(--surface-alt); text-decoration: none; }
.pager .page-link.current { background: var(--accent); border-color: var(--accent); color: #fff; }
.pager .page-link.disabled { color: #b6c0cc; pointer-events: none; background: var(--surface-alt); }

/* ---------- authentication pages ---------- */

.auth-page {
  min-height: 100vh; display: flex; flex-direction: column;
  align-items: center; justify-content: center; padding: 32px 16px; background: var(--canvas);
}
.auth-brand { text-align: center; margin-bottom: 22px; }
.auth-brand .brandmark-badge { margin-left: auto; margin-right: auto; }
.auth-brand .name {
  font-size: 17px; font-weight: 600; letter-spacing: 0.16em;
  text-transform: uppercase; color: var(--ink);
}
.auth-brand .qualifier {
  font-size: 10.5px; letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--faint); margin-top: 5px;
}
.auth-card { width: 100%; max-width: 396px; background: var(--surface); border: 1px solid var(--line-strong); }
.auth-card .head {
  padding: 14px 22px; border-bottom: 1px solid var(--line); background: var(--surface-alt);
}
.auth-card .head h1 { font-size: 13px; text-transform: uppercase; letter-spacing: 0.1em; }
.auth-card .head .sub { font-size: 12px; color: var(--muted); margin-top: 3px; }
.auth-card .body { padding: 22px; }
.auth-foot {
  margin-top: 18px; text-align: center; font-size: 11.5px; color: var(--faint);
  max-width: 396px; line-height: 1.6;
}
.auth-alt { margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--line);
  text-align: center; font-size: 12.5px; color: var(--muted); }

/* Square the widget's corners to match the interface; everything else is
   left at ALTCHA's own defaults so it looks like the standard control. */
altcha-widget { display: block; margin-bottom: 14px; }
.altcha { --altcha-border-radius: 0; --altcha-max-width: 100%; }

/* ---------- footer ---------- */

.footer {
  margin-top: 30px; padding-top: 14px; border-top: 1px solid var(--line);
  font-size: 11px; color: var(--faint); display: flex;
  justify-content: space-between; gap: 16px; flex-wrap: wrap;
}

/* ---------- misc ---------- */

.nowrap { white-space: nowrap; }
.muted { color: var(--muted); }
.faint { color: var(--faint); }
.strong { color: var(--ink); font-weight: 600; }
.break { word-break: break-all; }
.mt-0 { margin-top: 0; }
.mb-16 { margin-bottom: 16px; }
.error-text { color: var(--err); }
.stack { display: flex; flex-direction: column; gap: 8px; }

/* ---------- live capture progress ---------- */

/* A running capture has no measurable progress to report — SingleFile is
   silent until it finishes — so the bar is driven by elapsed time and the
   stripe animation carries the "still working" signal. */
.panel-live { border-color: var(--line-strong); }
.capture-list { display: flex; flex-direction: column; gap: 12px; }

.capture-card { border: 1px solid var(--line); background: var(--surface-alt); padding: 11px 13px; }
.capture-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; margin-bottom: 9px;
}
.capture-target { font-size: 13px; font-weight: 600; color: var(--ink-2); }
.capture-meta {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; margin-top: 7px; font-size: 11.5px; color: var(--muted);
}
.capture-meta-end { display: flex; align-items: center; gap: 10px; }
/* While a stop request is in flight the card is dimmed but not removed: the
   capture is still running until the next poll says otherwise. */
.capture-card.stopping { opacity: 0.6; }
.inline-form { display: inline-flex; margin: 0; }
.row-inline { display: inline-flex; align-items: center; gap: 6px; justify-content: flex-end; }

.progress { height: 6px; background: var(--line); overflow: hidden; position: relative; }
.progress-fill {
  display: block; height: 100%; background: var(--accent);
  transition: width 900ms linear;
}
.progress.live .progress-fill {
  background-image: linear-gradient(
    115deg,
    rgba(255, 255, 255, 0.28) 25%, transparent 25%,
    transparent 50%, rgba(255, 255, 255, 0.28) 50%,
    rgba(255, 255, 255, 0.28) 75%, transparent 75%);
  background-size: 16px 16px;
  animation: progress-stripes 900ms linear infinite;
}

@keyframes progress-stripes {
  from { background-position: 0 0; }
  to   { background-position: 16px 0; }
}

.spinner {
  display: inline-block; width: 7px; height: 7px; margin-right: 5px;
  border: 1.5px solid currentColor; border-right-color: transparent;
  border-radius: 50%; vertical-align: baseline;
  animation: spin 700ms linear infinite;
}

@keyframes spin { to { transform: rotate(360deg); } }

/* A row that changed state under the reader gets a brief highlight, so the
   change is noticed rather than silently swapped in. */
.row-settled { animation: settle 1.6s ease-out 1; }

@keyframes settle {
  from { background: var(--accent-soft); }
  to   { background: transparent; }
}

@media (prefers-reduced-motion: reduce) {
  .spinner, .progress.live .progress-fill { animation: none; }
  .row-settled { animation: none; }
  .progress-fill { transition: none; }
}

/* ---------- notification tray ---------- */

.alerts { position: relative; }
.bell {
  position: relative; display: flex; align-items: center; justify-content: center;
  width: 30px; height: 26px; padding: 0; cursor: pointer;
  background: none; border: 1px solid #37455a; color: #c3cdd9;
}
.bell:hover { background: #1b2534; color: #fff; border-color: #4a5b73; }
.bell-count {
  position: absolute; top: -7px; right: -7px; min-width: 16px; height: 16px;
  padding: 0 4px; background: var(--err); color: #fff; font-size: 10px;
  font-weight: 600; line-height: 16px; text-align: center;
  border: 1px solid #0f1723; font-variant-numeric: tabular-nums;
}

.alert-panel {
  position: absolute; top: calc(100% + 9px); right: 0; width: 330px; z-index: 60;
  background: var(--surface); border: 1px solid var(--line-strong);
  box-shadow: 0 10px 26px rgba(15, 23, 35, 0.18); color: var(--body);
}
.alert-panel-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 9px 13px; border-bottom: 1px solid var(--line); background: var(--surface-alt);
  font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.11em; color: var(--muted);
}
.alert-panel-head form { margin: 0; }
.linkbtn.tiny {
  border-color: var(--line-strong); color: var(--muted); background: #fff;
  font-size: 10.5px; padding: 2px 7px; letter-spacing: 0.04em; text-transform: none;
}
.linkbtn.tiny:hover { background: var(--surface-alt); color: var(--ink-2); border-color: var(--muted); }

.alert-list { list-style: none; margin: 0; padding: 0; max-height: 340px; overflow-y: auto; }
.alert-item { border-bottom: 1px solid var(--line); border-left: 3px solid var(--accent); }
.alert-item.ok    { border-left-color: var(--ok); }
.alert-item.warn  { border-left-color: var(--warn); }
.alert-item.error { border-left-color: var(--err); }
.alert-item.read  { border-left-color: var(--line-strong); background: var(--surface-alt); }
.alert-item:last-child { border-bottom: 0; }
.alert-item a { display: block; padding: 9px 13px; color: inherit; }
.alert-item a:hover { background: var(--accent-soft); text-decoration: none; }
.alert-title { display: block; font-size: 12.5px; font-weight: 600; color: var(--ink-2); }
.alert-item.read .alert-title { font-weight: 500; color: var(--muted); }
.alert-body { display: block; font-size: 11.5px; color: var(--muted); margin-top: 2px; }
.alert-time { display: block; font-size: 10.5px; color: var(--faint); margin-top: 4px; }
.alert-empty { padding: 22px 14px; text-align: center; font-size: 12px; color: var(--faint); }

/* ---------- toasts ---------- */

.toasts {
  position: fixed; right: 18px; bottom: 18px; z-index: 80;
  display: flex; flex-direction: column; gap: 9px; max-width: 360px;
}
.toast {
  display: flex; align-items: flex-start; gap: 10px;
  background: var(--surface); border: 1px solid var(--line-strong);
  border-left: 3px solid var(--accent); padding: 11px 12px;
  box-shadow: 0 8px 22px rgba(15, 23, 35, 0.16);
  animation: toast-in 180ms ease-out 1;
}
.toast.ok    { border-left-color: var(--ok); }
.toast.warn  { border-left-color: var(--warn); }
.toast.error { border-left-color: var(--err); }
.toast.leaving { opacity: 0; transition: opacity 180ms ease-out; }
.toast-body { flex: 1 1 auto; min-width: 0; font-size: 12.5px; }
.toast-body strong { display: block; color: var(--ink-2); }
.toast-body span { display: block; color: var(--muted); font-size: 11.5px; margin-top: 2px; }
.toast-link {
  flex: 0 0 auto; align-self: center; font-size: 11.5px; font-weight: 600;
  border: 1px solid var(--line-strong); padding: 3px 9px; color: var(--accent); background: #fff;
}
.toast-link:hover { background: var(--accent-soft); text-decoration: none; }
.toast-close {
  flex: 0 0 auto; background: none; border: 0; color: var(--faint);
  font-size: 16px; line-height: 1; padding: 0 2px; cursor: pointer;
}
.toast-close:hover { color: var(--ink-2); }

@keyframes toast-in {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  .toast { animation: none; }
}


/* ---------- narrow screens ---------- */

/* Below the shell breakpoint the interface is one mode rather than several
   patches: the sidebar becomes a strip of tabs above the page, tables stop
   being tables, and every control grows enough to be hit with a thumb. It is
   the last section in the file so it can override what it needs to on source
   order alone, without inflating its selectors to do it. */

@media (max-width: 780px) {

  /* --- masthead --- */

  /* The qualifier and the signed-in address are the first things to go: the
     bar has to hold the mark, the notification bell and the way out, and
     nothing else fits beside them at this width. The address is still on the
     Settings page, which is where it is looked up anyway. */
  .masthead { padding: 0 12px; gap: 10px; }
  .wordmark { gap: 8px; min-width: 0; }
  .wordmark .qualifier { display: none; }
  .masthead-right { gap: 10px; }
  .linkbtn { padding: 7px 11px; }
  .linkbtn.tiny { padding: 6px 10px; }

  /* The tray hangs off the bell, which is not at the edge of the screen — a
     panel wide enough to read, right-aligned to the bell, would run off the
     left of the page. Pinned to the viewport instead, under the sticky bar. */
  .alert-panel {
    position: fixed; top: 50px; right: 8px; left: 8px; width: auto;
  }
  .alert-list { max-height: min(60vh, 340px); }

  /* --- shell --- */

  .shell { flex-direction: column; }
  .sidebar {
    width: auto; flex: none; border-right: 0;
    border-bottom: 1px solid var(--line); padding: 10px 0 2px;
  }
  .sidebar .group-label { padding: 0 12px; margin-bottom: 2px; }
  .sidebar nav { flex-direction: row; flex-wrap: wrap; margin-bottom: 8px; }
  .sidebar nav a {
    padding: 8px 12px; border-left: 0; border-bottom: 2px solid transparent;
  }
  .sidebar nav a.active { border-left: 0; border-bottom-color: var(--accent); }

  .main { padding: 14px 12px 40px; }

  .page-head { flex-direction: column; gap: 12px; }
  .page-head h1 { overflow-wrap: break-word; }

  .panel { margin-bottom: 14px; }
  .panel-head { padding: 10px 12px; }
  .panel-body { padding: 14px 12px; }
  .panel-foot { padding: 10px 12px; }

  /* A two-column definition list at 132px a label leaves nothing for the
     value. spec-wide is named here too: its own rule outranks the plain one,
     so listing only dl.spec would leave the wide lists in two columns. */
  dl.spec, dl.spec.spec-wide { grid-template-columns: 1fr; }
  dl.spec dt { padding: 9px 0 0; border-bottom: 0; }
  dl.spec dd { padding-top: 2px; }

  /* --- tables become records --- */

  /* A seven-column grid is unreadable on a phone in either of the usual
     shapes: squeezed to the width it is a stack of one-word columns, and
     scrolled sideways the row being read walks off the screen. So a row is
     redrawn as a record — its subject on the first line, the remaining
     columns beneath it as labelled fields, its actions last. The headings the
     labels come from travel on the cells as data-label, so the table loses
     its shape but not one of its values. */
  table.grid, table.grid tbody, table.grid tr, table.grid td { display: block; }
  table.grid thead { display: none; }
  table.grid tr {
    display: flex; flex-wrap: wrap; align-items: flex-start;
    gap: 10px 16px; padding: 12px; border-bottom: 1px solid var(--line);
  }
  table.grid tbody tr:last-child { border-bottom: 0; }
  /* Hover is a pointer's way of tracking one row across a wide table. There
     is no wide table here, and on a touch screen the tint sticks after a tap
     on whatever was last touched. */
  table.grid tbody tr:hover { background: transparent; }
  table.grid td, table.grid td.num, table.grid td.mono {
    flex: 0 0 auto; max-width: 100%; padding: 0; border: 0;
    white-space: normal; overflow-wrap: break-word;
  }
  /* Right alignment was how a column of figures lined up with its heading.
     Neither the column nor the heading is there now. */
  table.grid td.right { text-align: left; }
  table.grid td[data-label]::before {
    content: attr(data-label);
    display: block; margin-bottom: 3px;
    font-family: var(--sans); font-size: 9.5px; font-weight: 600;
    letter-spacing: 0.11em; text-transform: uppercase; color: var(--faint);
  }
  /* The cell naming what the row is about takes the first line to itself —
     and so does a meter, which cannot read as a proportion at the width of
     the figure beside it. */
  table.grid td:has(.primary),
  table.grid td:has(.meter) { flex: 1 1 100%; min-width: 0; }
  /* …except where a thumbnail sits beside it, which is the one pairing worth
     keeping on a single line. More specific than the rule above, so it wins
     wherever both apply. */
  table.grid td.thumb-col { width: auto; }
  table.grid td.thumb-col + td { flex: 1 1 140px; }
  /* Buttons close the record on a line of their own, left-aligned under it
     rather than pushed against an edge that is no longer there. */
  table.grid td:has(.row-actions) { flex: 1 1 100%; }
  table.grid .row-actions { justify-content: flex-start; flex-wrap: wrap; gap: 8px; }
  /* break-all splits an address mid-word even when the line has room; at this
     width the addresses are the bulk of the text and that is unreadable. */
  table.grid .secondary { word-break: normal; overflow-wrap: break-word; }
  .empty { padding: 28px 14px; }

  /* --- toolbars --- */

  .toolbar { padding: 10px 12px; }
  .toolbar form { width: 100%; }
  /* The search box takes a line of its own and the selects share the next: at
     a fixed 260px it was wider than the screen it had to sit on. */
  .toolbar input[type=text] {
    flex: 1 1 100%; width: auto; min-width: 0;
    height: 38px; font-size: 16px; padding: 8px 10px;
  }
  .toolbar select {
    flex: 1 1 130px; width: auto;
    height: 38px; font-size: 16px; padding: 8px 30px 8px 10px;
  }
  .toolbar .btn { height: 38px; padding: 0 14px; font-size: 13px; }

  /* --- controls --- */

  /* 16px is the size below which iOS Safari zooms the page in on a focused
     field and leaves it zoomed — worse to be stuck in than the density this
     costs on the few pages that carry a form. */
  input[type=text], input[type=url], input[type=email], input[type=password],
  input[type=number], select, .text-search input[type=search] {
    font-size: 16px; padding: 9px 10px;
  }
  select { padding-right: 30px; }

  /* Everything that gets tapped grows to something a fingertip can land on
     without magnifying the page first. */
  .btn { padding: 9px 14px; }
  .btn-sm { padding: 7px 12px; font-size: 12.5px; }
  .text-search .btn { padding: 7px 12px; line-height: 1.2; }
  .pager { gap: 8px; }
  .pager .page-link { padding: 8px 14px; font-size: 12.5px; }
  .chip { padding: 8px 10px; }
  .chip-grid .chip { padding: 8px 2px; }
  .segmented .seg { padding: 9px 14px; }
  .modal-x { font-size: 24px; padding: 2px 8px; }
  .toast-close { font-size: 20px; padding: 2px 6px; }

  /* Find-in-text is the point of the panel it sits in, so on a head this
     narrow it takes a line of its own rather than being crushed against the
     heading and the byte count. */
  .text-search { flex: 1 1 100%; }

  /* --- live capture cards --- */

  .capture-head, .capture-meta { flex-wrap: wrap; }
  .capture-target { min-width: 0; overflow-wrap: break-word; }

  /* --- toasts --- */

  .toasts { right: 10px; left: 10px; bottom: 10px; max-width: none; }
}

/* On the narrowest handsets the wordmark itself has to give ground so the
   bell and the sign-out control keep their place on the bar. */
@media (max-width: 620px) {
  .masthead-user { display: none; }
}
@media (max-width: 400px) {
  .wordmark .name { font-size: 12.5px; letter-spacing: 0.07em; }
}
