/* src/web/static/app.css
   One stylesheet, served from this origin. No CDN, no webfont, nothing that
   a CSP has to make an exception for.

   Everything is a token, even though only one palette is defined: a later
   component still reads --bg / --text / etc rather than a literal colour. */

:root {
  color-scheme: light;

  /* Neutrals carry a trace of warmth. A pure grey UI reads clinical, and this
     is a tool somebody opens between serving customers. */
  --bg:            #f7f7f6;
  --surface:       #ffffff;
  --surface-2:     #f2f2f0;
  --surface-3:     #e9e9e6;
  --border:        #e3e3df;
  --border-strong: #c9c9c3;

  --text:          #1a1a19;
  --text-muted:    #5f5f5a;
  --text-faint:    #8c8c85;

  /* One accent, used only for the current page and the primary action. When
     everything is emphasised nothing is. */
  --accent:        #2f5bd8;
  --accent-hover:  #2549ba;
  --accent-soft:   #eaf0ff;
  --accent-text:   #ffffff;

  --ok:            #12704a;
  --ok-soft:       #e2f3ea;
  --warn:          #8a5300;
  --warn-soft:     #fdf0d9;
  --danger:        #b3261e;
  --danger-soft:   #fceceb;

  --radius:        12px;
  --radius-sm:     8px;
  --gap:           16px;

  /* Nothing tappable is smaller than this. A mis-tap on a page that changes
     someone's pay is worse than a page that scrolls a little more. */
  --tap:           46px;

  --shadow:    0 1px 2px rgba(20, 20, 18, .05);
  --shadow-lg: 0 4px 16px rgba(20, 20, 18, .10), 0 1px 3px rgba(20, 20, 18, .06);

  --mono: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  --sans: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", sans-serif;
}

/* The inline sprite sheet of <symbol> defs (partials/icons.html) must never
   paint. It used to carry style="display:none", but the CSP is style-src
   'self' with no unsafe-inline, so that attribute was silently dropped and
   the sprite rendered as a ~150px visible block at the top of every page,
   pushing the whole layout down. A class reaches the same rule through the
   stylesheet, which the CSP allows. */
.sprite-defs { display: none; }

/* ------------------------------------------------------------------- icons */
/* One weight, one size, aligned to the text it sits beside. Emoji were doing
   this job and rendered differently on every device — and at different sizes,
   so nothing lined up. */

.ico {
  width: 20px; height: 20px;
  flex: 0 0 auto;
  vertical-align: -.22em;
  stroke-width: 1.75;
}
.ico-sm { width: 16px; height: 16px; vertical-align: -.18em; }
.ico-lg { width: 26px; height: 26px; }
.ico-xl { width: 40px; height: 40px; stroke-width: 1.4; }

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font: 16px/1.62 var(--sans);
  -webkit-text-size-adjust: 100%;
}

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

/* Visible focus everywhere. Keyboard navigation is not optional. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

/* ------------------------------------------------------------------ layout */
/* A left sidebar on any reasonable screen, an off-canvas drawer on a phone.
   Seven destinations do not fit across the top of a handset, and a menu that
   wraps onto two lines is a menu people stop reading. */

.shell { display: flex; min-height: 100vh; }

.sidebar {
  flex: 0 0 232px;
  width: 232px;
  background: var(--surface);
  border-right: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 18px 12px 12px;
  position: sticky;
  top: 0;
  height: 100vh;
  overflow-y: auto;
  z-index: 30;
}

.brand {
  display: flex; align-items: center; gap: 9px;
  font-weight: 700;
  letter-spacing: -.015em;
  color: var(--text);
  font-size: 16.5px;
  white-space: nowrap;
}
.brand .ico { color: var(--accent); }
.brand:hover { text-decoration: none; }
.sidebar-brand { padding: 4px 10px 12px; }

.dev-tag {
  background: var(--warn-soft); color: var(--warn);
  border-radius: var(--radius-sm); padding: 7px 10px; margin-bottom: 6px;
  font-size: 12px; font-weight: 650; text-align: center; line-height: 1.35;
}

nav.main { display: flex; flex-direction: column; gap: 2px; }

