:root {
  --bg: #f6f7f9;
  --card: #ffffff;
  --ink: #1b1f24;
  --muted: #6b7480;
  --line: #e2e6ea;
  --brand: #2563eb;
  --ok: #12805c;
  --warn: #b45309;
  --danger: #b42318;
  --radius: 10px;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font: 16px/1.5 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}

.wrap { max-width: 1180px; margin: 0 auto; padding: 24px 16px 64px; }
.wrap.narrow { max-width: 460px; padding-top: 48px; }

h1 { font-size: 1.5rem; margin: 0 0 16px; }
h2 { font-size: 1.05rem; margin: 0 0 12px; }

.muted { color: var(--muted); }
.small { font-size: .85rem; }
.mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
.break { word-break: break-all; }
.center { text-align: center; }
.right { text-align: right; }
.dim { opacity: .55; }

/* nav */
.nav {
  display: flex; gap: 18px; align-items: center; flex-wrap: wrap;
  background: var(--card); border-bottom: 1px solid var(--line);
  padding: 12px 20px;
}
.nav a { color: var(--muted); text-decoration: none; font-size: .92rem; }
.nav a:hover { color: var(--ink); }
.nav .brand { font-weight: 650; color: var(--ink); font-size: 1rem; }
.nav .right { margin-left: auto; }

/* cards */
.card {
  background: var(--card); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 20px; margin-bottom: 18px;
}
.notice-card { border-color: var(--brand); }
.cols { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; }
@media (max-width: 820px) { .cols { grid-template-columns: 1fr; } }

.tiles { display: flex; gap: 14px; margin-bottom: 18px; flex-wrap: wrap; }
.tile {
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 16px 22px; display: flex; flex-direction: column; min-width: 180px;
}
.tile .num { font-size: 1.9rem; font-weight: 650; }
.tile .lbl { color: var(--muted); font-size: .85rem; }

/* forms */
input, select, button { font: inherit; }
input, select {
  padding: 9px 11px; border: 1px solid var(--line); border-radius: 8px;
  background: #fff; color: var(--ink); width: 100%;
}
label { display: flex; flex-direction: column; gap: 5px; font-size: .85rem; color: var(--muted); }
label input, label select { color: var(--ink); }
.row { display: flex; gap: 12px; align-items: flex-end; flex-wrap: wrap; }
.row > * { flex: 1 1 160px; }
.row > button, .row > .btn { flex: 0 0 auto; }
.stack { display: flex; flex-direction: column; gap: 14px; }
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 12px; }
.inline { display: inline; }
.check { flex-direction: row; align-items: center; gap: 8px; color: var(--ink); font-size: .95rem; }
.check input { width: auto; }
fieldset { border: 1px solid var(--line); border-radius: 8px; padding: 14px; }
legend { padding: 0 6px; color: var(--muted); font-size: .85rem; }

.btn {
  display: inline-block; padding: 10px 16px; border-radius: 8px; cursor: pointer;
  border: 1px solid var(--line); background: #fff; color: var(--ink);
  text-decoration: none; text-align: center;
}
.btn.primary { background: var(--brand); border-color: var(--brand); color: #fff; }
.btn.block { display: block; width: 100%; margin-top: 12px; }
.btn.big { padding: 20px; font-size: 1.15rem; font-weight: 600; }
.btn:disabled { opacity: .55; cursor: default; }
.link { background: none; border: 0; color: var(--brand); cursor: pointer; padding: 4px 6px; }
.link.danger { color: var(--danger); }

/* tables */
.table { width: 100%; border-collapse: collapse; }
.table th, .table td { text-align: left; padding: 9px 8px; border-bottom: 1px solid var(--line); }
.table th { font-size: .78rem; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); }

