/* The whole design, hand-written. There is no framework under this and no
   build step over it: what is here is what the browser gets.

   The palette and the shapes are the landing page's (landing/index.html) and
   the mail template's (Mail/MailTemplate.cs), because clicking the broken e on
   a navy page and arriving at a white Bootstrap form was the join showing.
   Those two files are the reference; if a colour here disagrees with them,
   they are right and this is stale.

   Committed to dark. Not a preference toggle and not a prefers-color-scheme
   pair: the landing page is navy for everyone, so a light app would mean the
   join reappears for exactly the people whose machine is set to light. The
   tokens below are the whole palette, so a second scheme later is an added
   block rather than a rewrite.

   Naming is semantic and flat — .field, .badge, .card. No utility grid, no
   responsive class families. Where Bootstrap wanted a dozen classes on an
   element to say "a row that spreads its children", there is one class here
   that says it. That is the point of the change, so resist adding .mb-3 back
   in: give the component the margin instead. */

/* ------------------------------------------------------------------ tokens */

:root {
  /* Straight from the landing page. --ink on --bg is 14.8:1, --muted on --bg
     is 6.4:1 — the latter passes AA as normal-size body text and not merely as
     decoration, which is what lets .muted carry real sentences. */
  --bg: #0b1f4b;
  --ink: #e8eefc;
  --muted: #8fa4cf;

  /* The mail template's dark card, and for the same reason it gives: a raised
     surface here is *lighter* than its surround, never darker. Black on black
     has no edge on an OLED screen. */
  --surface: #0f2757;
  --surface-raised: #143062;
  /* Inputs are sunk rather than raised — a field is a hole you type into, and
     on this background darker is the only direction that reads as one. */
  --sunk: #081840;

  --line: rgba(232, 238, 252, 0.16);
  --line-strong: rgba(232, 238, 252, 0.28);

  /* The mail template's dark-mode link. Deliberately not the same blue as any
     status colour below: a link and a state must not be confusable. */
  --accent: #a9c4ff;
  --accent-strong: #c3d6ff;

  /* Status. All four are lifted off their conventional values towards white,
     because the conventional ones are chosen against paper. Bootstrap's
     #28a745 and #dc3545 both sit at about 3:1 on this navy, which is under the
     line for anything carrying a word. */
  --good: #45cf9b;
  --bad: #ff7a8f;
  --warn: #f2c14e;
  --info: #6fb6ee;

  /* The day graph's fills are their own tokens and not the four above. A status
     colour there is *text on navy* and must be light; a bar fill is a solid
     block carrying white numerals and must be saturated enough to hold them.
     One token cannot be both, and making it try is what turned the bar pastel
     and its hour numbers unreadable. These are the old bar's colours, deepened
     so white would sit on them.

     --bar-work is the exception now, and knowingly. It was #1e9455, deepened
     for exactly that reason; it has been lifted to a green chosen on the phone,
     where the bar carries no text at all and the depth read as heavy and dull.
     White on it is 2.59:1, against #1e9455's 3.87:1 and Bootstrap's own ~3.1:1
     — so the claim that these beat what Bootstrap shipped is no longer true of
     this one. The numerals inside a work segment are the thing that pays.

     If that turns out to matter, the fix is not to re-darken the fill: give
     .progress-bar a text-shadow, or let the label colour vary per segment,
     which the "white on every fill" note below argues against for the track
     rather than for the labels. */
  --bar-work: #2eb858;
  --bar-rest: #2f7ded;
  --bar-missed-work: #d63644;
  --bar-missed-rest: #7c4dff;
  /* Time already spent. Bootstrap called it bg-secondary and drew it a solid
     grey, which was right: on a bar that drains, this is half the reading. */
  --bar-spent: #5c6a8a;

  --radius: 14px;
  --radius-sm: 9px;
  --radius-pill: 999px;

  --font: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
  --font-mono: SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
  /* The icon's face (apple/Tools/make-icon.swift), used only for the wordmark.
     The rest of the stack is what each platform has that is closest. */
  --font-brand: "Avenir Next", Avenir, "Century Gothic", Futura, "Trebuchet MS", Helvetica, Arial, sans-serif;

  /* One shadow, used sparingly. On a dark ground a shadow reads as haze rather
     than lift, so elevation is mostly carried by --surface being lighter. */
  --shadow: 0 18px 40px rgba(0, 0, 0, 0.38);
}

/* -------------------------------------------------------------------- base */

*,
*::before,
*::after { box-sizing: border-box; }

html {
  /* Bootstrap's template shipped 14px here and 16px above 768px, which makes
     the text smaller on the smaller screen. Gone: 16px everywhere, and the few
     places that want less say so in em. */
  font-size: 16px;
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  /* The footer sits at the bottom of a short page without being positioned
     there — main takes the slack. The old rule reserved 60px of body margin
     for an absolutely positioned footer, which is wrong the moment the footer
     wraps to two lines. */
  min-height: 100vh;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font);
  font-size: 1rem;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}