nav.main a {
  display: flex;
  align-items: center;
  gap: 11px;
  min-height: var(--tap);
  padding: 10px 12px;
  border-radius: var(--radius-sm);
  color: var(--text-muted);
  font-weight: 550;
  font-size: 15px;
}
nav.main a .ico { color: var(--text-faint); transition: color .12s; }
nav.main a:hover .ico { color: var(--text-muted); }
nav.main a[aria-current="page"] .ico { color: var(--accent); }
nav.main a:hover { background: var(--surface-2); color: var(--text); text-decoration: none; }
nav.main a[aria-current="page"] { background: var(--accent-soft); color: var(--accent); font-weight: 650; }

/* Pinned to the bottom, because settings is where you go last, not first. */
.sidebar-foot { margin-top: auto; padding-top: 10px; border-top: 1px solid var(--border); }

.who-link {
  display: flex; align-items: center; gap: 11px;
  padding: 10px 12px; border-radius: var(--radius-sm);
  color: var(--text); min-height: var(--tap);
}
.who-link:hover { background: var(--surface-2); text-decoration: none; }
.who-link.is-current { background: var(--accent-soft); }
.who-link .ico { color: var(--text-faint); }
.who-link span { display: block; min-width: 0; }
.who-name { font-weight: 650; font-size: 14px; overflow: hidden;
            text-overflow: ellipsis; white-space: nowrap; }
.who-role { font-size: 12px; color: var(--text-muted); }

.mainpane { flex: 1 1 auto; min-width: 0; }

/* The phone header. Hidden wherever the sidebar is permanent. */
.mobilebar { display: none; }
.menu-btn {
  min-height: 42px; min-width: 42px; padding: 0 12px;
  font-size: 20px; line-height: 1; border-radius: var(--radius-sm);
}
.scrim { display: none; }

main {
  max-width: 1180px;
  margin: 0 auto;
  padding: 24px 22px 72px;
}
main.anon { max-width: 640px; }

.page-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--gap);
  flex-wrap: wrap;
  margin-bottom: 18px;
}

h1 { font-size: 22px; margin: 0; letter-spacing: -.02em; }
h2 { font-size: 15.5px; margin: 0 0 12px; letter-spacing: -.01em;
     display: flex; align-items: center; gap: 9px; }
h2 .ico { color: var(--text-faint); }
h3 { font-size: 13px; margin: 0 0 8px; color: var(--text-muted);
     text-transform: uppercase; letter-spacing: .06em; font-weight: 600; }

.sub { color: var(--text-muted); font-size: 13px; margin: 2px 0 0; }

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

.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 18px;
  box-shadow: var(--shadow);
  margin-bottom: var(--gap);
}
.card.flush { padding: 0; overflow: hidden; }
.card > h2 { padding: 0; }
.card.flush > h2 { padding: 16px 18px 0; }

.grid { display: grid; gap: var(--gap); }
.grid.two   { grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); }
.grid.three { grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); }
.grid.stats { grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); }

/* ------------------------------------------------------------------- stats */

.stat {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 14px 16px;
}
.stat .label {
  font-size: 11px; text-transform: uppercase; letter-spacing: .07em;
  color: var(--text-faint); font-weight: 600;
}
.stat .value {
  font-size: 25px; font-weight: 620; letter-spacing: -.02em;
  margin-top: 5px; font-variant-numeric: tabular-nums;
}
.stat .value.small { font-size: 19px; }
.stat .note { font-size: 12px; color: var(--text-muted); margin-top: 3px; }

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

.table-wrap { overflow-x: auto; }

table { width: 100%; border-collapse: collapse; font-size: 14px; }

th {
  text-align: left;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--text-faint);
  font-weight: 600;
  padding: 10px 14px;
  border-bottom: 1px solid var(--border);
  background: var(--surface-2);
  white-space: nowrap;
}

td {
  padding: 11px 14px;
  border-bottom: 1px solid var(--border);
  vertical-align: middle;
}
tbody tr:last-child td { border-bottom: 0; }
tbody tr:hover { background: var(--surface-2); }