.pill { display: inline-block; padding: 2px 9px; border-radius: 999px; font-size: .75rem; background: #eef1f4; }
.pill.ok { background: #e2f5ee; color: var(--ok); }
.pill.warn { background: #fdf0dd; color: var(--warn); }
.pill.off { background: #fbe6e4; color: var(--danger); }

/* collapsible list rows (locations) */
.hidden { display: none !important; }

.filter {
  margin-bottom: 14px; padding: 10px 13px;
  border: 1px solid var(--line); border-radius: 8px; width: 100%;
}

details.loc { padding: 0; margin-bottom: 10px; }
details.loc > summary {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 14px 18px; cursor: pointer; list-style: none;
  border-radius: var(--radius);
}
details.loc > summary::-webkit-details-marker { display: none; }
details.loc > summary::before {
  content: "▸"; color: var(--muted); font-size: .8rem; width: .8em;
  transition: transform .12s ease;
}
details.loc[open] > summary::before { transform: rotate(90deg); }
details.loc > summary:hover { background: #f9fafb; }
details.loc[open] > summary { border-bottom: 1px solid var(--line); border-radius: var(--radius) var(--radius) 0 0; }
/* Name takes the free space and stays on one line; the tags sit right and only
   drop below once the row genuinely cannot hold them. */
.loc-name { font-weight: 600; flex: 1 1 auto; min-width: 12ch; }
.loc-tags { display: flex; gap: 6px; flex-wrap: wrap; margin-left: auto; flex: 0 0 auto; }
.loc-form { padding: 18px; }

.nowrap { white-space: nowrap; }

/* keep inline-edit controls from bloating table rows */
.table input, .table select { padding: 6px 9px; font-size: .92rem; }

/* Tables with editable cells need explicit column shares: left to itself the
   browser starves the first column and the name wraps onto a second line. */
.table-scroll { overflow-x: auto; }
.table-scroll .table { min-width: 760px; }

/* The floor above is for the wide tables -- a roster or an attendance export
   squeezed into 400px is unreadable, so it scrolls instead. A three or four
   column table does not need protecting, and inheriting the floor hurt it: the
   dashboard puts two such tables in a half-width grid, so 760px overflowed a
   ~700px card and hid the last column. The Time was there all along, one scroll
   down and one scroll right away, which is the same as not being there. */
.table-scroll.narrow .table { min-width: 0; }

/* On a phone even four columns of real content do not fit: a long name alone
   claimed 172 of 385 available pixels and pushed Time off the edge again. So
   below 560px the location stops being a column and moves under the name,
   leaving who / in or out / when -- the three things the card is for. */
.fold-under { display: none; }
@media (max-width: 560px) {
  .table-scroll.narrow .col-fold { display: none; }
  .table-scroll.narrow .fold-under { display: block; }
  /* One long unbreakable name sets the table's minimum width on its own, and
     three folded columns do not help if the first will not yield. `anywhere`
     rather than `break-word` because only the former lowers min-content, which
     is the number the table is actually sized from. */
  .table-scroll.narrow td, .table-scroll.narrow th { overflow-wrap: anywhere; }
}

#emptable { table-layout: fixed; }
#emptable th:nth-child(1), #emptable td:nth-child(1) { width: 30%; }
#emptable th:nth-child(2), #emptable td:nth-child(2) { width: 26%; }
#emptable th:nth-child(3), #emptable td:nth-child(3) { width: 16%; }
#emptable th:nth-child(4), #emptable td:nth-child(4) { width: 14%; }
#emptable th:nth-child(5), #emptable td:nth-child(5) { width: 14%; }
#emptable td:nth-child(2) { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

#disptable { table-layout: fixed; }
#disptable th:nth-child(1), #disptable td:nth-child(1) { width: 24%; }
#disptable th:nth-child(2), #disptable td:nth-child(2) { width: 24%; }
#disptable th:nth-child(3), #disptable td:nth-child(3) { width: 18%; }
#disptable th:nth-child(4), #disptable td:nth-child(4) { width: 18%; }
#disptable th:nth-child(5), #disptable td:nth-child(5) { width: 16%; }

/* live view */
.sites { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 16px; }
.site { margin-bottom: 0; }
.site-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
.site-head h2 { margin-bottom: 2px; }
a.count { font-size: 1.7rem; font-weight: 650; line-height: 1;
          color: inherit; text-decoration: none; }
a.count:hover { color: var(--brand); }
.count.zero { color: var(--muted); font-weight: 400; }
.people { list-style: none; margin: 14px 0 10px; padding: 0; }
.people li { display: flex; justify-content: space-between; gap: 12px; padding: 6px 0;
             border-bottom: 1px solid var(--line); }
.people li:last-child { border-bottom: 0; }
.who { font-weight: 500; color: inherit; text-decoration: none; }
a.who:hover, .site-head h2 a:hover { color: var(--brand); text-decoration: underline; }
.site-head h2 a { color: inherit; text-decoration: none; }
.more { margin: 0; font-size: .88rem; }
.more a { color: var(--brand); text-decoration: none; }
.more a:hover { text-decoration: underline; }
.listhead { margin: 12px 0 0; }
.site-foot { margin: 10px 0 0; padding-top: 10px; border-top: 1px solid var(--line); }
#asof.stale { color: var(--danger); }
a.tile { text-decoration: none; color: inherit; }
a.tile:hover { border-color: var(--brand); }

/* site-time / my-time toggle */
.tzbar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin: -6px 0 16px; }
.chip {
  padding: 4px 12px; border: 1px solid var(--line); background: #fff;
  border-radius: 999px; cursor: pointer; font-size: .85rem; color: var(--muted);
}
.chip.on { background: var(--brand); border-color: var(--brand); color: #fff; }

.notice { background: #fdf0dd; color: var(--warn); padding: 10px; border-radius: 8px; }
.checks { list-style: none; padding: 0; margin: 18px 0 4px; text-align: left; }
.checks li { display: flex; gap: 10px; align-items: flex-start; padding: 7px 0;
             font-size: .95rem; }
.checks li::before { flex: 0 0 auto; font-weight: 700; }
.checks li.ok::before   { content: "✓"; color: var(--ok); }
.checks li.fail::before { content: "✕"; color: var(--danger); }
.checks li.warn::before { content: "!"; color: var(--warn); }
.checks li.fail { color: var(--danger); }

/* After the tap. The card holds one fact and nothing to press: a tick, and
   what was recorded. Everything that belonged to the decision is gone, because
   a screen still offering "Verify again" reads as though nothing happened. */
.card.done .status { font-size: 1.12rem; margin-top: 4px; }
.done-mark {
  width: 64px; height: 64px; margin: 22px auto 6px; border-radius: 50%;
  background: #e2f5ee; display: grid; place-items: center;
}
.done-mark svg {
  width: 34px; height: 34px; fill: none; stroke: var(--ok);
  stroke-width: 2.6; stroke-linecap: round; stroke-linejoin: round;
}

.status { min-height: 1.5em; margin-top: 14px; font-weight: 500; }
.status.ok { color: var(--ok); }
.status.err { color: var(--danger); }
.error-title { color: var(--danger); }
hr { border: 0; border-top: 1px solid var(--line); margin: 20px 0; }

/* tablet registration */
.steps { text-align: left; display: inline-block; margin: 0 0 22px; padding-left: 20px;
         color: #9aa4b2; line-height: 1.9; }
.confirm { font-size: 1.5rem; letter-spacing: .12em; font-family: ui-monospace, monospace; }
.pushright { margin-left: auto; }
.row > .pushright { flex: 0 0 auto; }

.exportbar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
             margin: 14px 0 0; padding-top: 14px; border-top: 1px solid var(--line); }

.rowbar { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; margin: 10px 0 0; }

.flagmark { text-decoration: none; font-size: 1rem; }
.flagmark.open { color: var(--danger); }
.flagmark.reviewed { color: var(--ok); }
.flagmark:hover { opacity: .7; }

.danger-zone {
  margin: 0 18px 18px; padding-top: 14px; border-top: 1px solid var(--line);
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
}
.danger-zone button[disabled] { color: var(--muted); cursor: not-allowed; }
.actions { display: flex; gap: 10px; }

details.setup { padding: 0; }
details.setup > summary { padding: 16px 18px; cursor: pointer; list-style: none; }
details.setup > summary::-webkit-details-marker { display: none; }
details.setup > summary:hover .btnish { border-color: var(--brand); color: var(--brand); }
details.setup > ol, details.setup > p, details.setup > .sub { margin-left: 18px; margin-right: 18px; }
details.setup > .sub { padding-bottom: 16px; }
.btnish {
  display: inline-block; padding: 8px 15px; border: 1px solid var(--line);
  border-radius: 8px; font-size: .95rem;
}

.addbar { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; margin: 0 0 18px; }
.setup-steps { margin: 0 0 16px; padding-left: 20px; line-height: 2; }

.entry-input {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 1.5rem; letter-spacing: .18em; text-align: center; text-transform: uppercase;
  padding: 14px;
}
.confirm-check { text-align: center; font-size: 1.05rem; }
.confirm-check .confirm { display: block; margin-top: 6px; color: var(--brand); }

/* display mode */
.display-mode { background: #0d1117; color: #fff; }
.display-wrap { display: grid; place-items: center; min-height: 100vh; padding: 20px; }
.display { text-align: center; }
.display h1 { font-size: 1.6rem; margin-bottom: 20px; }
.display .muted { color: #9aa4b2; }
/* The white card is the tablet screen, drawn the same way the demo draws it.
   Brand blue lives on the card and never on the wall behind it: #2563eb on
   #0d1117 is unreadable, and the card is what makes the colour work. */
.qr-frame {
  display: inline-block; background: #fff; padding: 18px 18px 14px;
  border-radius: 16px; text-align: center;
}
.qr-site {
  /* The demo's tablet is 300px on a laptop; this one is on a wall and read
     from across a room, so the size follows the card rather than the demo's
     figure. Less letter-spacing than the demo, because large caps need less. */
  font-size: clamp(1.15rem, 3.6vmin, 1.8rem);
  font-weight: 700; letter-spacing: .03em;
  text-transform: uppercase; color: var(--brand); margin-bottom: 14px;
}
.qr-shot { position: relative; }
.qr-frame img { display: block; width: min(60vmin, 420px); height: auto; image-rendering: pixelated; }
.qr-tick { margin-top: 11px; font-size: .86rem; color: #64748b; }
/* Tabular figures so the line does not jog sideways every time the count
   crosses ten. On a wall display that twitch is the most visible thing on it. */
#countdown { color: var(--ink); font-variant-numeric: tabular-nums; font-weight: 650; }

.qr-brand {
  display: flex; align-items: center; justify-content: center; gap: 7px;
  margin-top: 12px; padding-top: 11px; border-top: 1px solid #eef1f5;
  font-size: .76rem; color: #94a3b8;
}
.qr-brand svg { width: 15px; height: 15px; fill: var(--brand); }
.qr-brand strong { color: var(--ink); font-weight: 650; letter-spacing: -.01em; }

.overlay {
  position: absolute; inset: 0; display: grid; place-items: center;
  background: rgba(255, 255, 255, .82); color: #0d1117; font-weight: 600; border-radius: 8px;
}
.overlay.hidden { display: none; }

/* The control list lives in controls.css, shared with the demo. It used to be
   duplicated here, and the copy made .ctl a three-column grid while the shared
   rules expect it to stack a head, a button group and a hint -- so the children
   were laid into columns and printed over each other. */

/* Demo sandbox banner. Present on every page of the administrator half so a
   visitor is never unsure whether what they are looking at is real -- and
   carrying the demo's own navigation, since the console's chrome belongs to
   the product and should not grow demo links. */


/* The list shows successes as well as failures -- a screen that only ever
   appears to complain reads as an obstacle, where the same screen with three
   ticks and one cross reads as a system doing its job. Marks come from
   .checks li::before above; a second span here drew every tick twice. */
.checks li > span { min-width: 0; }

/* Re-checking swaps the mark for a spinner and leaves the text alone, so the
   row that is being re-examined is obvious and nothing else moves. */
.checks li.wait::before {
  content: ""; width: .85em; height: .85em; border-radius: 50%;
  border: 2px solid var(--line); border-top-color: var(--muted);
  animation: check-spin .7s linear infinite; margin-top: .18em;
}
@keyframes check-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .checks li.wait::before { animation: none; border-top-color: var(--line); }
}


/* A date or a time is one thing, and breaking it across two lines turns a
   compact list into a wall. Only these: a long location name may still wrap. */
.table td.nowrap, .table th { white-space: nowrap; }


/* Employees: rows are read-only until Edit, and the CSV importer is folded
   away because most days nobody is importing anything. */
.import { margin-top: 16px; }
.import summary { cursor: pointer; font-size: .9rem; color: var(--brand); }
.import summary::marker { color: var(--muted); }
.notice { background: #eff6ff; border: 1px solid #bfdbfe; color: #1e3a8a;
          border-radius: 10px; padding: 10px 14px; margin-bottom: 16px;
          font-size: .92rem; }


@media (max-width: 640px) {
  .policy { grid-template-columns: 18px 1fr; }
  .policy-detail { grid-column: 2; }
  .policy .seg { grid-column: 2; justify-self: start; margin-top: 4px; }
}


/* Settings: fields on a line rather than a column. */
.row.wrap { display: flex; flex-wrap: wrap; gap: 14px; align-items: flex-end; }
.field { display: grid; gap: 4px; font-size: .82rem; color: #64748b; }
.field.grow { flex: 1 1 240px; }
.field input, .field select { font: inherit; font-size: .95rem; }

.plan { display: flex; align-items: center; gap: 10px; margin-bottom: 14px; }
.usage { display: flex; gap: 28px; margin: 0 0 12px; flex-wrap: wrap; }
.usage div { display: grid; gap: 2px; }
.usage dt { font-size: .78rem; color: #64748b; text-transform: uppercase;
            letter-spacing: .05em; }
.usage dd { margin: 0; font-size: 1.4rem; font-weight: 600; color: #0f172a;
            font-variant-numeric: tabular-nums; }


/* "?" at the end of the line it explains, and a panel that floats over the
   page when pressed. An expanding block moves everything below it, so opening
   one to read about a field shifts that field out from under the cursor -- and
   closing it moves the page back, which reads as a jump. */
.helpwrap { position: relative; display: inline-block; }
.help {
  font: inherit; font-size: .7rem; line-height: 1; font-weight: 700;
  width: 17px; height: 17px; border-radius: 50%; cursor: pointer;
  border: 1px solid var(--line); background: #fff; color: var(--muted);
  padding: 0; margin-left: 5px; vertical-align: middle;
}
.help:hover, .help.on { border-color: var(--brand); color: var(--brand); }
.help.on { background: var(--brand); color: #fff; }

.help-pop {
  position: absolute; z-index: 40; top: calc(100% + 8px); left: -8px;
  width: min(340px, calc(100vw - 32px));
  padding: 12px 14px; border-radius: 10px;
  background: #fff; border: 1px solid var(--line);
  box-shadow: 0 4px 6px rgba(15, 23, 42, .04), 0 12px 28px rgba(15, 23, 42, .14);
  font-size: .86rem; font-weight: 400; line-height: 1.55;
  color: var(--body); text-align: left; text-transform: none;
  letter-spacing: normal; white-space: normal; display: block;
}
.help-pop[hidden] { display: none; }
.help-pop p { margin: 0 0 8px; }
.help-pop p:last-child { margin-bottom: 0; }

/* Near the right edge it opens leftwards, so it cannot fall off the page. */
.help-pop.flip { left: auto; right: -8px; }



/* Pager: quiet, and only drawn when there is more than one page. */
.pager { display: flex; align-items: center; gap: 12px; margin: 14px 18px 4px;
         flex-wrap: wrap; }
.btn.small { padding: 5px 12px; font-size: .85rem; }
.btn.disabled { color: var(--muted); border-color: var(--line); cursor: default;
                pointer-events: none; }

/* Locations, tightened to the demo's density. The page was a stack of tall
   cards with a paragraph under every field; it is now a list of sites you can
   see at once, with the explanations a click away. */
.card.loc { padding: 0; margin-bottom: 10px; }
.card.loc > summary {
  padding: 12px 18px; display: flex; align-items: center; gap: 10px;
  flex-wrap: wrap; cursor: pointer;
}
.card.loc > summary:hover { background: var(--wash, #f8fafc); }
.card.loc[open] > summary { border-bottom: 1px solid var(--line); }
.card.loc .loc-name { font-weight: 600; }
.card.loc form { padding: 16px 18px; }
.card.loc fieldset { border: 0; padding: 0; margin: 0 0 18px; }
.card.loc legend {
  padding: 0; font-size: .78rem; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: var(--muted); margin-bottom: 8px;
}
.card.loc .grid { gap: 10px; }
.card.loc label { font-size: .82rem; color: var(--muted); }
.card.loc input, .card.loc select { font-size: .95rem; }
.pill { font-size: .74rem; padding: 2px 8px; }


/* A location, expanded: name and timezone, then what it checks on the left and
   the settings for whichever gear is pressed on the right. Expanding them
   under a row pushed everything below it down the page, which moved the
   control you had just pressed. */
.loc-identity {
  display: flex; align-items: center; gap: 10px; margin: 0 0 12px;
  padding-bottom: 10px; border-bottom: 1px solid var(--line);
}
.loc-identity .ctl-label { font-weight: 600; }
.loc-grid {
  display: grid; gap: 20px; align-items: stretch;
  grid-template-columns: minmax(0, 380px) minmax(0, 1fr);
}
@media (max-width: 820px) { .loc-grid { grid-template-columns: 1fr; } }

.loc-checks {
  border: 1px solid var(--line); border-radius: 12px;
  padding: 14px 16px; background: var(--wash, #f8fafc);
}
.loc-checks h3, .loc-config h4 {
  margin: 0; font-size: .78rem; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: var(--muted);
}

/* Beside the name, so it reads as belonging to it. */
.cog {
  font: inherit; font-size: .8rem; line-height: 1; cursor: pointer;
  width: 20px; height: 20px; border-radius: 5px; padding: 0; margin-left: 6px;
  border: 1px solid var(--line); background: #fff; color: var(--muted);
  vertical-align: middle;
}
.cog:hover, .cog.on { border-color: var(--brand); color: var(--brand); }
.cog.on { background: var(--brand); color: #fff; }

/* The settings column is the same panel as the checks it belongs to: same
   border, same wash, same height. Two differently-coloured boxes side by side
   read as two unrelated things. */
.loc-config {
  border: 1px solid var(--line); border-radius: 12px;
  padding: 14px 16px; background: var(--wash, #f8fafc);
}
.cfg-empty { margin: 0; }
.ctl-config .grid { gap: 10px; margin-top: 10px; }
/* A label is a column, so the "?" beside its text needs to share a line with
   it rather than become the next row. */
.lbl { display: flex; align-items: center; }
.ctl-config label { font-size: .8rem; color: var(--muted); }
.ctl-config .check { margin-top: 12px; display: block; }

/* CSP forbids inline styles, and this one had slipped in. */
.archived-table { margin-top: 14px; }


/* Each settings panel commits itself. A Save at the bottom of the card was a
   long way from whatever had just been changed, and the delete sat below it
   with nothing to say which location it meant. */
.cfg-actions {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--line);
}
/* Removing a location belongs with its name -- that is the thing being
   removed, and the panel already says which one. */
.cfg-danger {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--line);
}

/* Map picker. Typing coordinates by hand is the step of location setup most
   likely to be got wrong, and a geofence centred on the wrong building locks
   out a whole site until somebody works out why. */
.mappick { margin: 12px 0 4px; }
.map-find { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 8px; }
.map-find .map-search {
  flex: 1 1 220px; font: inherit; font-size: .92rem; padding: 7px 10px;
  border: 1px solid var(--line); border-radius: 8px;
}
.map { height: 300px; border-radius: 10px; border: 1px solid var(--line); }
.map-results { display: grid; gap: 4px; margin-bottom: 8px; }
.map-result {
  text-align: left; font: inherit; font-size: .86rem; cursor: pointer;
  padding: 7px 10px; border: 1px solid var(--line); border-radius: 8px;
  background: #fff; color: var(--body);
}
.map-result:hover { border-color: var(--brand); color: var(--brand); }
.map-status { min-height: 1.2em; margin: 6px 0 0; }
.map-status.err { color: var(--danger); }
/* Leaflet draws its own controls; keep them under our own popovers. */
.leaflet-container { z-index: 0; }

/* "Saved", beside the button that did it. A page that reloads to store one
   number throws away the open card, the open panel and the map with them. */
.save-note { margin-left: 4px; }
.save-note.err { color: var(--danger); }

/* Adding people is a once-in-a-while task, so it sits behind a button and the
   page opens on the roster. Two ways in, one divider between them. */
details.setup > .sub > form + .rule { margin-top: 18px; }
.rule {
  padding-top: 16px; margin-bottom: 12px; border-top: 1px solid var(--line);
}

/* The roster's shape, above the roster. Reads as a sentence rather than a row
   of dashboard tiles: this is one line of context, not the page's subject. */
.stats {
  display: flex; flex-wrap: wrap; gap: 6px 22px;
  margin: -6px 0 18px; color: var(--muted); font-size: .92rem;
}
.stat strong {
  color: var(--ink); font-weight: 650; font-variant-numeric: tabular-nums;
  margin-right: 3px;
}
.stat.off strong { color: var(--muted); }

/* The display's own name, for whoever has to identify it. Cornered rather than
   removed: it is a support handle, not part of checking in. */
.display-id {
  position: fixed; bottom: 10px; right: 14px;
  font-size: .72rem; color: #39414d; letter-spacing: .01em;
}

/* An edit table must not change width when a row opens.
   The read-only row is text and one link; the editing row is two fields and up
   to five actions. Left to size itself the table grew past its container and
   put a horizontal scrollbar under the one row you had just opened to read.
   Fixed layout with declared columns holds the width; the fields fill their
   own column instead of claiming a default twenty characters. */
.table[data-rowedit] { table-layout: fixed; }
.table[data-rowedit] .editing input,
.table[data-rowedit] .editing select {
  width: 100%; box-sizing: border-box; min-width: 0;
}
.table[data-rowedit] td { overflow-wrap: anywhere; }
/* The actions wrap rather than widen. Two short lines inside one row beats a
   scrollbar across the whole table, and only while that row is open. */
.table[data-rowedit] td.acts { white-space: normal; }
.table[data-rowedit] td.acts .link { white-space: nowrap; }

/* The ⋯ menu on a table row. It floats over the page like the "?" does, so
   opening it changes no column's width and no row's height -- the row you just
   opened is the row you are trying to read. */
.rowmenuwrap { position: relative; display: inline-block; }
.rowmenu { font-size: 1.05rem; line-height: 1; padding: 0 4px; letter-spacing: .04em; }
.rowmenu.on { color: var(--brand); }
.rowmenu-pop {
  position: absolute; z-index: 40; top: calc(100% + 6px); left: -8px;
  min-width: 148px; padding: 6px; border-radius: 10px;
  background: #fff; border: 1px solid var(--line);
  box-shadow: 0 4px 6px rgba(15, 23, 42, .04), 0 12px 28px rgba(15, 23, 42, .14);
  text-align: left; white-space: nowrap; display: block;
}
.rowmenu-pop[hidden] { display: none; }
.rowmenu-pop.flip { left: auto; right: -8px; }
.rowmenu-pop form { margin: 0; }
.rowmenu-pop .link {
  display: block; width: 100%; text-align: left;
  padding: 7px 10px; border-radius: 7px; font-size: .9rem;
}
.rowmenu-pop .link:hover:not([disabled]) { background: var(--wash); }
/* A destructive button and its "?" travel together: the pair wraps as one, so
   an explanation never ends up on a different line from what it explains. */
.cfg-danger .act { display: inline-flex; align-items: center; }
.cfg-danger { gap: 10px 18px; }

/* A pair of opposite states, both drawn, one shown. The card carries which it
   is in, so stopping or restarting a tablet swaps the buttons and the pill in
   place -- no page rebuilt around a one-word change. */
[data-status="ACTIVE"] .when-disabled,
[data-status="DISABLED"] .when-active { display: none; }

/* A display an administrator turned off is not reconnecting, and saying so is
   the difference between somebody waiting and somebody asking. Opaque, because
   the code underneath is no longer one anybody should scan. */
.overlay.stopped {
  background: #fff; color: #b42318; font-weight: 600;
  white-space: pre-line; text-align: center; padding: 0 18px; line-height: 1.45;
}

/* Two ways to pair a tablet, side by side. Run together as one list, the
   choice -- does the thing in your hand have a camera -- was buried in the
   middle of step two. */
.setup-lead { margin: 0 18px 16px; }
.ways {
  display: grid; gap: 16px; margin: 0 18px 4px;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
}
.way {
  border: 1px solid var(--line); border-radius: 12px; padding: 14px 16px;
}
.way h3 { font-size: 1rem; margin-bottom: 2px; }
.way .setup-steps { margin: 10px 0 12px; line-height: 1.7; }
.way > p:last-child { margin-bottom: 0; }

/* The tablet's turn: a code on the admin's screen for the tablet's camera. */
.card.pairing {
  display: flex; align-items: center; gap: 22px; flex-wrap: wrap;
  justify-content: space-between;
}
.card.pairing > div { flex: 1 1 280px; }
.card.pairing h2 { margin-bottom: 6px; }
.card.pairing p { margin-bottom: 8px; }
.pair-qr {
  display: block; background: #fff; padding: 10px; border-radius: 12px;
  border: 1px solid var(--line); image-rendering: pixelated;
}
.setup-form { margin: 0 18px 14px; }

/* Three states, not two. A display waiting for its first device has never
   issued anything, so it can be neither disabled nor re-enabled -- what it
   needs is another pairing code. */
[data-status="ACTIVE"] .when-disabled,
[data-status="ACTIVE"] .when-pending,
[data-status="DISABLED"] .when-active,
[data-status="DISABLED"] .when-pending,
[data-status="PENDING"] .when-active,
[data-status="PENDING"] .when-disabled,
[data-status="PENDING"] .when-paired { display: none; }

.pair-or { padding-top: 10px; border-top: 1px solid var(--line); }
.pair-code { max-width: 300px; margin-bottom: 10px; }
.pair-code .entry-input { font-size: 1.1rem; padding: 9px 12px; }
.setup-note { margin: 0 18px 4px; }
/* The pairing link, long and meant to be copied rather than read. */
#pairlink { flex: 1 1 220px; min-width: 0; font-size: .82rem; }

/* A panel inside a scrolling table: it leaves the box that would clip it and
   is placed against the viewport instead, under the button it belongs to.
   Both kinds -- the "?" and the row menu -- since both live in tables. */
.help-pop.floating,
.rowmenu-pop.floating { position: fixed; top: 0; left: 0; right: auto; }

/* Google's sign-in button, to their identity guidelines: white ground, the
   grey border they specify, near-black label, and the mark at 18px beside it.
   Our own blue with plain text was not one of the permitted styles, and a
   button carrying somebody else's brand should look like theirs. */
.btn-google {
  display: inline-flex; align-items: center; justify-content: center; gap: 12px;
  padding: 11px 16px; border-radius: 8px;
  background: #fff; border: 1px solid #747775; color: #1f1f1f;
  font: inherit; font-size: .97rem; font-weight: 500; text-decoration: none;
  cursor: pointer;
}
.btn-google:hover { background: #f7f8f8; border-color: #5f6368; }
.btn-google:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.btn-google svg { width: 18px; height: 18px; flex: 0 0 18px; }

/* Choosing an organization. One button per membership, each naming the role --
   somebody with two usually has different powers in each, and that is what
   tells them which one they meant. */
.ws-choices { gap: 10px; }
.ws-choice {
  display: flex; align-items: center; justify-content: space-between; gap: 14px;
  width: 100%; padding: 14px 16px; border-radius: 10px; cursor: pointer;
  background: #fff; border: 1px solid var(--line); font: inherit; text-align: left;
}
.ws-choice:hover { border-color: var(--brand); background: var(--wash); }
.ws-choice:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.ws-name { font-weight: 600; color: var(--ink); }
.ws-role {
  font-size: .78rem; text-transform: uppercase; letter-spacing: .05em;
  color: var(--muted);
}
/* Which organization this session is in, in the nav. Quiet, because it is
   context rather than a destination -- but always there. */
/* The organization this session is in, beside Sign out. When there is somewhere
   to switch to the name itself is the control -- a separate "Switch" link was
   a second thing to notice, and the name is what somebody reads when they
   suspect they are in the wrong place. */
.ws-current {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: .88rem; color: var(--muted); text-decoration: none;
  padding: 4px 10px; border-radius: 999px; border: 1px solid transparent;
}
a.ws-current:hover { border-color: var(--line); color: var(--ink); background: #fff; }
a.ws-current:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.ws-current span { font-size: .82rem; opacity: .7; }
.ws-current.plain { padding-left: 0; }
.ws-choice.on { border-color: var(--brand); background: var(--wash); }

/* The nav's right-hand pair sat at different heights. Two causes, both quiet:
   a <button> keeps the browser's own font unless told otherwise -- 13.3px
   Arial in Chrome against the nav's 0.92rem -- and .inline is display:inline,
   so the logout form boxed differently from the organization pill beside it.
   One font, one line-height, both centred on the same axis. */
.nav .link { font: inherit; font-size: .92rem; line-height: 1.2; }
.nav .inline { display: inline-flex; align-items: center; }
.nav .ws-current { font-size: .92rem; line-height: 1.2; }
/* The way out of the login page for somebody who has no account yet. Quiet,
   but on the page they are stuck on rather than back on the landing page they
   have already left. */
.signup-hint { margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--line); }
/* The mark on the sign-in page. A page with nothing but a name on it does not
   look like the product you were about to use. */
.login-mark {
  display: inline-flex; align-items: center; gap: 10px;
  letter-spacing: -.02em;
}
.login-mark svg { width: 26px; height: 26px; fill: var(--brand); }
/* The mark beside the name in the console's nav. Same size as the type it
   sits against, so the pair reads as one word rather than a logo and a label. */
.nav .brand { display: inline-flex; align-items: center; gap: 8px; }
.nav .brand svg { width: 18px; height: 18px; fill: var(--brand); }
.ws-or { margin: 2px 0; }

/* ── phones ───────────────────────────────────────────────────────────────
   Found by loading every page at 320–412px in a real browser rather than by
   eye. Each of these made the page itself scroll sideways, which is the worst
   kind of broken on a phone: the reader cannot tell whether they have seen the
   whole thing. */

/* A flex or grid child refuses to shrink below its content unless told, and
   these hold long names, long addresses and search boxes. */
.row > *, .grid > *, .loc-grid > *, .map-find > *,
.cols > *, .tiles > *, .stats > * { min-width: 0; }

/* The pills beside a location's name were flex: 0 0 auto, so a site with three
   of them set the width of the page. They wrap now, and give ground first. */
.loc-tags { flex: 1 1 auto; justify-content: flex-end; }

@media (max-width: 560px) {
  .wrap { padding-left: 12px; padding-right: 12px; }
  .card { padding: 16px 14px; }
  .loc-form { padding: 14px 12px; }

  /* Name and pills stack rather than sharing a line neither of them fits. */
  .card.loc > summary { flex-wrap: wrap; }
  .loc-tags { justify-content: flex-start; margin-left: 0; width: 100%; }

  /* Two columns of settings is one column on a phone. */
  .loc-grid { grid-template-columns: 1fr; }
  .grid { grid-template-columns: 1fr; }

  /* The map picker's controls, one per line, rather than a search box squeezed
     to nothing beside two buttons. */
  .map-find { flex-direction: column; align-items: stretch; }
  .map-find .btn { width: 100%; }
}