/* Every page's horizontal bounds, including the header's and the footer's, so
   the brand, the content and the version all sit on the same two edges. */
.wrap {
  width: 100%;
  max-width: 68rem;
  margin: 0 auto;
  padding: 0 1.25rem;
}

main {
  flex: 1 0 auto;
}

/* main.wrap, not main. Every page's <main> carries .wrap as well, and .wrap
   sets `padding` as a shorthand — so its 0 for the top beat a padding declared
   on a bare element selector, and every page's first element sat hard against
   the header's rule at 0px. A one-class selector (0,1,0) outranks a one-element
   one (0,0,1); it never reached the browser as anything but zero.

   Longhands here on purpose, so .wrap keeps owning the horizontal padding and
   the two rules stop competing for the same shorthand. */
main.wrap {
  /* 1rem, which is what the old navbar's mb-3 put here. The 2rem this had for
     one commit was not stacked with anything — it was simply twice the gap the
     page used to have, and read as a blank line too many under the header. */
  padding-top: 1rem;
  padding-bottom: 3.5rem;
}

/* -------------------------------------------------------------- typography */

h1, h2, h3, h4, h5, h6 {
  margin: 0 0 0.6em;
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: -0.015em;
}

h1 { font-size: 1.9rem; }
h2 { font-size: 1.5rem; }
h3 { font-size: 1.25rem; }
h4 { font-size: 1.1rem; }
h5 { font-size: 1rem; }
h6 { font-size: 0.9rem; }

p { margin: 0 0 1rem; }

/* A page is a stack of sections with air between them, so headings do not have
   to carry margin-top and fight the first-child case. */
.section { margin-top: 2.75rem; }

/* The Time page is one instrument read top to bottom — bar, then its controls,
   then the day's figures — so it wants its own tighter rhythm. The full
   .section gap is for unrelated blocks and here it just pulls the parts of one
   thing away from each other. */
.section-tight { margin-top: 1.1rem; }

/* Sits under a heading as its explanation. Wider than the form it introduces,
   because prose wants a longer measure than a field does. */
.lede {
  max-width: 46rem;
  color: var(--muted);
}

a {
  color: var(--accent);
  text-decoration-color: rgba(169, 196, 255, 0.4);
  text-underline-offset: 0.15em;
}

a:hover { color: var(--accent-strong); text-decoration-color: currentColor; }

code {
  padding: 0.12em 0.36em;
  border-radius: 5px;
  background: rgba(232, 238, 252, 0.09);
  color: var(--ink);
  font-family: var(--font-mono);
  font-size: 0.87em;
  /* In prose — a hint naming an env var, a path in a paragraph — a long
     unbroken token would otherwise run out of its column. Tables opt back out
     of this below, and they have to: see .table code. */
  overflow-wrap: anywhere;
}

hr {
  height: 0;
  margin: 2rem 0;
  border: 0;
  border-top: 1px solid var(--line);
}

strong { font-weight: 600; }

/* Keyboard focus, once, for everything. Without this the app inherits whatever
   the browser draws by default, which on this navy is somewhere between faint
   and invisible — and the version that ships is the one nobody tabbed through.
   :focus-visible rather than :focus, so a clicked button does not keep a ring. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 3px;
}

/* ------------------------------------------------------------------ header */

.site-header {
  border-bottom: 1px solid var(--line);
  background: rgba(9, 25, 62, 0.72);
  /* Sticky and translucent: the tables here are long, and the way back is
     worth keeping on screen. The blur is what stops text scrolling under it
     into an unreadable smear; without support it stays a flat wash, which is
     why the colour above already has most of the opacity it needs. */
  position: sticky;
  top: 0;
  z-index: 20;
  backdrop-filter: saturate(1.6) blur(12px);
  -webkit-backdrop-filter: saturate(1.6) blur(12px);
}

.site-header .wrap {
  display: flex;
  align-items: center;
  gap: 1rem;
  min-height: 3.65rem;
  flex-wrap: wrap;
}

/* The landing page's mark, at the size a header wants. The two spans butt
   together in the markup — whitespace between them renders as a gap. */
.brand {
  font-family: var(--font-brand);
  font-size: 1.16rem;
  font-weight: 800;
  letter-spacing: -0.015em;
  color: var(--ink);
  text-decoration: none;
  white-space: nowrap;
}

.brand:hover { color: var(--ink); }
.brand .tld { color: var(--muted); }

