
:root {
  /* Neutrals — tinted toward the brand navy */
  --n0:#fff; --n25:#fbfcfe; --n50:#f5f7fa; --n100:#eaeef4; --n200:#d9e0ea;
  --n300:#bcc7d6; --n400:#94a2b6; --n500:#6c7a90; --n600:#4e5b6f; --n700:#374252;
  --n800:#222c3a; --n900:#16202f; --n950:#0d1420;

  /* Brand. #1b2a41 slate navy and #d7263d crimson are the two fixed values;
     the rest of each ramp is mixed around them for the jobs one value cannot
     do — a link has to separate from near-black ink on white and from
     near-white ink on navy, and one colour cannot be both. */
  --navy:#1b2a41;
  --a300:#8fabd4; --a400:#5f83b8; --a500:#3f6396; --a600:#2c4a76; --a700:#1b2a41;
  --crimson:#d7263d;
  --c300:#f08a97; --c400:#e85266; --c500:#d7263d; --c600:#ac1c30;

  /* Category, per §7. Fixed meanings, used nowhere else.
     Fire is the brand crimson: the mark is a siren, so the colour the product
     is drawn in and the colour of an alarm are the same claim, and having them
     be two different reds on one page would make both of them mean less. */
  --fire:var(--crimson); --medical:#c0326a; --intrusion:#c8760a; --environmental:#0e7f9c;
  --ok:#12795a; --warn:#a86a0a; --bad:var(--crimson);

  /* Semantic surfaces */
  --bg:var(--n50); --surface:var(--n0); --raised:var(--n0);
  --line:var(--n200); --line-strong:var(--n300);
  --ink:var(--n900); --ink-2:var(--n600); --ink-3:var(--n500);
  --accent:var(--a600); --accent-ink:#fff; --accent-soft:#e9eff8;
  --focus:var(--a500);

  /* Spacing — 4px base */
  --s1:.25rem; --s2:.5rem; --s3:.75rem; --s4:1rem; --s5:1.5rem;
  --s6:2rem; --s7:3rem; --s8:4rem;

  /* Type scale */
  --t-xs:.75rem; --t-sm:.8125rem; --t-md:.875rem; --t-base:.9375rem;
  --t-lg:1.125rem; --t-xl:1.5rem; --t-2xl:2rem;

  --r-sm:6px; --r-md:10px; --r-lg:14px; --r-full:999px;

  --shadow-1:0 1px 2px rgb(13 20 32 / .05), 0 1px 3px rgb(13 20 32 / .07);
  --shadow-2:0 2px 4px rgb(13 20 32 / .05), 0 8px 24px rgb(13 20 32 / .09);
  --shadow-focus:0 0 0 3px color-mix(in srgb, var(--focus) 28%, transparent);
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg:var(--n950); --surface:var(--n900); --raised:var(--n800);
    --line:#2a3546; --line-strong:#3b4759;
    --ink:var(--n50); --ink-2:var(--n300); --ink-3:var(--n400);
    --accent:var(--a300); --accent-ink:var(--n950); --accent-soft:#1d2b40;
    --focus:var(--a400);
    /* Crimson lifted off the navy ground. #d7263d is mixed for white and goes
       muddy on a dark surface; this is the same hue with the lightness the
       background takes back. */
    --crimson:#f2465c;
    --fire:var(--crimson); --medical:#e8709b; --intrusion:#eda93f; --environmental:#3fb8c6;
    --ok:#45b184; --warn:#d99a3a; --bad:var(--crimson);
    --shadow-1:0 1px 2px rgb(0 0 0 / .3);
    --shadow-2:0 2px 4px rgb(0 0 0 / .3), 0 8px 24px rgb(0 0 0 / .35);
  }
}