td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; }
td.mono { font-family: var(--mono); font-size: 13px; }
tfoot td { font-weight: 620; background: var(--surface-2); }

/* ------------------------------------------------------------------- chips */

.chip {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 100px;
  font-size: 11.5px;
  font-weight: 600;
  line-height: 1.6;
  white-space: nowrap;
}
.chip.ok      { background: var(--ok-soft);     color: var(--ok); }
.chip.warn    { background: var(--warn-soft);   color: var(--warn); }
.chip.danger  { background: var(--danger-soft); color: var(--danger); }
.chip.muted   { background: var(--surface-2);   color: var(--text-muted); }
.chip.accent  { background: var(--accent-soft); color: var(--accent); }

.dot {
  display: inline-block; width: 7px; height: 7px; border-radius: 50%;
  background: var(--ok); margin-right: 6px; vertical-align: middle;
}

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

label { display: block; font-size: 14px; font-weight: 600;
        color: var(--text); margin-bottom: 6px; }

input[type="text"], input[type="number"], input[type="date"],
input[type="time"], input[type="month"], input[type="search"],
select, textarea {
  width: 100%;
  min-height: var(--tap);
  padding: 11px 13px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--text);
  font: inherit;
  font-size: 16px;   /* 16px exactly: anything less and iOS zooms on focus */
  /* `body` sets line-height:1.62 for prose. A <select> ignores an inherited
     line-height for its own box height, but Chromium's native date/time/month
     picker does not — it was inflating those three to ~52px next to a 47px
     select and text input, staggering every filter row that mixes them. */
  line-height: 1.2;
}
input:focus, select:focus, textarea:focus {
  border-color: var(--accent);
  outline: 2px solid var(--accent-soft);
  outline-offset: 0;
}
textarea { resize: vertical; min-height: 62px; }

.field { margin-bottom: 13px; }
.field .hint { font-size: 13.5px; color: var(--text-muted); margin-top: 5px;
                font-weight: 400; line-height: 1.5; }

.row { display: flex; gap: 10px; flex-wrap: wrap; align-items: flex-end; }
.row > * { flex: 1; min-width: 120px; }
.row > .narrow { flex: 0 0 auto; min-width: 0; }

.check { display: flex; align-items: center; gap: 8px; }
.check input { width: auto; }
.check label { margin: 0; }

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

button, .btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  min-height: var(--tap);
  padding: 11px 18px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-strong);
  background: var(--surface);
  color: var(--text);
  font: inherit;
  font-size: 15px;
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
}
button:hover, .btn:hover { background: var(--surface-2); text-decoration: none; }

.btn-primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-text);
}
.btn-primary:hover { background: var(--accent); filter: brightness(1.08); }

/* Destructive actions are deliberately plain until hovered: they should never
   be the thing your eye lands on first. */
.btn-danger { color: var(--danger); border-color: var(--border-strong); }
.btn-danger:hover { background: var(--danger-soft); border-color: var(--danger); }

.btn-sm { min-height: 38px; padding: 7px 12px; font-size: 14px; gap: 6px; }
.btn .ico, button .ico { opacity: .9; }
.btn-link { border: 0; background: none; color: var(--accent); padding: 0; }
.btn-link:hover { background: none; text-decoration: underline; }

.actions { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }

/* ------------------------------------------------------------------ flashes */

.flash {
  padding: 13px 16px;
  border-radius: var(--radius);
  margin-bottom: var(--gap);
  font-size: 14px;
  border: 1px solid transparent;
  display: flex; gap: 9px; align-items: flex-start;
}
.flash .ico { margin-top: 1px; }
.flash.ok  { background: var(--ok-soft);     color: var(--ok);     border-color: var(--ok); }
.flash.err { background: var(--danger-soft); color: var(--danger); border-color: var(--danger); }
.flash.warn{ background: var(--warn-soft);   color: var(--warn);   border-color: var(--warn); }

/* ------------------------------------------------------------- empty states */