.nav {
  display: flex;
  align-items: center;
  gap: 0.15rem;
  flex-wrap: wrap;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Pushed to the right edge; the session links come after it in the source, so
   the primary nav and the account nav end up on opposite sides without either
   needing to know the other exists. */
.nav-primary { margin-left: auto; }

/* Who you are and the way out, held a little apart from the pages — they are a
   different kind of thing from the tabs and should not read as two more. */
.nav-account { gap: 0.35rem; padding-left: 0.5rem; }

/* Sign out is a <button> in a form and everything else is an <a>; they have to
   be indistinguishable, so the button's native chrome is reset here rather
   than the link being made to look like a button. */
.nav-link {
  display: inline-block;
  padding: 0.4rem 0.72rem;
  border: 0;
  border-radius: var(--radius-sm);
  background: none;
  color: var(--muted);
  font-family: inherit;
  font-size: 0.94rem;
  line-height: 1.55;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
}

.nav-link:hover {
  background: rgba(232, 238, 252, 0.08);
  color: var(--ink);
}

/* The account name is a link to the account page, but it is a label first.
   Dimmer and unpadded so it reads as "who you are" and not as another tab. */
.nav-user {
  color: var(--muted);
  font-size: 0.94rem;
  white-space: nowrap;
}

/* Sign out has to be a form so the POST carries its antiforgery token, but it
   must sit in the nav looking like everything else. display:contents makes the
   form generate no box at all, so the button lands directly in the flex row. */
.nav form { display: contents; }

/* A button sized for the header rather than for a page. Only Sign in uses it,
   and only when signed out — the one place the header offers an action. */
.btn-nav { min-height: 2.1rem; padding: 0.3rem 0.95rem; font-size: 0.9rem; }

/* On a phone the header wraps instead of collapsing behind a toggle. That is
   the trade for having no navbar component and no script: there are four links
   at most, and a burger that hides four links costs more than it saves. It does
   need to stop taking a third of the screen, though — so everything shrinks and
   the account list gives up its auto-margin, which would otherwise strand it on
   a line of its own. */
@media (max-width: 640px) {
  .site-header .wrap {
    gap: 0.15rem 0.5rem;
    padding-top: 0.5rem;
    padding-bottom: 0.5rem;
    min-height: 0;
  }

  .brand { font-size: 1.05rem; }
  /* Not width:100%. Forcing the pages onto their own line made the header three
     rows deep — brand, pages, account — on the screen with least room to spare.
     Letting the two lists share a line puts it back to two. */
  .nav-primary { margin-left: auto; }
  .nav-account { padding-left: 0; }
  .nav-link { padding: 0.25rem 0.5rem; font-size: 0.88rem; }

  /* Sticky costs more than it gives here: it is two rows tall on a screen this
     short, and it would sit on top of the content for the whole scroll. */
  .site-header { position: static; }
}

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

.site-footer {
  flex-shrink: 0;
  padding: 1.15rem 0;
  border-top: 1px solid var(--line);
  color: var(--muted);
  font-size: 0.86rem;
}

.site-footer .wrap {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
}

/* Tabular figures so the version does not change width as it changes value —
   it is the one thing on the page that is read by glancing at the same spot. */
.version {
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

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

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.45rem;
  /* Sized to be hit, not to be admired: 2.5rem tall is a comfortable target
     with a mouse and survives a thumb. The old shape here was a full pill,
     which on a short label draws a lozenge barely wider than the word and
     wastes the corners — a rounded rectangle is both bigger and quieter. */
  min-height: 2.5rem;
  padding: 0.5rem 1.15rem;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--ink);
  font-family: inherit;
  font-size: 0.94rem;
  font-weight: 500;
  line-height: 1.35;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  /* Colour only. Nothing here moves or scales on hover: half these buttons are
     beside a table row and the row must not twitch. */
  transition: background-color 0.14s ease, border-color 0.14s ease, color 0.14s ease;
}

.btn:disabled,
.btn[disabled] {
  opacity: 0.45;
  cursor: not-allowed;
}

/* The one filled button. Light on navy rather than another blue, because a
   blue button on a blue page has to shout to be seen at all. */
.btn-primary {
  background: var(--ink);
  border-color: var(--ink);
  color: #0b1f4b;
  font-weight: 600;
}

.btn-primary:hover:not(:disabled) { background: #ffffff; border-color: #ffffff; color: #0b1f4b; }

.btn-outline {
  border-color: var(--line-strong);
  color: var(--ink);
}

.btn-outline:hover:not(:disabled) { background: rgba(232, 238, 252, 0.09); color: var(--ink); }

.btn-danger-outline {
  border-color: rgba(255, 122, 143, 0.5);
  color: var(--bad);
}

.btn-danger-outline:hover:not(:disabled) { background: rgba(255, 122, 143, 0.14); color: var(--bad); }

/* Reads as a link, behaves as a button. Used for Cancel beside a Save, and for
   the in-table verbs, where a second bordered shape would be noise. */
.btn-quiet {
  padding-left: 0.5rem;
  padding-right: 0.5rem;
  color: var(--accent);
  font-weight: 400;
}

.btn-quiet:hover:not(:disabled) { color: var(--accent-strong); text-decoration: underline; }
.btn-quiet.is-danger { color: var(--bad); }

/* Inside a table cell: no padding, no pill, and sitting on the text baseline
   so it lines up with the plain links next to it. */
.btn-inline {
  min-height: 0;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
  color: var(--accent);
  font-family: inherit;
  font-size: inherit;
  font-weight: 400;
  line-height: inherit;
  vertical-align: baseline;
  cursor: pointer;
}

.btn-inline:hover:not(:disabled) { color: var(--accent-strong); text-decoration: underline; }
.btn-inline.is-danger { color: var(--bad); }

/* A row of buttons that belong together. */
.actions {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
}

/* A heading with its controls on the far side. Replaces the four-class
   d-flex/justify-content-between/align-items-center/mb-3 incantation, which
   appeared six times and said the same thing every time. */
.head-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  margin-bottom: 0.9rem;
}