*,*::before,*::after { box-sizing:border-box; }
html { -webkit-text-size-adjust:100%; }
body {
  margin:0; background:var(--bg); color:var(--ink);
  font-family:system-ui,-apple-system,"Segoe UI Variable Text","Segoe UI",
              Roboto,"Helvetica Neue",sans-serif;
  font-size:var(--t-base); line-height:1.55;
  -webkit-font-smoothing:antialiased; -moz-osx-font-smoothing:grayscale;
  font-variant-numeric:tabular-nums;
}
h1,h2,h3 { margin:0; font-weight:600; letter-spacing:-.02em; line-height:1.25; }
h1 { font-size:var(--t-xl); }
h2 { font-size:var(--t-lg); }
h3 { font-size:var(--t-base); }
p { margin:0; }
a { color:var(--accent); text-decoration:none; }
a:hover { text-decoration:underline; }

/* ---- app frame ---------------------------------------------------------- */
/* 16rem, not 15: the mark is the widest fixed thing in the sidebar, and the
   column is sized to hold it at a legible size on one line rather than the
   mark being sized to fit a column picked before it existed. */
.app { display:grid; grid-template-columns:16rem 1fr; min-height:100vh; }
@media (max-width:56rem) { .app { grid-template-columns:1fr; } }

.side {
  background:var(--surface); border-right:1px solid var(--line);
  padding:var(--s5) var(--s4); display:flex; flex-direction:column; gap:var(--s5);
}
@media (max-width:56rem) { .side { border-right:0; border-bottom:1px solid var(--line); } }

/* The wordmark is a link home, which is where a wordmark goes. It keeps the
   ink colour and gains no underline: the generic anchor rule paints links
   accent and underlines them on hover, which on a masthead reads as a stray
   link rather than as the identity of the page. */
.mark { display:flex; align-items:center; gap:var(--s3); font-weight:650;
        font-size:var(--t-xl); letter-spacing:-.02em; color:var(--ink);
        text-decoration:none; white-space:nowrap; }
.mark:hover { text-decoration:none; color:var(--ink); }
/* The screen outline inherits this, so the mark reads correctly in both
   schemes from one drawing. The siren does not: it is crimson either way. */
.mark-logo { width:3.2rem; height:2.4rem; flex:none; color:var(--ink);
             overflow:visible; }

/* A breadcrumb, not a label.
   This said "Dealer: Development", which reads as an identity claim — as though
   the signed-in platform administrator WERE that dealer. They are not; they
   navigated into it. A breadcrumb says location, which is what this is, and its
   first segment is the way back out, so no separate control is needed. */
.crumbs { display:flex; align-items:center; gap:var(--s2); margin-bottom:var(--s5);
          font-size:var(--t-md); color:var(--ink-3); }
.crumbs strong { color:var(--ink); font-weight:560; }
.crumbs span { color:var(--n400); }

.nav { display:flex; flex-direction:column; gap:1px; }
.nav a {
  display:flex; align-items:center; gap:var(--s3); padding:var(--s2) var(--s3);
  border-radius:var(--r-sm); color:var(--ink-2); font-size:var(--t-md);
  font-weight:500; transition:background .12s ease, color .12s ease;
}
.nav a:hover { background:var(--n100); color:var(--ink); text-decoration:none; }
@media (prefers-color-scheme:dark){ .nav a:hover { background:var(--raised); } }
.nav a[aria-current="page"] { background:var(--accent-soft); color:var(--accent); font-weight:600; }
.nav .count { margin-left:auto; font-size:var(--t-xs); color:var(--ink-3);
              font-variant-numeric:tabular-nums; }

.side .foot { margin-top:auto; display:flex; flex-direction:column; gap:var(--s2);
              border-top:1px solid var(--line); padding-top:var(--s4); }
.who { font-size:var(--t-sm); color:var(--ink-2); overflow-wrap:anywhere; }
.who .role { display:inline-block; margin-top:var(--s1); padding:.05rem .45rem;
             border:1px solid var(--line-strong); border-radius:var(--r-full);
             font-size:var(--t-xs); color:var(--ink-3); }

.main { padding:var(--s6) var(--s6) var(--s8); max-width:72rem; }
@media (max-width:56rem) { .main { padding:var(--s5) var(--s4) var(--s7); } }