.empty { text-align: center; padding: 34px 20px; color: var(--text-muted); }
.empty .ico-xl { color: var(--text-faint); opacity: .5; margin-bottom: 12px; }
.empty p { margin: 0 0 4px; }
.empty .hint { font-size: 13px; color: var(--text-faint); }

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

.muted { color: var(--text-muted); }
.faint { color: var(--text-faint); }
.small { font-size: 13px; }
.tiny  { font-size: 12px; }
.right { text-align: right; }
.nowrap { white-space: nowrap; }
.stack > * + * { margin-top: 12px; }
.divider { height: 1px; background: var(--border); margin: 16px 0; }
hr { border: 0; border-top: 1px solid var(--border); margin: 16px 0; }

details.disclose > summary {
  cursor: pointer; font-size: 14.5px; font-weight: 600; color: var(--text);
  padding: 9px 0; list-style: none; min-height: var(--tap);
  display: flex; align-items: center; gap: 9px;
}
details.disclose > summary::-webkit-details-marker { display: none; }
details.disclose > summary:hover { color: var(--accent); }
details.disclose > summary .ico { color: var(--text-faint); }
/* The chevron turns to point down when open — the one universally understood
   signal that a section holds more. */
details.disclose > summary::after {
  content: ""; margin-left: auto;
  width: 8px; height: 8px; border-right: 2px solid var(--text-faint);
  border-bottom: 2px solid var(--text-faint); border-radius: 1px;
  transform: rotate(-45deg); transition: transform .15s;
}
details.disclose[open] > summary::after { transform: rotate(45deg); }

.banner-dev {
  background: var(--warn-soft); color: var(--warn);
  text-align: center; padding: 5px; font-size: 12.5px; font-weight: 600;
}

/* --------------------------------------------------------------- login page */

.login-wrap {
  min-height: 100vh; display: grid; place-items: center; padding: var(--gap);
}
.login-card {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 14px; padding: 34px; max-width: 420px; width: 100%;
  box-shadow: var(--shadow); text-align: center;
}
.login-card h1 { margin-bottom: 8px; }
.login-card ol { text-align: left; padding-left: 20px; color: var(--text-muted);
                 font-size: 14px; line-height: 1.9; }
.login-card code {
  font-family: var(--mono); background: var(--surface-2);
  padding: 2px 6px; border-radius: 4px; color: var(--text); font-size: 13px;
}

.code-display {
  font-family: var(--mono); font-size: 30px; font-weight: 700;
  letter-spacing: .1em; padding: 20px; background: var(--surface-2);
  border: 1px dashed var(--border-strong); border-radius: var(--radius);
  text-align: center; margin: 16px 0; user-select: all;
}

/* ------------------------------------------------- big friendly task cards */
/* The home screen is a set of jobs to do, not a set of tables to read. */

.tasks { display: grid; gap: 12px; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); }

.task {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 18px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  color: var(--text);
  text-align: left;
}
.task:hover {
  text-decoration: none;
  border-color: var(--accent);
  background: var(--accent-soft);
}
.task .icon { flex: 0 0 auto; color: var(--accent); display: flex; }
.task .label { font-weight: 650; font-size: 16px; }
.task .desc  { font-size: 13.5px; color: var(--text-muted); margin-top: 2px; }
.task .badge {
  margin-left: auto; flex: 0 0 auto;
  background: var(--warn); color: #fff;
  border-radius: 100px; padding: 3px 10px;
  font-size: 13px; font-weight: 700;
}

/* ------------------------------------------------------------ mobile tables */
/* Below this width a table stops being a grid and becomes one card per row,
   each cell labelled by the column it came from. Sideways scrolling on a
   payroll table is how you misread a wage. */