.head-row > h1,
.head-row > h2,
.head-row > h3,
.head-row > h4 { margin-bottom: 0; }

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

/* Forms are capped rather than gridded. A text input wider than this is harder
   to use, not easier, and the cap holds at every width without a breakpoint —
   which is what the col-md-4 / col-md-6 / col-lg-7 spread was reaching for. */
.form-narrow { max-width: 34rem; }

.field { margin-bottom: 1.15rem; }

.field > label {
  display: block;
  margin-bottom: 0.34rem;
  font-size: 0.9rem;
  font-weight: 500;
  color: var(--ink);
}

input[type="text"],
input[type="email"],
input[type="password"],
input[type="number"],
input[type="url"],
input[type="search"],
input[type="tel"],
select,
textarea {
  display: block;
  width: 100%;
  padding: 0.55rem 0.75rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--sunk);
  color: var(--ink);
  font-family: inherit;
  font-size: 0.97rem;
  line-height: 1.45;
  transition: border-color 0.14s ease, background-color 0.14s ease;
}

textarea { resize: vertical; min-height: 5rem; }

input:hover:not(:disabled),
select:hover:not(:disabled),
textarea:hover:not(:disabled) { border-color: var(--line-strong); }

input:focus,
select:focus,
textarea:focus {
  outline: none;
  border-color: var(--accent);
  background: #061334;
  box-shadow: 0 0 0 3px rgba(169, 196, 255, 0.16);
}

input:disabled,
select:disabled,
textarea:disabled { opacity: 0.55; cursor: not-allowed; }

::placeholder { color: rgba(143, 164, 207, 0.65); }

/* The native arrow is drawn for a light control and disappears on this
   background, so it is replaced. The SVG is inline as a data URI rather than a
   file, because a stylesheet that needs a second request to render a dropdown
   arrow is a stylesheet that flashes a blank one. */
select {
  appearance: none;
  -webkit-appearance: none;
  padding-right: 2.1rem;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath fill='none' stroke='%238fa4cf' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round' d='M1 1.5 6 6.5 11 1.5'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 0.8rem center;
}

/* The option list is drawn by the OS, which does not inherit any of the above.
   Left dark on platforms that honour it and native everywhere else — an
   unreadable dropdown is worse than an unstyled one. */
select option { background: var(--surface); color: var(--ink); }

/* The note under a field. Not .small + .text-muted: it is one thing. */
.hint {
  display: block;
  margin-top: 0.35rem;
  color: var(--muted);
  font-size: 0.845rem;
  line-height: 1.5;
}

.hint code { font-size: 0.95em; }

/* Checkboxes and radios. accent-color does the whole job on every current
   browser, so the box stays a real native control — which is the one that
   already works with a keyboard, a screen reader and a hardware switch. */
.check {
  display: flex;
  align-items: flex-start;
  gap: 0.55rem;
  margin-bottom: 1.15rem;
}

.check input[type="checkbox"],
.check input[type="radio"] {
  width: 1.05rem;
  height: 1.05rem;
  flex: none;
  /* The label's first line is 1.55 lh at ~0.95rem; this centres the box on it
     rather than on the label's whole height, which matters once a label wraps. */
  margin-top: 0.24rem;
  padding: 0;
  accent-color: var(--accent);
  cursor: pointer;
}

.check label {
  margin: 0;
  font-size: 0.95rem;
  font-weight: 400;
  cursor: pointer;
}

/* The hint belongs to the whole check, not to the box, so it clears the gutter
   and lines up under the label. */
.check .hint { margin-left: 0; }

.check-group { margin-bottom: 1.15rem; }
.check-group .check { margin-bottom: 0.55rem; }

/* Radios offered side by side, wrapping to a column when there is no room —
   the three transfer modes, which are read as a set. */
.check-row {
  display: flex;
  gap: 1.4rem;
  flex-wrap: wrap;
}

.check-row .check { margin-bottom: 0; }

/* Two fields that are one idea — the read and write operation limits. */
.field-pair {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  gap: 0 1rem;
}

/* Validation. jQuery Validation writes these class names itself, so they are
   not ours to rename — .input-validation-error lands on the input and
   .field-validation-error on the span beside it. Bootstrap styled neither, so
   until now a rejected field looked exactly like an accepted one. */
.field-error,
.field-validation-error {
  display: block;
  margin-top: 0.32rem;
  color: var(--bad);
  font-size: 0.86rem;
}

input.input-validation-error,
select.input-validation-error,
textarea.input-validation-error {
  border-color: var(--bad);
  background: rgba(255, 122, 143, 0.07);
}