.head { display:flex; align-items:flex-start; gap:var(--s4); margin-bottom:var(--s5); }
.head .grow { flex:1; min-width:0; }
/* The kicker naming what this page is. Quiet enough that the name still leads,
   loud enough to answer "what am I looking at" without reading the breadcrumb. */
.head .kind { font-size:var(--t-xs); font-weight:600; letter-spacing:.08em;
              text-transform:uppercase; color:var(--n500); margin-bottom:var(--s1); }
.lede { color:var(--ink-2); font-size:var(--t-md); margin-top:var(--s1); }

/* ---- surfaces ----------------------------------------------------------- */
.card { background:var(--surface); border:1px solid var(--line);
         border-radius:var(--r-lg); box-shadow:var(--shadow-1); overflow:hidden; }
.card + .card { margin-top:var(--s5); }
.card-head { display:flex; align-items:center; gap:var(--s3);
              padding:var(--s4) var(--s5); border-bottom:1px solid var(--line); }
.card-head .grow { flex:1; }
.card-body { padding:var(--s5); }

.tiles { display:grid; gap:var(--s4);
         grid-template-columns:repeat(auto-fit,minmax(11rem,1fr)); margin-bottom:var(--s5); }
.tile { background:var(--surface); border:1px solid var(--line);
        border-radius:var(--r-lg); padding:var(--s4) var(--s5);
        box-shadow:var(--shadow-1); transition:box-shadow .15s ease, transform .15s ease; }
a.tile:hover { box-shadow:var(--shadow-2); transform:translateY(-1px); text-decoration:none; }
.tile .n { font-size:var(--t-2xl); font-weight:640; letter-spacing:-.03em; line-height:1.1; }
.tile .label { color:var(--ink-3); font-size:var(--t-sm); margin-top:var(--s1);
               text-transform:uppercase; letter-spacing:.06em; font-weight:600; }

/* ---- detail screens ------------------------------------------------------ */
.facts { display:grid; gap:var(--s4) var(--s6); margin:0;
         grid-template-columns:repeat(auto-fit,minmax(13rem,1fr)); }
.facts div { min-width:0; }
.facts dt { font-size:var(--t-xs); text-transform:uppercase; letter-spacing:.06em;
            font-weight:600; color:var(--ink-3); margin-bottom:var(--s1); }
.facts dd { margin:0; font-size:var(--t-base); overflow-wrap:anywhere; }

/* ---- tables ------------------------------------------------------------- */
.rows { width:100%; border-collapse:collapse; }
.rows th { text-align:left; font-size:var(--t-xs); text-transform:uppercase;
           letter-spacing:.06em; color:var(--ink-3); font-weight:600;
           padding:var(--s3) var(--s5); border-bottom:1px solid var(--line); }
.rows td { padding:var(--s3) var(--s5); border-bottom:1px solid var(--line);
           font-size:var(--t-md); vertical-align:middle; }
.rows tr:last-child td { border-bottom:0; }
.rows tbody tr { transition:background .1s ease; }
.rows tbody tr:hover { background:var(--n50); }
@media (prefers-color-scheme:dark){ .rows tbody tr:hover { background:var(--raised); } }
.rows .strong { font-weight:560; color:var(--ink); }
.rows .muted { color:var(--ink-3); }

/* A value beside its copy button.
   ALWAYS VISIBLE, not revealed on hover. Hiding them made a tidier table and a
   worse one: this page exists so somebody can put these two values into a form
   on Alarm.com, and a button you have to go looking for with the mouse is not
   the easy path — it is the same fiddly selection with an extra step. Quiet
   and small is how it stays out of the way instead. */
.copyable { display:flex; align-items:center; gap:var(--s2); }
.copyable .copy { font-size:var(--t-xs); padding:.1rem .4rem; color:var(--ink-3); }
.copyable .copy:hover { color:var(--ink); }

/* Small print under the value it qualifies. */
.sub { font-size:var(--t-xs); color:var(--ink-3); margin-top:.1rem; }
.mono { font-family:ui-monospace,SFMono-Regular,"SF Mono",Menlo,monospace;
        font-size:var(--t-sm); }