@media (max-width: 720px) {
  table, thead, tbody, tr, td { display: block; width: 100%; }

  thead { position: absolute; left: -9999px; }   /* kept for screen readers */

  tbody tr {
    border: 1px solid var(--border);
    border-radius: var(--radius);
    margin-bottom: 10px;
    padding: 6px 2px;
    background: var(--surface);
  }
  tbody tr:hover { background: var(--surface); }

  td {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 14px;
    border: 0;
    padding: 9px 14px;
    text-align: right;
  }
  td.num { text-align: right; }

  /* The column heading, carried down as data-label on each cell. */
  td[data-label]::before {
    content: attr(data-label);
    font-size: 12.5px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: .05em;
    color: var(--text-faint);
    text-align: left;
    flex: 0 0 auto;
  }
  /* A cell with no label is the row's own actions: give it the full width. */
  td:not([data-label]) { justify-content: flex-end; }
  td:empty { display: none; }

  /* The "Change" row on Shops and the roster editor on a staff page are a
     colspan'd cell holding a whole embedded form, not a label/value pair —
     the flex/right-align treatment above squeezed the form's own rows into
     one horizontal line and pushed everything against the right edge. Block
     layout lets it stack normally, the same shape it already has on desktop,
     just full width. */
  td.bg-surface-2 {
    display: block;
    text-align: left;
  }

  tfoot { display: block; }
  tfoot td { justify-content: space-between; border-radius: var(--radius); }

  .table-wrap { overflow-x: visible; }
}

/* ------------------------------------------------------- the phone drawer */
/* Below this width the sidebar slides in over the page instead of taking a
   third of it. The page itself never shifts, so tapping a link does not make
   the content jump under your thumb. */

@media (max-width: 900px) {
  .shell { display: block; }

  .mobilebar {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 8px 12px;
    background: var(--surface);
    border-bottom: 1px solid var(--border);
    position: sticky;
    top: 0;
    z-index: 25;
  }

  .sidebar {
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    width: 264px;
    flex-basis: 264px;
    height: 100dvh;
    transform: translateX(-100%);
    transition: transform .18s ease-out;
    box-shadow: 0 0 0 rgba(0, 0, 0, 0);
  }
  .sidebar.open { transform: translateX(0); box-shadow: 0 0 40px rgba(0, 0, 0, .3); }

  .scrim {
    display: block;
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, .45);
    z-index: 28;
  }
  .scrim[hidden] { display: none; }

  /* With scripting off the drawer cannot be opened, so the menu is simply
     part of the page rather than an inaccessible off-screen panel. */
  .no-js .sidebar {
    position: static; transform: none; width: auto; flex-basis: auto;
    height: auto; border-right: 0; border-bottom: 1px solid var(--border);
  }
  .no-js .menu-btn, .no-js .scrim { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .sidebar { transition: none; }
  .live-dot { animation: none; }
  .qr-modal { transition: none; }
}

/* The quiet signal on /who that the board is keeping itself current, so a
   manager glancing at a shop-counter screen does not have to know the page
   polls every 30s to trust the number in front of them. */
.live-dot {
  display: inline-block;
  width: 7px; height: 7px;
  margin-left: 2px;
  border-radius: 50%;
  background: var(--ok);
  vertical-align: middle;
  animation: live-pulse 2s ease-in-out infinite;
}
@keyframes live-pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: .35; }
}

@media (max-width: 640px) {
  main { padding: 16px 12px 60px; }
  h1 { font-size: 21px; }
  .row > * { flex: 1 1 100%; }        /* one field per line, never a squeeze */
  .actions { gap: 10px; }
  .actions > form, .actions > form > button { width: 100%; }
  .card { padding: 16px; }
  .stat .value { font-size: 23px; }
}