input.input-validation-error:focus,
select.input-validation-error:focus,
textarea.input-validation-error:focus {
  border-color: var(--bad);
  box-shadow: 0 0 0 3px rgba(255, 122, 143, 0.18);
}

/* The summary above a form. Empty until something fails — and it is rendered
   empty, so it must collapse rather than leave a gap. :empty does not do it:
   the tag helper emits an empty <ul> inside, which is a child. */
.validation-summary-valid { display: none; }

.validation-summary-errors {
  margin-bottom: 1.15rem;
  padding: 0.8rem 1rem;
  border: 1px solid rgba(255, 122, 143, 0.4);
  border-radius: var(--radius-sm);
  background: rgba(255, 122, 143, 0.09);
  color: var(--bad);
  font-size: 0.9rem;
}

.validation-summary-errors ul { margin: 0; padding-left: 1.1rem; }

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

/* Tables go edge to edge and scroll inside their own box on a narrow screen.
   Without the wrapper a wide table widens the page itself, and then every
   other line on it is off-centre. */
.table-scroll {
  overflow-x: auto;
  /* The focus ring of an in-cell button would be clipped by the scroll box;
     the padding is the room it needs. */
  margin: 0 -0.25rem;
  padding: 0 0.25rem;
}

.table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.93rem;
}

.table th,
.table td {
  padding: 0.58rem 0.7rem;
  text-align: left;
  vertical-align: top;
  border-bottom: 1px solid var(--line);
  /* Data, not prose: these cells hold a role, a type, a count, a name. Left to
     wrap they turn "Warm storage" into two lines and the row into a paragraph,
     and the eleven-column providers table does it to every column at once.
     Scrolling a wide table sideways is a thing people know how to do; reading a
     ragged one is not. .table-plain opts back out for the cells that really do
     hold sentences. */
  white-space: nowrap;
}

.table th {
  color: var(--muted);
  font-weight: 500;
  font-size: 0.82rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  white-space: nowrap;
}

.table thead th { border-bottom-color: var(--line-strong); }

.table tbody tr:last-child td { border-bottom: 0; }

.table tbody tr:hover td { background: rgba(232, 238, 252, 0.035); }

/* First and last cells align with the text above and below the table rather
   than being inset by the cell padding. */
.table th:first-child,
.table td:first-child { padding-left: 0; }

.table th:last-child,
.table td:last-child { padding-right: 0; }

/* Paths, endpoints and bucket names stay on one line and the table scrolls if
   that costs width. They inherit overflow-wrap:anywhere from `code` otherwise,
   and in a table that is not a wrapping rule but a sizing one: it tells the
   layout algorithm the column can shrink to a single character, so the browser
   squeezes it to a ribbon and hyphen-less-breaks a path down four lines while
   half the viewport sits empty beside it. Not a narrow-screen effect — it
   happened at 1280 with room to spare. */
.table code {
  overflow-wrap: normal;
  white-space: nowrap;
}

/* A row for something switched off or retired. Dimmed as a whole, so it stays
   readable but stops competing with the live rows. */
.table tr.is-off td { color: var(--muted); }

/* Numbers right-aligned and tabular, so a column of them can be compared by
   looking down it. */
.table .num {
  text-align: right;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* The verbs at the end of a row. */
.table .row-actions {
  text-align: right;
  white-space: nowrap;
}

.table .row-actions > * + * { margin-left: 0.75rem; }

/* Each verb that changes something is its own form, so it can POST with a
   token. They have to sit on one line beside the plain links. */
.table .row-actions form { display: inline-block; }

/* A table used as a reference list rather than as data — the failure-modes
   table in the mail card. No uppercase heads, the left column is the key, and
   the right one is a sentence, so it wraps like one. */
.table-plain th { text-transform: none; letter-spacing: 0; font-size: 0.86rem; }
.table-plain td { white-space: normal; }
.table-plain code { white-space: normal; overflow-wrap: anywhere; }

/* ------------------------------------------------------------------- cards */

.card {
  padding: 1.25rem 1.35rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
}

.card > :last-child { margin-bottom: 0; }

.card-title {
  margin-bottom: 0.5rem;
  font-size: 0.95rem;
  font-weight: 600;
}

/* Two panels side by side, collapsing to one column when there is no room for
   two. Replaces .row + .col-*, and unlike those it has no breakpoint of its
   own to keep in step with anything. */
.split {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
  gap: 2rem;
  align-items: start;
}

/* ------------------------------------------------------------------ badges */

.badge {
  display: inline-block;
  padding: 0.14em 0.6em;
  border: 1px solid transparent;
  border-radius: var(--radius-pill);
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  line-height: 1.5;
  vertical-align: middle;
  white-space: nowrap;
}

/* Tinted rather than filled: a filled badge beside a name reads as a button.
   The border is what keeps it a shape at these sizes — a wash alone on navy is
   too close to nothing. */
.badge-good { background: rgba(69, 207, 155, 0.15); border-color: rgba(69, 207, 155, 0.4); color: var(--good); }
.badge-bad { background: rgba(255, 122, 143, 0.14); border-color: rgba(255, 122, 143, 0.4); color: var(--bad); }
.badge-warn { background: rgba(242, 193, 78, 0.15); border-color: rgba(242, 193, 78, 0.4); color: var(--warn); }
.badge-accent { background: rgba(169, 196, 255, 0.16); border-color: rgba(169, 196, 255, 0.42); color: var(--accent); }
.badge-quiet { background: rgba(232, 238, 252, 0.09); border-color: var(--line); color: var(--muted); }

/* A badge sitting inside a heading, saying something about the page rather
   than about a row — the live-events indicator on Host settings. It must not
   inherit the heading's size, and it sits on the cap height rather than the
   baseline so it does not drag the line down. */
.status-badge {
  font-size: 0.7rem;
  font-weight: 500;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  vertical-align: middle;
  position: relative;
  top: -0.1em;
}

/* ------------------------------------------------------------------ alerts */

.alert {
  margin-bottom: 1.4rem;
  padding: 0.85rem 1.1rem;
  border: 1px solid var(--line);
  border-left-width: 3px;
  border-radius: var(--radius-sm);
  background: var(--surface);
  font-size: 0.95rem;
}

.alert-good { border-left-color: var(--good); background: rgba(69, 207, 155, 0.08); }
.alert-bad { border-left-color: var(--bad); background: rgba(255, 122, 143, 0.08); }

/* ------------------------------------------------------------- choice list */

/* A list of options, each a whole clickable block with a title and an
   explanation — picking a provider role, then a provider type. */
.choices {
  display: grid;
  gap: 0.6rem;
  max-width: 40rem;
}

.choice {
  display: block;
  padding: 0.9rem 1.1rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--ink);
  text-decoration: none;
  transition: border-color 0.14s ease, background-color 0.14s ease;
}