.empty { padding:var(--s7) var(--s5); text-align:center; color:var(--ink-3); }
.empty .big { font-size:var(--t-lg); color:var(--ink-2); font-weight:560;
              margin-bottom:var(--s2); letter-spacing:-.01em; }

/* ---- controls ----------------------------------------------------------- */
.btn {
  display:inline-flex; align-items:center; justify-content:center; gap:var(--s2);
  padding:.5rem .9rem; border:1px solid transparent; border-radius:var(--r-sm);
  background:var(--accent); color:var(--accent-ink);
  font:inherit; font-size:var(--t-md); font-weight:560; cursor:pointer;
  transition:filter .12s ease, box-shadow .12s ease, transform .06s ease;
  white-space:nowrap;
}
.btn:hover { filter:brightness(1.07); text-decoration:none; }
.btn:active { transform:translateY(1px); }
.btn.ghost { background:transparent; border-color:var(--line-strong); color:var(--ink); }
.btn.ghost:hover { background:var(--n100); filter:none; }
@media (prefers-color-scheme:dark){ .btn.ghost:hover { background:var(--raised); } }
.btn.quiet { background:transparent; color:var(--ink-2); padding:.35rem .5rem; }
.btn.quiet:hover { background:var(--n100); color:var(--ink); filter:none; }
@media (prefers-color-scheme:dark){ .btn.quiet:hover { background:var(--raised); } }
.btn.danger { background:transparent; border-color:var(--line-strong); color:var(--bad); }
.btn.danger:hover { background:color-mix(in srgb,var(--bad) 10%,transparent); filter:none; }
.btn:focus-visible, a:focus-visible, input:focus-visible, select:focus-visible {
  outline:none; box-shadow:var(--shadow-focus); border-color:var(--focus);
}

.field { display:flex; flex-direction:column; gap:var(--s2); margin-bottom:var(--s4); }
.field label { font-size:var(--t-sm); font-weight:560; color:var(--ink-2); }
.field .hint { font-size:var(--t-sm); color:var(--ink-3); line-height:1.45; }
input[type=text],input[type=email],input[type=password],select {
  width:100%; padding:.55rem .7rem; border:1px solid var(--line-strong);
  border-radius:var(--r-sm); background:var(--surface); color:var(--ink);
  font:inherit; font-size:var(--t-md); transition:border-color .12s ease, box-shadow .12s ease;
}
input::placeholder { color:var(--n400); }
.grid2 { display:grid; gap:0 var(--s4); grid-template-columns:1fr 1fr; }
@media (max-width:36rem){ .grid2 { grid-template-columns:1fr; } }
.actions { display:flex; gap:var(--s3); align-items:center; margin-top:var(--s5); }
.actions .spacer { flex:1; }

/* ---- badges ------------------------------------------------------------- */
.chip { display:inline-flex; align-items:center; gap:var(--s2);
        padding:.15rem .55rem; border-radius:var(--r-full);
        font-size:var(--t-xs); font-weight:600; letter-spacing:.01em;
        border:1px solid var(--line-strong); color:var(--ink-2); }
.chip .swatch { width:.5rem; height:.5rem; border-radius:var(--r-full); }
.chip.fire .swatch{background:var(--fire)} .chip.medical .swatch{background:var(--medical)}
.chip.intrusion .swatch{background:var(--intrusion)}
.chip.environmental .swatch{background:var(--environmental)}

/* An incident label. The hue comes from the row (--incident-hue), so two rows
   of the same incident match and the eye can pair them without reading the
   number. Saturation and lightness are fixed so every hue stays legible in both
   themes; only the hue varies. */
.chip.incident { border-color:hsl(var(--incident-hue) 45% 50% / .5); }
.chip.incident .swatch { background:hsl(var(--incident-hue) 55% 48%); }
@media (prefers-color-scheme:dark){
  .chip.incident .swatch { background:hsl(var(--incident-hue) 60% 62%); }
}
a.chip { text-decoration:none; }
a.chip:hover { border-color:var(--ink-3); color:var(--ink); }
/* The isolated view, where an incident's events finally sit together. */
.incident-head { display:flex; align-items:center; gap:var(--s3); flex-wrap:wrap; }