@media print {
  .topbar, .actions, form { display: none !important; }
  body { background: #fff; }
  .card { box-shadow: none; border-color: #ccc; }
}

/* ------------------------------------------------------------- timelines */
/* One person against their shop's opening hours, so a gap in cover is a gap
   you can actually see. Used by the live board and the weekly rota alike. */

.tl-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 12px; flex-wrap: wrap; margin-bottom: 4px;
}

/* Name column, bar, then the length. The name column width is shared by the
   ruler's left margin so the hour marks line up with the bars beneath. */
.tl-ruler { position: relative; height: 20px; margin: 0 46px 4px 116px; }
.tl-tick { position: absolute; top: 0; transform: translateX(-50%);
           display: flex; flex-direction: column; align-items: center; }
.tl-tick i { width: 1px; height: 5px; background: var(--border-strong); display: block; }
.tl-tick b { font: 400 10.5px var(--mono); color: var(--text-faint); }

.tl-rows { display: flex; flex-direction: column; gap: 5px; }
.tl-row { display: grid; grid-template-columns: 116px 1fr 46px; align-items: center; }
.tl-who { font-size: 13.5px; padding-right: 10px; overflow: hidden;
          text-overflow: ellipsis; white-space: nowrap; }
.tl-who a { color: var(--text); }

.tl-track { position: relative; height: 26px; background: var(--surface-2);
            border-radius: 5px; overflow: hidden; }
.tl-bar { position: absolute; top: 0; bottom: 0; border-radius: 5px;
          display: flex; align-items: center; justify-content: center;
          min-width: 42px; background: var(--accent); }
.tl-bar span { font: 500 11px var(--mono); color: var(--accent-text);
               white-space: nowrap; padding: 0 6px; }
.tl-len { font: 400 11.5px var(--mono); color: var(--text-faint);
          text-align: right; font-variant-numeric: tabular-nums; }

/* State colours the bar, so the board reads at a glance without the legend. */
.tl-bar.is-on_site  { background: var(--ok); }
.tl-bar.is-on_site span { color: #fff; }
.tl-bar.is-late     { background: var(--danger); }
.tl-bar.is-late span { color: #fff; }
.tl-bar.is-finished { background: var(--border-strong); }
.tl-bar.is-finished span { color: var(--text); }
.tl-bar.is-due_later,
.tl-bar.is-off, .tl-bar.is-on_leave, .tl-bar.is-not_working {
  background: transparent; border: 1px dashed var(--border-strong);
}
.tl-bar.is-due_later span, .tl-bar.is-off span,
.tl-bar.is-on_leave span, .tl-bar.is-not_working span { color: var(--text-muted); }

/* Somebody working a shop that is not their own. */
.tl-bar.visiting { background: transparent; border: 1px dashed var(--accent); }
.tl-bar.visiting span { color: var(--accent); }

/* Where "now" falls across the opening hours. */
.tl-now { position: absolute; top: -2px; bottom: -2px; width: 2px;
          background: var(--danger); z-index: 2; pointer-events: none; }
.tl-now::after {
  content: ""; position: absolute; top: -3px; left: -3px;
  width: 8px; height: 8px; border-radius: 50%; background: var(--danger);
}

.tl-day { padding: 14px 0 4px; border-top: 1px solid var(--border); }
.tl-day:first-of-type { border-top: 0; }
.tl-daylabel { font-size: 13.5px; font-weight: 650; margin-bottom: 9px;
               display: flex; justify-content: space-between; gap: 10px; }
.tl-daylabel em { font: 400 11.5px var(--mono); color: var(--text-faint);
                  font-style: normal; }

.legend { display: flex; flex-wrap: wrap; gap: 6px 16px; font-size: 12.5px;
          color: var(--text-muted); }
.legend span { display: inline-flex; align-items: center; gap: 6px; }
.legend i { width: 15px; height: 9px; border-radius: 3px; display: inline-block; }

@media (max-width: 640px) {
  .tl-ruler { margin: 0 34px 4px 82px; }
  .tl-row { grid-template-columns: 82px 1fr 34px; }
  .tl-who { font-size: 12.5px; }
  .tl-bar span { font-size: 10px; padding: 0 3px; }
  .tl-bar { min-width: 30px; }
  .tl-tick b { font-size: 9.5px; }
  /* Every other hour mark below this width, or they collide into a smear. */
  .tl-tick:nth-child(even) { display: none; }
}

/* ================================================================ polish ===
   Refinements that change how the thing feels to use rather than how it looks
   in a screenshot. */

/* Page headings carry a one-line explanation. Somebody landing on a screen for
   the first time should not have to infer what it is for from the table. */
.page-head h1 { display: flex; align-items: center; gap: 10px; }
.page-head h1 .ico { color: var(--text-faint); }

/* A sticky action bar on long forms means Save is always reachable, instead of
   sitting below a screenful of fields. */
.card > form > .actions:last-child { margin-top: 4px; }

/* Tables: the first column is the subject and carries the weight. */
tbody td:first-child { font-weight: 500; }
tbody tr { transition: background .1s; }

/* Rows that lead somewhere show it. A table full of clickable rows with no
   affordance is a table people do not realise they can click. */
tbody tr:hover .btn-sm { border-color: var(--accent); color: var(--accent); }

/* Numbers line up under each other, always. Ragged decimals in a wage column
   are how a misread happens. */
td.num, th.num, .stat .value { font-variant-numeric: tabular-nums; }

/* Primary action is unmistakable; everything else recedes. */
.btn-primary { box-shadow: var(--shadow); }
.btn-primary:hover { background: var(--accent-hover); border-color: var(--accent-hover); filter: none; }

/* Disabled state must look disabled, not merely unresponsive. */
button:disabled, .btn:disabled {
  opacity: .55; cursor: not-allowed; box-shadow: none;
}

/* Inputs that are about to receive a value the user must not fat-finger. */
input:required:invalid:not(:placeholder-shown) { border-color: var(--danger); }

/* A form that failed shows the message beside the field, not only at the top. */
.field-error { color: var(--danger); font-size: 13px; margin-top: 5px; font-weight: 500; }

/* Cards group; sections inside them separate without another border. */
.card + .card { margin-top: var(--gap); }
h3 { display: flex; align-items: center; gap: 8px; }

/* Keyboard users get a route past the sidebar on every page. */
.skip {
  position: absolute; left: -9999px; top: 0; z-index: 60;
  background: var(--accent); color: var(--accent-text);
  padding: 12px 18px; border-radius: 0 0 var(--radius-sm) 0; font-weight: 600;
}
.skip:focus { left: 0; text-decoration: none; }

/* Status dot sizing sits with the chip it lives in. */
.chip .dot { width: 6px; height: 6px; margin-right: 5px; }
.chip.ok .dot { background: var(--ok); }

/* Long shop or staff names must not blow a table column apart. */
.tl-who, td { overflow-wrap: anywhere; }
.tl-who { overflow-wrap: normal; }

/* The payout QR on /payroll. One cell, stacked so the mobile card layout
   (label on the left, everything else as one block on the right) never
   splits the code from the account text beside it. */
.payout-cell {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 4px;
}
.payout-qr {
  display: block;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  /* A QR code needs a plain white quiet zone to stay scannable regardless of
     the row it sits in, not the page's own surface colour. */
  background: #fff;
  padding: 3px;
  cursor: zoom-in;
}

/* The QR lightbox: a native <dialog>, so open/close, the backdrop, and
   Escape-to-close all come from the browser for free instead of hand-rolled
   JS. Large enough to scan directly off the screen, which a 72px table
   thumbnail is not. */
.qr-modal {
  position: relative;
  border: 0;
  border-radius: var(--radius);
  padding: 28px;
  box-shadow: var(--shadow-lg);
  max-width: min(360px, 90vw);
  text-align: center;
  transition: max-width .15s ease-out;
}
/* Tap the code to fill most of the screen with it, tap again to shrink back
   — a phone scanning off another screen needs it bigger than a 360px dialog
   gives it by default. */
.qr-modal.zoomed {
  /* `<dialog>` defaults to width:fit-content, so a bigger max-width alone is
     only a higher ceiling — nothing pushes it to actually grow. An explicit
     width forces it, which also gives img{width:100%} a definite size to
     resolve against instead of falling back to the image's own intrinsic
     pixel size. */
  width: min(92vw, 92vh);
  max-width: none;
  padding: 16px;
}
.qr-modal::backdrop { background: rgba(20, 20, 18, .55); }
.qr-modal img {
  width: 100%; height: auto;
  border-radius: var(--radius-sm);
  background: #fff;
  padding: 10px;
  cursor: zoom-in;
}
.qr-modal.zoomed img { cursor: zoom-out; }
.qr-modal-caption { margin: 16px 0 0; font-weight: 620; }
.qr-modal-close {
  position: absolute; top: 10px; right: 10px;
  width: 34px; height: 34px; border-radius: 50%;
  border: 1px solid var(--border-strong); background: var(--surface);
  font-size: 20px; line-height: 1; cursor: pointer;
}
.qr-modal-close:hover { background: var(--surface-2); }

/* Per-person "mark paid" toggle on /payroll. Ticked state reuses the same
   ok/soft colouring every other "this is good" chip on the page already
   uses, so a scanned-and-paid row reads at a glance among the rest. */
.paid-toggle.is-paid {
  background: var(--ok-soft);
  border-color: var(--ok);
  color: var(--ok);
}

@media (max-width: 720px) {
  /* On a phone the first cell is the row's title, so it reads as a heading
     rather than as another labelled field. */
  tbody td:first-child {
    font-size: 15.5px; font-weight: 650;
    border-bottom: 1px solid var(--border); margin-bottom: 4px; padding-bottom: 10px;
  }
  tbody td:first-child::before { display: none; }
  tbody td:first-child { justify-content: flex-start; }
}

/* ---------------------------------------------------------------- utilities
   One-off placement that used to live in a `style=""` attribute. The CSP is
   `style-src 'self'` with no `unsafe-inline`, so an inline style is not a
   convenience here — it is invisible: the browser drops the whole attribute
   and the element renders as if it were never written. Anything that is not
   a per-row computed value (the timeline bars below) belongs here instead. */

.mt-0    { margin-top: 0; }
.mt-8    { margin-top: 8px; }
.mt-9    { margin-top: 9px; }
.mt-10   { margin-top: 10px; }
.mt-12   { margin-top: 12px; }
.mt-14   { margin-top: 14px; }
.mt-16   { margin-top: 16px; }
.mt-18   { margin-top: 18px; }
.mt-20   { margin-top: 20px; }
.mt-26   { margin-top: 26px; }
.mt-neg4 { margin-top: -4px; }

.mb-10  { margin-bottom: 10px; }
.mb-12  { margin-bottom: 12px; }
.mb-gap { margin-bottom: var(--gap); }

.m-0 { margin: 0; }
.m-8-12  { margin: 8px 0 12px; }
.m-8-14  { margin: 8px 0 14px; }
.m-10-0  { margin: 10px 0 0; }

.text-left   { text-align: left; }
.text-danger { color: var(--danger); }
.text-accent { color: var(--accent); }
.text-ok     { color: var(--ok); }

.border-warn   { border-color: var(--warn); }
.bg-surface-2  { background: var(--surface-2); }

.minw-220 { min-width: 220px; }

.max-620 { max-width: 620px; }
.max-540-center { max-width: 540px; margin: 0 auto; text-align: center; }
.max-520-center { max-width: 520px; margin: 0 auto; }
.wrap-420 { max-width: 420px; overflow-wrap: anywhere; }
.wrap-280 { max-width: 280px; overflow-wrap: anywhere; }

.jc-end    { justify-content: flex-end; }
.jc-center { justify-content: center; }

/* The big number on a "your rate" / summary card. */
.stat-big { font-size: 29px; font-weight: 650; margin: 2px 0 4px; }

.fs-16-normal  { font-size: 16px; font-weight: 400; }
.fs-15-tight   { font-size: 15px; letter-spacing: 0; }
.note-inline   { text-align: left; color: var(--text-muted); font-size: 14px; }

/* Card header padding, for the `<h2>`/`<p class="sub">` pair that opens a
   `.card.flush` — the card itself has no padding, so its first children
   carry it instead. */
.card-head    { padding: 16px 18px 12px; }
.card-head-sm { padding: 16px 18px 8px; }
.card-head-x  { padding: 0 18px 12px; }

/* Legend swatches on the presence timeline: a small block of the same
   colour or border the bar it explains uses. */
.swatch-ok      { background: var(--ok); }
.swatch-danger  { background: var(--danger); }
.swatch-muted   { background: var(--border-strong); }
.swatch-dashed-muted  { border: 1px dashed var(--border-strong); }
.swatch-dashed-accent { border: 1px dashed var(--accent); }

/* A timeline bar that spans its whole row — the "off / on leave" case,
   which has no real start or end to place. */
.tl-bar-full { left: 0; width: 100%; }