.choice:hover {
  border-color: var(--line-strong);
  background: var(--surface-raised);
  color: var(--ink);
}

.choice strong { display: block; font-weight: 600; }

.choice-note {
  margin-top: 0.15rem;
  color: var(--muted);
  font-size: 0.875rem;
}

/* ------------------------------------------------------------------ dialog */

/* Native <dialog>. This replaced Bootstrap's modal, which was the only
   component here that needed its JavaScript at all — the whole 3.5 MB was
   being carried for one window. showModal() brings the focus trap, the
   inert background, the Escape key and the top layer with it, none of which
   is worth reimplementing. */
dialog.modal {
  width: min(46rem, calc(100vw - 2rem));
  max-height: calc(100vh - 4rem);
  padding: 0;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--ink);
  box-shadow: var(--shadow);
  overflow: hidden;
}

dialog.modal::backdrop {
  background: rgba(4, 12, 32, 0.66);
  backdrop-filter: blur(3px);
  -webkit-backdrop-filter: blur(3px);
}

/* The body scrolls, the header and footer do not — with a live log inside,
   a dialog that scrolls as a whole puts the Start button off screen. */
.modal-inner {
  display: flex;
  flex-direction: column;
  max-height: calc(100vh - 4rem);
}

.modal-head,
.modal-foot {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex: none;
  padding: 1rem 1.35rem;
}

.modal-head { border-bottom: 1px solid var(--line); }
.modal-foot { border-top: 1px solid var(--line); flex-wrap: wrap; }

.modal-head h2 { margin: 0; font-size: 1.1rem; }

.modal-body {
  flex: 1 1 auto;
  padding: 1.25rem 1.35rem;
  overflow-y: auto;
}

/* The × . A button because it does something, sized to be hittable on a phone
   rather than to match the glyph. */
.modal-close {
  margin-left: auto;
  padding: 0.15rem 0.5rem;
  border: 0;
  border-radius: var(--radius-sm);
  background: none;
  color: var(--muted);
  font-size: 1.4rem;
  line-height: 1;
  cursor: pointer;
}

.modal-close:hover { background: rgba(232, 238, 252, 0.09); color: var(--ink); }

/* --------------------------------------------------------- transfer window */

/* The live log of a move/copy/prune run. A div rather than a pre so each line
   can be an element with its tag coloured; monospace and pre-wrap do the rest. */
.log {
  height: 18rem;
  padding: 0.6rem 0.75rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--sunk);
  font-family: var(--font-mono);
  font-size: 0.82rem;
  line-height: 1.5;
  white-space: pre-wrap;
  overflow: auto;
}

/* Written by host-settings.ts, one span per part of a line, so these names are
   a contract with that file. They were Bootstrap's text utilities; scoping them
   to the log is what stops the next person reading .text-warning as something
   general and using it on a form. */
.log-time { color: var(--muted); }
.log-tag { font-weight: 700; }
.log-note { font-weight: 700; }
.log-ok { color: var(--good); }
.log-skip { color: var(--muted); }
.log-kept { color: var(--warn); }
.log-bad { color: var(--bad); }

.warning { color: var(--warn); }

.transfer-status {
  display: flex;
  align-items: baseline;
  gap: 1rem;
  margin: 1.1rem 0 0.5rem;
}

.transfer-status strong { margin-right: auto; }