/* A row that has just arrived.
   THE ROW HAS TO GROW, or nothing has arrived — it has appeared. A table row
   takes its full height the instant it is inserted, so animating only opacity
   and a small offset leaves everything below it JUMPING down while the new
   row's contents fade in on the spot. The eye reads that as a redraw.
   So the cells animate their own padding and line-height from nothing to their
   natural values. The row's height follows, and every row beneath it slides
   down continuously for the whole half-second instead of teleporting once.
   Three things, each saying something the others cannot:
     the GROWTH says a row was inserted here and pushed the rest down;
     the FADING TINT says which rows are new for a few seconds after the
     movement is over and can no longer say anything at all. */
/* 1.55 is the body's line-height, written out as a number because the keyword
   "normal" cannot be interpolated — animating to it would snap at the end
   rather than ease, which is exactly the jump this exists to remove. */
@keyframes feed-grow {
  from { padding-top:0; padding-bottom:0; line-height:0; opacity:0; }
  55%  { opacity:1; }
  to   { padding-top:var(--s3); padding-bottom:var(--s3); line-height:1.55; opacity:1; }
}
@keyframes feed-settle {
  from { background:var(--accent-soft); }
  to   { background:transparent; }
}
/* cubic-bezier(.16,1,.3,1) decelerates hard and then eases the last few pixels,
   which is what makes it read as settling into place rather than stopping.
   The opacity finishes early, at 55%, so the text is legible while the row is
   still opening — the content arrives, then the space around it finishes. */
.rows tr.is-new > td {
  animation: feed-grow .6s cubic-bezier(.16,1,.3,1) both var(--stagger, 0ms);
}
.rows tr.is-new {
  animation: feed-settle 3s ease-out both var(--stagger, 0ms);
}
/* Motion is the point here, but it is not worth making somebody ill over. With
   reduced motion the tint still marks what is new; only the movement goes. */
@media (prefers-reduced-motion: reduce) {
  .rows tr.is-new > td { animation:none; }
  .rows tr.is-new { animation: feed-settle 3s ease-out both; }
}
/* The live beacon.
   A dot with a ring expanding out of it, slowly. It says the page is still
   asking — which is otherwise invisible, because a feed that has quietly
   stopped looks exactly like a feed with nothing to report.
   MOTION IS THE HEALTHY STATE, and its absence is the signal. When polling
   fails the ring stops and the dot turns amber, so a stalled feed is something
   the eye catches rather than something you have to read. */
.beacon { display:inline-flex; align-items:center; gap:var(--s2); }
.beacon-dot { position:relative; width:.5rem; height:.5rem; border-radius:var(--r-full);
              background:var(--ok); flex:none; }
.beacon-dot::after {
  content:""; position:absolute; inset:0; border-radius:var(--r-full);
  background:var(--ok); animation: beacon 2.6s ease-out infinite;
}
@keyframes beacon {
  0%   { opacity:.55; transform:scale(1); }
  70%  { opacity:0;   transform:scale(3.2); }
  100% { opacity:0;   transform:scale(3.2); }
}
.beacon[data-state="stalled"] .beacon-dot { background:var(--warn); }
.beacon[data-state="stalled"] .beacon-dot::after { animation:none; }
/* Without motion the ring cannot say anything, so the dot carries it alone —
   and the stalled colour still does its job. */
@media (prefers-reduced-motion: reduce) {
  .beacon-dot::after { animation:none; }
}

/* How many arrived while you were reading further down.
   It leaves on a fade. The count going away is not news, and something
   blinking out of the header pulls the eye to the one spot where nothing just
   happened — the opposite of what an attention cue is for. Arriving is still
   instant: that IS news.
   The duration is mirrored in feedscript.go, which hides the element once the
   fade has played; a transition cannot run on display:none. */