/* ---------------------------------------------------- time balance: graph */

/* Built by wwwroot/js/graph.ts, not by a view, so these names are a contract
   with that file. The Bootstrap colour classes it used to add alongside them
   (bg-success, bg-danger, bg-secondary) are gone — the specific class carries
   the colour now, which is one place to change it instead of two. */
/* Read the arithmetic in graph.ts before touching any of this: every segment is
   time *remaining*, not time spent. The bar starts full and drains. That makes
   the track the spent part of the day — a real quantity, and by the evening the
   larger one — so it is a surface in its own right and not the absence of one.
   Bootstrap had this right by accident: the markup said bg-secondary, a solid
   mid grey. A faint wash here quietly deleted half of what the bar says. */
/* Read the arithmetic in graph.ts before touching any of this: every segment is
   time *remaining*, not time spent. The bar starts full and drains, so the
   track is the part of the day already gone — a real quantity, and by the
   evening the larger one.

   The geometry is not free choice either. .hour-scale below is 20px pulled up
   18px, which lands its numerals centred on a bar exactly this tall. Changing
   this height without redoing that offset is what put the numbers on the floor
   of the bar; the radius is small for the same kind of reason, because a large
   one eats the final hour cell and leaves a blob. */
.progress {
  display: flex;
  width: 100%;
  height: 1rem;
  border-radius: 4px;
  background: var(--bar-spent);
  overflow: hidden;
}

.progress-bar {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100%;
  /* White on every fill and on the spent track alike — one colour that works
     the whole length of the bar, which is the only kind that can, since the
     proportions swap as the day runs. */
  color: #fff;
  font-size: 0.75rem;
  font-weight: 600;
  /* Widths are set inline as it runs; the transition is what turns each poll
     into movement rather than a jump. */
  transition: width 0.4s ease;
}

.progress-bar-work { background-color: var(--bar-work); }
.progress-bar-rest { background-color: var(--bar-rest); }
.progress-bar-missed-work { background-color: var(--bar-missed-work); }

/* Rest that can no longer be taken because the work went long — the mirror of
   .progress-bar-missed-work in red. Violet rather than another red: both are
   losses, but one is work you will not get to and the other is rest you will
   not get, and reading which is which at a glance is the whole point of the
   bar. */
.progress-bar-missed-rest { background-color: var(--bar-missed-rest); }

/* A loss that is settled is struck out: heavy slashes leaning against the
   brrr, dark where the brrr is light, and sparse where it is dense — 3-in-8 is
   37% ink against its 50%. Density is what separates "already gone" from
   "going right now"; the opposite lean only says the two are not the same
   gesture.

   It is a second channel, not decoration. Measured with the Vienot 1999
   dichromat projection, the work-left | work-missed boundary is dE2000 69 to a
   normal eye and 7.2 to a deuteranope — about one man in sixteen has been
   reading a bar whose most important join was one flat block. No rehue fixes
   that; a pattern is the one cue colour blindness cannot erase. */
.progress-bar-struck {
  background-image: repeating-linear-gradient(
    -45deg,
    rgba(0, 0, 0, 0.30) 0,
    rgba(0, 0, 0, 0.30) 3px,
    transparent 3px,
    transparent 8px);
}

/* A loss that is filling right now marches instead, in the brrr's own geometry
   but dark: a pool draining adds light, a loss filling adds dark. Same gesture,
   opposite sign, because it is the same event — something changing now —
   happening to a quantity you do not have rather than one you do.

   Which loss is filling needs no new state: working eats rest, so rest-lost
   grows while the green burns; resting misses work, so missed-work grows while
   the blue does. graph.ts's setWorkStyle picks both from the same flag. */
.progress-bar-striped-dark {
  background-image: repeating-linear-gradient(
    45deg,
    rgba(0, 0, 0, 0.25) 0,
    rgba(0, 0, 0, 0.25) 0.5rem,
    transparent 0.5rem,
    transparent 1rem);
  background-size: 1.415rem 1.415rem;
}

/* Bootstrap's stripes, reimplemented. Marks the segment that is running now. */
.progress-bar-striped {
  background-image: repeating-linear-gradient(
    45deg,
    rgba(255, 255, 255, 0.17) 0,
    rgba(255, 255, 255, 0.17) 0.5rem,
    transparent 0.5rem,
    transparent 1rem);
  background-size: 1.415rem 1.415rem;
}

.progress-bar-animated { animation: progress-stripes 1s linear infinite; }

@keyframes progress-stripes {
  from { background-position: 1.415rem 0; }
  to { background-position: 0 0; }
}

/* The start/stop button is the bar's twin — index.ts toggles these two as the
   clock runs — so it takes the bar's own two colours and its white label.

   Both states are striped and animated, as the original was. The stripes say
   the clock is live, and it is live in both: you are accumulating work or
   accumulating rest, and time is going either way. They also tie the button to
   the bar, where the striped segment is the pool currently draining — which is
   the very mode this button names. Restricting them to is-working, as this
   briefly did, said the resting clock was stopped. */
.btn.is-working,
.btn.is-resting {
  color: #fff;
  font-weight: 600;
  background-image: repeating-linear-gradient(
    45deg,
    rgba(255, 255, 255, 0.17) 0,
    rgba(255, 255, 255, 0.17) 0.5rem,
    transparent 0.5rem,
    transparent 1rem);
  background-size: 1.415rem 1.415rem;
  animation: progress-stripes 1s linear infinite;
}

.btn.is-working { background-color: var(--bar-work); border-color: var(--bar-work); }
.btn.is-working:hover:not(:disabled) { background-color: #34d066; border-color: #34d066; }

.btn.is-resting { background-color: var(--bar-rest); border-color: var(--bar-rest); }
.btn.is-resting:hover:not(:disabled) { background-color: #4a8ef0; border-color: #4a8ef0; }

/* Rows in the day-details card that are the totals rather than the parts. */
.is-bold { font-weight: 700; }

/* A spacer element, kept because graph.ts appends one to reserve the space a
   username would have taken when it is not showing one. */
.graph-spacer { height: 0.5rem; }

/* Two controls that must not be adjacent. One is pressed many times a day and
   the other throws the day away and starts another; side by side with a 0.5rem
   gap they are one mis-aim apart. Opposite ends, as before. */
.actions-split { justify-content: space-between; }

/* The hour ticks, laid over the bar. The scale is exactly the bar's height and
   is pulled up by exactly that, so a separator spans the bar and stops.

   It used to be 20px pulled up 18px, which left every tick 2px proud at the top
   and 2px at the bottom. That was free on a white page — white overhang on
   white — and on this one it is twelve pairs of little white nubs standing off
   the bar. Measured, not guessed: bar 16px at y=71.4, scale 20px at y=69.4.

   Centring is done by the flexbox rather than by the old vertical-align:top and
   a small-screen padding nudge, both of which were aiming the digits at the
   middle of a box that did not line up with the bar in the first place.

   White on white separators, as before: the bar is a run of saturated fills and
   a solid spent track, and white is the one colour legible on all of them all
   day, while a separator has to be nothing's colour so it reads as a cut rather
   than as another segment.

   The 1px below is only the value before script runs. site.ts's refresh1px(),
   called from graph.ts and again on every resize, replaces it with
   1/devicePixelRatio — one *physical* pixel, so the cut stays hairline on a
   retina screen instead of doubling to two. Verified at dpr 2: the inline width
   lands at 0.51px and computes to 0.5px. Renaming .hour-scale-element would
   silently break that, since the script finds these by class. */
.hour-scale {
  display: flex;
  width: 100%;
  height: 1rem;
  margin-top: -1rem;
  color: #fff;
}

.hour-scale-element {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: auto;
  border-right: 1px solid #fff;
}

/* The last cut would land on the bar's rounded end and show as a nick in it. */
.hour-scale-element:last-child { border-right: 0; }

.hour-scale-element span {
  font-size: 0.75rem;
  line-height: 1;
}

.graph-username { font-size: 0.8em; color: var(--muted); }

/* The day's detail card, floated beside the bar it describes. */
#dayDetailsContainer .card {
  float: right;
  max-width: 22rem;
  margin-left: 0.75rem;
  text-align: right;
}

#dayDetailsContainer .card-header {
  margin: -1.25rem -1.35rem 0.9rem;
  padding: 0.7rem 1.35rem;
  border-bottom: 1px solid var(--line);
  color: var(--muted);
  font-size: 0.85rem;
}

#dayDetailsContainer .card-body p { margin: 0 0 0.35rem; }

/* --------------------------------------------------------------- utilities */

/* Kept deliberately short. Everything above is a component that owns its own
   spacing; these are the handful of things that genuinely are one-off. */

/* A list of links with nothing else to say about them. Unbulleted because the
   marker adds nothing when every row is one word. */
.plain-list {
  margin: 0;
  padding: 0;
  list-style: none;
}

.plain-list li + li { margin-top: 0.4rem; }

.muted { color: var(--muted); }
.small { font-size: 0.875rem; }
.nowrap { white-space: nowrap; }
/* For elements shown and hidden by a *class* — host-settings.ts toggles this on
   the running badge. Not for anything jQuery's .show()/.hide()/.toggle()
   manages: those write an inline display, the !important here beats it, and the
   element is then hidden forever with nothing failing anywhere. The Time page's
   graph and work button are toggled that way and so are hidden inline instead.
   The !important stays because a utility that can lose is not one. */
.hidden { display: none !important; }
.danger { color: var(--bad); }

.truncate {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Off-screen until focused. The way in for a keyboard, past the nav. */
.skip-link {
  position: absolute;
  top: 0.6rem;
  left: 0.6rem;
  z-index: 50;
  padding: 0.55rem 1rem;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--ink);
  font-size: 0.9rem;
  text-decoration: none;
  transform: translateY(-300%);
}

.skip-link:focus { transform: none; }

/* Nobody asked to be made ill by a progress bar. Same branch the landing page
   takes, for the same reason. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
}