.feed-new { background:var(--accent-soft); color:var(--accent); border-radius:var(--r-full);
            padding:.15rem .55rem; font-size:var(--t-xs); font-weight:600;
            transition: opacity .45s ease-out; }
.feed-new[data-leaving] { opacity:0; }
.feed-new[hidden] { display:none; }
@media (prefers-reduced-motion: reduce) {
  .feed-new { transition:none; }
}

.note { display:flex; gap:var(--s3); padding:var(--s3) var(--s4);
        border-radius:var(--r-md); font-size:var(--t-md); line-height:1.5;
        border:1px solid var(--line-strong); background:var(--n100); color:var(--ink-2); }
@media (prefers-color-scheme:dark){ .note { background:var(--raised); } }
.note.bad { border-color:color-mix(in srgb,var(--bad) 45%,var(--line));
            background:color-mix(in srgb,var(--bad) 8%,transparent); color:var(--ink); }
.note strong { color:var(--ink); font-weight:600; }

/* ---- viewing as another user -------------------------------------------- */
/* Deliberately loud. Forgetting you are wearing someone else's face is the
   whole hazard of the feature, so it is warm-coloured, sticky, and the only
   element on any page allowed to look like this. */
.viewing { position:sticky; top:0; z-index:10; display:flex; align-items:center;
           gap:var(--s4); margin:calc(var(--s6) * -1) calc(var(--s6) * -1) var(--s5);
           padding:var(--s3) var(--s6); font-size:var(--t-md);
           background:color-mix(in srgb,var(--intrusion) 16%,var(--surface));
           border-bottom:2px solid var(--intrusion); color:var(--ink); }
.viewing .grow { flex:1; }
.viewing .btn { background:var(--intrusion); color:#fff; padding:.35rem .8rem; }
@media (max-width:56rem){ .viewing { margin:calc(var(--s5) * -1) calc(var(--s4) * -1) var(--s5);
                                     padding:var(--s3) var(--s4); } }

/* ---- sign in ------------------------------------------------------------ */
.signin { min-height:100vh; display:grid; place-items:center; padding:var(--s5); }
.signin .box { width:100%; max-width:23rem; }
.signin .brandline { display:flex; align-items:center; gap:var(--s3);
                     justify-content:center; margin-bottom:var(--s5);
                     font-size:var(--t-2xl); font-weight:650; letter-spacing:-.02em;
                     color:var(--ink); white-space:nowrap; }
/* Larger than the sidebar's, because on the sign-in page the mark IS the page:
   there is no navigation around it and nothing else to say what this is. */
.signin .mark-logo { width:4.4rem; height:3.3rem; }
.signin .card-body { padding:var(--s6); }
.signin h1 { font-size:var(--t-lg); margin-bottom:var(--s1); }
.signin .lede { margin-bottom:var(--s5); }
.signin .btn { width:100%; margin-top:var(--s2); }

/* ---- station enrolment (§11) -------------------------------------------- */
/* The code has to be readable across a room, by somebody holding a tablet and
   looking up at a laptop screen. That is the whole brief, and it is why the
   digits are the largest thing on the page rather than a form field. */
.card-foot { display:flex; gap:var(--s3); align-items:center;
             padding:var(--s4) var(--s5); border-top:1px solid var(--line); }
.rows td.right { text-align:right; }
.pair { display:flex; gap:var(--s6); align-items:center; flex-wrap:wrap;
        padding:var(--s6) var(--s5); }
.pair-code { flex:1; min-width:16rem; }
.pair-digits { font-family:ui-monospace,SFMono-Regular,"SF Mono",Menlo,monospace;
               font-size:clamp(2.2rem,7vw,3.4rem); font-weight:650;
               letter-spacing:.12em; color:var(--ink);
               margin:var(--s3) 0; line-height:1.1; }
.pair-qr { width:15rem; height:15rem; border-radius:var(--r-sm);
           background:#fff; padding:var(--s3); }
.pair-note { padding:0 var(--s5) var(--s5); max-width:40rem; }
.pair-row { display:flex; align-items:center; gap:var(--s4); flex-wrap:wrap; }
