/* Revised 2026-10-05: self hosted fonts, own select arrow, page links. */
/* Self hosted type. These are the exact latin woff2 files Google Fonts served,
   kept here so a page never waits on fonts.googleapis.com before it can paint.
   That stylesheet was a render blocker on every navigation in the app. */
@font-face {
  font-family: 'Fraunces';
  font-style: normal;
  font-weight: 450 700;
  font-display: swap;
  src: url(../assets/fonts/fraunces-var-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Figtree';
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url(../assets/fonts/figtree-var-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* ==========================================================================
   Gator admin, design system.
   --------------------------------------------------------------------------
   Built for one person holding a phone in a crawlspace, not for a desk. That
   still drives everything: 20px base text, 52px minimum tap targets, and the
   phone number rendered as the biggest, most obvious thing on every card,
   because calling back fast is the entire job.

   The look is liquid glass, applied with one rule that keeps it honest:

     GLASS IS THE CHROME, NEVER THE CONTENT.

   Blur and translucency go on the bar, the segmented nav, the map overlays and
   the ambient background. Anything carrying a name or a number sits on a near
   opaque plate with a specular top edge. It reads as glass and costs nothing
   in contrast, which matters more here than anywhere.

   Everything below is driven by the tokens in section 1. Re-skin the whole app
   by editing those and nothing else.

   Sections
     1  Tokens, light
     2  Tokens, dark
     3  Reset
     4  Surfaces, glass and plate
     5  Type
     6  Shell, bar and nav
     7  Controls
     8  Callouts
     9  Numbers, tiles and bars
    10  Charts
    11  Tables
    12  Leads
    13  Route
    14  Sign in
    15  Motion and responsive
   ========================================================================== */


/* --- 1. Tokens, light ---------------------------------------------------- */

:root {
  /* RON IS RED-GREEN COLOUR BLIND. He cannot tell greens from reds, and he
     sees blue well. So this app is blue and high contrast, unlike the green
     public site, and nothing here may carry meaning by red against green.
     The usable axes are blue against yellow, and light against dark. Every
     colour below was checked under protan and deutan simulation (Machado
     2009, full severity) before it went in. See CLAUDE.md, hard rule 9.

     Ground and ink. Ink is near black navy, and even the quietest text
     (ink-3) clears 8:1 on the ground. */
  --ground:        #F3F6FC;
  --ink:           #07122B;
  --ink-2:         #1C2B4A;
  --ink-3:         #3A4868;
  --hair:          rgba(7, 18, 43, .16);
  --hair-2:        rgba(7, 18, 43, .32);

  /* Surfaces. plate carries content, glass carries chrome. Near opaque, and
     every card edge is a visible line rather than a white highlight. */
  --plate:         rgba(255, 255, 255, .97);
  --plate-2:       rgba(255, 255, 255, .84);
  --glass:         rgba(255, 255, 255, .72);
  --glass-edge:    rgba(7, 18, 43, .18);
  --glass-spec:    rgba(255, 255, 255, .95);
  --shadow:        0 1px 2px rgba(6, 26, 70, .06), 0 10px 30px -8px rgba(6, 26, 70, .18);
  --shadow-lg:     0 2px 6px rgba(6, 26, 70, .08), 0 30px 70px -20px rgba(6, 26, 70, .30);

  /* Brand. Blue, not the public site's gator green, for the reason above.
     White on it is 7.2:1. */
  --brand:         #0A4FC2;
  --brand-deep:    #06307A;
  --brand-soft:    rgba(10, 79, 194, .12);
  --on-brand:      #ffffff;

  /* Semantic state. Good is blue, watch is gold, crit is crimson. Watch
     against crit is the pair that matters, and it holds deltaE 53 protan and
     31 deutan. Crit also renders solid rather than tinted, so it differs by
     shape as well as by colour. */
  --good:          #0A4FC2;
  --watch:         #8A6300;
  --crit:          #A0003C;
  --on-crit:       #ffffff;
  --good-bg:       rgba(10, 79, 194, .12);
  --watch-bg:      rgba(214, 160, 0, .20);
  --crit-bg:       rgba(160, 0, 60, .10);

  /* Crew and series colours. Four, fixed order, never cycled: blue, gold,
     charcoal, teal. Worst pair is charcoal against teal at deltaE 34 under
     both protan and deutan. All four hold white text at 4.8:1 or better,
     because the map pins and stop numbers print white on them. */
  --c1: #0A4FC2;
  --c2: #8A5800;
  --c3: #1E2433;
  --c4: #007C91;

  /* Map. Land is sand and water is blue, so the two rivers and the Atlantic
     separate on the one axis Ron sees best. */
  --land:          #EFEBE0;
  --land-2:        #E4DFD1;
  --water:         #9CC3EE;
  --water-2:       #7FAFE6;
  --ocean:         #5E93D6;
  --ocean-2:       #3F7AC4;
  --road:          rgba(7, 18, 43, .20);

  /* Type. Fraunces for figures and titles, because a dashboard about a family
     business founded in 1994 should not read like generic software. Figtree
     for everything else: humanist, round, and legible at size. */
  --font-display:  Fraunces, ui-serif, Georgia, "Times New Roman", serif;
  --font-body:     Figtree, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;

  --fs-2xs:  0.75rem;
  --fs-xs:   0.875rem;
  --fs-sm:   1rem;
  --fs-md:   1.125rem;
  --fs-base: 1.25rem;
  --fs-lg:   1.5rem;
  --fs-xl:   2rem;
  --fs-title: clamp(2rem, 4.5vw, 2.75rem);
  --fs-figure: clamp(2.25rem, 5.5vw, 3.25rem);

  /* Space. A 4px base, named by step rather than by size, so a layout change
     never means hunting through pixel values. */
  --sp-1: 0.25rem;
  --sp-2: 0.5rem;
  --sp-3: 0.75rem;
  --sp-4: 1rem;
  --sp-5: 1.25rem;
  --sp-6: 1.5rem;
  --sp-7: 2rem;
  --sp-8: 2.75rem;
  --sp-9: 4rem;

  /* Shape and size. */
  --r-sm:  10px;
  --r-md:  16px;
  --r-lg:  24px;
  --r-pill: 999px;
  --tap:   52px;          /* never go below this on anything Ron taps */
  --wrap:  1180px;
  --wrap-slim: 760px;
  --gutter: clamp(1rem, 4vw, 1.5rem);

  --ease: cubic-bezier(.32, .72, 0, 1);
}


/* --- 2. Tokens, dark ------------------------------------------------------

   Three states, not two. An explicit choice stamps data-theme on the root; the
   default "system" setting stamps nothing, and only prefers-color-scheme
   separates light from dark there. So the tokens are redefined twice: once
   behind the media query, guarded so an explicit light choice still wins, and
   once behind the stamp so the toggle wins in both directions.
   -------------------------------------------------------------------------- */

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --ground:      #050B18;
    --ink:         #F4F7FF;
    --ink-2:       #D3DDF2;
    --ink-3:       #AEBBD6;
    --hair:        rgba(244, 247, 255, .18);
    --hair-2:      rgba(244, 247, 255, .32);

    --plate:       rgba(20, 33, 60, .94);
    --plate-2:     rgba(20, 33, 60, .70);
    --glass:       rgba(20, 33, 60, .62);
    --glass-edge:  rgba(244, 247, 255, .20);
    --glass-spec:  rgba(255, 255, 255, .16);
    --shadow:      0 1px 2px rgba(0, 0, 0, .5), 0 10px 30px -8px rgba(0, 0, 0, .6);
    --shadow-lg:   0 2px 6px rgba(0, 0, 0, .5), 0 30px 70px -20px rgba(0, 0, 0, .75);

    --brand:       #6FAEFF;
    --brand-deep:  #B5D5FF;
    --brand-soft:  rgba(111, 174, 255, .18);
    --on-brand:    #04122B;

    --good:        #6FAEFF;
    --watch:       #F5C542;
    --crit:        #FF6B8F;
    --on-crit:     #1A0008;
    --good-bg:     rgba(111, 174, 255, .16);
    --watch-bg:    rgba(245, 197, 66, .16);
    --crit-bg:     rgba(255, 107, 143, .15);

    --c1: #1F5FD6;
    --c2: #A06A00;
    --c3: #4A5264;
    --c4: #4A8EE8;

    --land:        #262D3B;
    --land-2:      #2E3646;
    --water:       #2A62B0;
    --water-2:     #3874C4;
    --ocean:       #133A72;
    --ocean-2:     #0F3063;
    --road:        rgba(244, 247, 255, .16);
  }
}

:root[data-theme="dark"] {
  --ground:      #050B18;
  --ink:         #F4F7FF;
  --ink-2:       #D3DDF2;
  --ink-3:       #AEBBD6;
  --hair:        rgba(244, 247, 255, .18);
  --hair-2:      rgba(244, 247, 255, .32);

  --plate:       rgba(20, 33, 60, .94);
  --plate-2:     rgba(20, 33, 60, .70);
  --glass:       rgba(20, 33, 60, .62);
  --glass-edge:  rgba(244, 247, 255, .20);
  --glass-spec:  rgba(255, 255, 255, .16);
  --shadow:      0 1px 2px rgba(0, 0, 0, .5), 0 10px 30px -8px rgba(0, 0, 0, .6);
  --shadow-lg:   0 2px 6px rgba(0, 0, 0, .5), 0 30px 70px -20px rgba(0, 0, 0, .75);

  --brand:       #6FAEFF;
  --brand-deep:  #B5D5FF;
  --brand-soft:  rgba(111, 174, 255, .18);
  --on-brand:    #04122B;

  --good:        #6FAEFF;
  --watch:       #F5C542;
  --crit:        #FF6B8F;
  --on-crit:     #1A0008;
  --good-bg:     rgba(111, 174, 255, .16);
  --watch-bg:    rgba(245, 197, 66, .16);
  --crit-bg:     rgba(255, 107, 143, .15);

  --c1: #1F5FD6;
  --c2: #A06A00;
  --c3: #4A5264;
  --c4: #4A8EE8;

  --land:        #262D3B;
  --land-2:      #2E3646;
  --water:       #2A62B0;
  --water-2:     #3874C4;
  --ocean:       #133A72;
  --ocean-2:     #0F3063;
  --road:        rgba(244, 247, 255, .16);
}


/* Every single column grid in this file declares minmax(0, 1fr) rather than
   leaving the track implicit or writing a bare 1fr. An implicit track is sized
   to its content, so one long unbreakable string pushes the whole card wider
   than the viewport and the page scrolls sideways. This has now cost time on
   both sides of the repo, see CLAUDE.md. */

/* --- 3. Reset -------------------------------------------------------------
   Wrapped in :where() so every rule here sits at zero specificity. A reset
   that outranks a component class killed card padding on the public site once
   already, see CLAUDE.md. Not repeating it.
   -------------------------------------------------------------------------- */

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

:where(ul, ol) { margin: 0; padding: 0; list-style: none; }
:where(figure, blockquote, dl, dd, p, h1, h2, h3, h4) { margin: 0; }
:where(img, svg) { max-width: 100%; }
:where(button, input, select, textarea) { font: inherit; color: inherit; }

html {
  font-size: 100%;
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  font-family: var(--font-body);
  font-size: var(--fs-base);
  line-height: 1.5;
  color: var(--ink-2);

  background: var(--ground);

  /* clip, not hidden. overflow-x: hidden turns body into the scroll container
     and quietly breaks position: sticky on the bar. */
  overflow-x: clip;
}

/* The ambient field. Brevard abstracted: lagoon blue, Atlantic blue, sky, a
   little sand. Kept faint, because it sits behind text. It is what the glass
   surfaces refract, so it has to stay put while the page moves over it.
 *
 * A fixed element, not background-attachment: fixed on the body. That property
 * repaints the whole background on every scroll frame, which is expensive
 * anywhere and outright unreliable on iOS Safari, which is the browser this app
 * is mostly opened in. A fixed element gets its own compositing layer and costs
 * nothing to scroll past. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background:
    radial-gradient(60vw 50vw at 10%  4%,  rgba(10, 79, 194, .20),  transparent 62%),
    radial-gradient(66vw 54vw at 96% 16%,  rgba(62, 140, 230, .18), transparent 64%),
    radial-gradient(52vw 48vw at 82% 82%,  rgba(120, 160, 220, .16), transparent 62%),
    radial-gradient(56vw 50vw at 4%  92%,  rgba(217, 201, 163, .22), transparent 64%);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) body::before { opacity: .55; }
}
:root[data-theme="dark"] body::before { opacity: .55; }

a { color: var(--brand-deep); }

:focus-visible {
  outline: 3px solid var(--brand);
  outline-offset: 2px;
  border-radius: 8px;
}

.hidden { display: none !important; }

.visually-hidden,
.visually-hidden-label {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}


/* --- 4. Surfaces ---------------------------------------------------------- */

.glass,
.plate {
  position: relative;
  border-radius: var(--r-lg);
  border: 1px solid var(--glass-edge);
  box-shadow: var(--shadow), inset 0 1px 0 var(--glass-spec);
}
.plate { background: var(--plate); }

/* Only .glass actually blurs what is behind it, and the blurring surfaces are
   counted on one hand: the bar, the map header, the pin popover and the chart
   tooltip. That is the rule this whole stylesheet is built on, and it is also
   the reason the app stays smooth on a phone. A .plate is 88% opaque in light
   and sits directly on the ambient field in dark, so there is nothing sharp
   behind it for a blur to reveal, and fifteen full screen blur passes per
   scroll frame is a real cost for an effect nobody can see. */
.glass {
  background: var(--glass);
  backdrop-filter: blur(24px) saturate(180%);
  -webkit-backdrop-filter: blur(24px) saturate(180%);
}

/* The sheen. One diagonal pass at low opacity, clipped to the radius. This is
   the whole difference between "translucent box" and "glass". */
.glass::after,
.plate::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  background: linear-gradient(145deg, rgba(255, 255, 255, .28) 0%, rgba(255, 255, 255, 0) 38%);
  mix-blend-mode: soft-light;
}

.panel,
.card { padding: var(--sp-6); }

.card__h {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-3);
}
.card__h h2 { font-size: var(--fs-md); font-weight: 700; }


/* --- 5. Type -------------------------------------------------------------- */

h1, h2, h3 {
  color: var(--ink);
  line-height: 1.18;
  text-wrap: balance;
  font-weight: 700;
}

.display {
  font-family: var(--font-display);
  font-variation-settings: "opsz" 40, "SOFT" 28, "WONK" 0;
  font-weight: 600;
  letter-spacing: -.012em;
}

/* Figures get the display face at a large optical size. Tabular by default,
   because a number that shifts sideways as it updates is a number nobody
   trusts. */
.figure {
  font-family: var(--font-display);
  font-variation-settings: "opsz" 120, "SOFT" 40, "WONK" 0;
  font-weight: 600;
  font-variant-numeric: tabular-nums lining-nums;
  letter-spacing: -.03em;
  line-height: .92;
  color: var(--ink);
}

.eyebrow {
  display: block;
  font-size: var(--fs-2xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .1em;
  color: var(--ink-3);
}

.num { font-variant-numeric: tabular-nums lining-nums; }

.sub {
  margin-top: var(--sp-2);
  font-size: var(--fs-sm);
  color: var(--ink-3);
  line-height: 1.45;
}

.empty {
  text-align: center;
  color: var(--ink-3);
  font-size: var(--fs-sm);
  padding: var(--sp-8) var(--sp-4);
}

.foot {
  text-align: center;
  color: var(--ink-3);
  font-size: var(--fs-xs);
  padding: var(--sp-8) var(--sp-4) var(--sp-9);
}


/* --- 6. Shell ------------------------------------------------------------- */

.wrap {
  max-width: var(--wrap);
  margin: 0 auto;
  padding: 0 var(--gutter);
}
.wrap--slim { max-width: var(--wrap-slim); }

main { padding: var(--sp-6) 0 var(--sp-9); }

.bar {
  position: sticky;
  top: 0;
  z-index: 60;
  background: color-mix(in srgb, var(--ground) 62%, transparent);
  backdrop-filter: blur(28px) saturate(190%);
  -webkit-backdrop-filter: blur(28px) saturate(190%);
  border-bottom: 1px solid var(--hair);
}
.bar__inner {
  display: grid;
  grid-template-columns: 44px minmax(0, 1fr) 44px;
  align-items: center;
  gap: var(--sp-3);
  min-height: 68px;
  padding: var(--sp-2) var(--gutter);
  max-width: var(--wrap);
  margin: 0 auto;
}
/* An empty cell the same width as the theme button, so the tabs sit on the
   true centre of the bar rather than the centre of the space beside it. */
.bar__side { width: 44px; }
.bar__inner > .tabs { justify-self: center; }


/* Segmented nav. Real links, so a middle click still opens a tab, with a glass
   pill that slides to whichever one is current. */
.tabs {
  display: flex;
  position: relative;
  flex: none;
  gap: 2px;
  padding: 4px;
  border-radius: var(--r-pill);
  background: var(--plate-2);
  border: 1px solid var(--hair);
}
.tabs__pill {
  position: absolute;
  top: 4px; bottom: 4px; left: 4px;
  border-radius: var(--r-pill);
  background: var(--plate);
  border: 1px solid var(--glass-edge);
  box-shadow: 0 1px 3px rgba(6, 26, 70, .16), inset 0 1px 0 var(--glass-spec);
  transition: transform .42s var(--ease), width .42s var(--ease);
}
.tab {
  position: relative;
  z-index: 1;
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: var(--sp-2) var(--sp-4);
  border-radius: var(--r-pill);
  font-size: var(--fs-xs);
  font-weight: 650;
  color: var(--ink-2);
  text-decoration: none;
  transition: color .25s;
}
.tab:hover { color: var(--ink); }
/* The current tab is marked by colour and weight, not by the sliding pill
   alone, so it still reads when the pill is mid slide or hard to see. */
.tab[aria-current="page"] { color: var(--brand-deep); font-weight: 800; }

.icon-btn {
  appearance: none;
  flex: none;
  width: 44px; height: 44px;
  display: grid;
  place-items: center;
  border-radius: var(--r-pill);
  border: 1px solid var(--hair);
  background: var(--plate-2);
  color: var(--ink-2);
  cursor: pointer;
}
.icon-btn:hover { color: var(--ink); border-color: var(--hair-2); }

.head {
  display: flex;
  align-items: baseline;
  gap: var(--sp-4);
  flex-wrap: wrap;
  margin: var(--sp-2) 0 var(--sp-5);
}
.head h1 { font-size: var(--fs-title); }
.head p { color: var(--ink-3); font-size: var(--fs-sm); }

/* The bento. One column on a phone, two once there is room, with the wider
   track on the left for the things that need it. */
.bento {
  display: grid;
  gap: var(--sp-4);
  grid-template-columns: minmax(0, 1fr);
  align-items: start;
}
.bento__col { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--sp-4); align-items: start; }

@media (min-width: 980px) {
  .bento { grid-template-columns: minmax(0, 1.28fr) minmax(0, 1fr); }
}

.tools { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--sp-3); margin-bottom: var(--sp-5); justify-items: start; }
.tools > * { width: 100%; }
.tools > .chips { width: auto; }
/* Full width on a phone, where there is nothing to sit beside it. Capped once
   there is room, so a period select does not span a 1180px page. */
@media (min-width: 700px) { .tools > *:not(.chips) { max-width: 340px; } }


/* --- 7. Controls ---------------------------------------------------------- */

label {
  display: block;
  font-weight: 650;
  color: var(--ink);
  font-size: var(--fs-sm);
  margin-bottom: var(--sp-1);
}

/* Every text-like input, by exclusion rather than by listing the types. The
   list version missed type="tel" the moment the sign in page grew a phone
   field, and left it rendering as an unstyled browser default in the middle of
   a styled card. */
input:not([type="checkbox"]):not([type="radio"]):not([type="submit"]):not([type="button"]):not([type="file"]),
textarea,
select {
  width: 100%;
  min-height: var(--tap);
  padding: var(--sp-3) var(--sp-4);
  padding-right: calc(var(--sp-4) + 28px);
  font-size: var(--fs-md);
  color: var(--ink);
  border: 1px solid var(--hair-2);
  border-radius: var(--r-md);
  /* Draw the arrow ourselves. The native one sits in its own grey box on
     some browsers and never lines up with the padding. One mid grey chevron
     reads on both themes. */
  appearance: none;
  -webkit-appearance: none;
  background: var(--plate) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%238b94a7' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E") no-repeat right var(--sp-4) center / 16px 16px;
  cursor: pointer;
}
select::-ms-expand { display: none; }
input::placeholder { color: var(--ink-3); }
input:focus, textarea:focus, select:focus {
  outline: 3px solid var(--brand);
  outline-offset: 1px;
  border-color: var(--brand);
}

/* Six digits, spaced wide enough to check by eye. */
#code {
  letter-spacing: .4em;
  font-size: var(--fs-lg);
  text-align: center;
  font-variant-numeric: tabular-nums;
}

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  min-height: var(--tap);
  padding: 0 var(--sp-5);
  border: 1px solid transparent;
  border-radius: var(--r-md);
  font-size: var(--fs-sm);
  font-weight: 700;
  cursor: pointer;
  text-decoration: none;
  color: var(--on-brand);
  background: linear-gradient(170deg, var(--brand), color-mix(in srgb, var(--brand) 62%, #06307A));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .32), 0 4px 14px -5px rgba(6, 48, 122, .6);
  transition: filter .18s, transform .18s var(--ease);
}
.btn:hover { filter: brightness(1.06); transform: translateY(-1px); color: var(--on-brand); }
.btn:active { transform: translateY(0); }
.btn[disabled] { opacity: .55; cursor: default; transform: none; filter: none; }

.btn--wide { width: 100%; }

.btn--quiet {
  background: var(--plate-2);
  color: var(--ink);
  border-color: var(--hair-2);
  box-shadow: none;
}
.btn--quiet:hover { background: var(--plate); color: var(--brand-deep); filter: none; }

/* Chips do two jobs: a filter you press, and a static label. Both share the
   pill, only the pressed one takes brand fill. */
.chip {
  appearance: none;
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  min-height: 44px;
  padding: var(--sp-2) var(--sp-4);
  border-radius: var(--r-pill);
  border: 1px solid var(--hair);
  background: var(--plate-2);
  color: var(--ink-2);
  font-size: var(--fs-xs);
  font-weight: 650;
  cursor: pointer;
  transition: background .25s var(--ease), color .25s, border-color .25s;
}
.chip:hover { color: var(--ink); border-color: var(--hair-2); }
.chip[aria-pressed="true"] {
  background: var(--brand);
  border-color: transparent;
  color: var(--on-brand);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .3), 0 4px 12px -4px rgba(6, 48, 122, .5);
}
.chips { display: flex; flex-wrap: wrap; gap: var(--sp-2); }

/* Static state labels. Not buttons, so no min height and no pointer. */
.tag {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  padding: 5px 11px;
  border-radius: var(--r-pill);
  border: 1px solid transparent;
  font-size: var(--fs-2xs);
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
  white-space: nowrap;
}
.tag--good  { color: var(--good);  background: var(--good-bg);  border-color: color-mix(in srgb, var(--good) 24%, transparent); }
.tag--watch { color: var(--watch); background: var(--watch-bg); border-color: color-mix(in srgb, var(--watch) 26%, transparent); }
/* Crit is solid, the others are tinted. The difference is shape and weight,
   not only hue, so it survives colour blindness. */
.tag--crit  { color: var(--on-crit); background: var(--crit);   border-color: var(--crit); }
.tag--flat  { color: var(--ink-3); background: var(--plate-2);  border-color: var(--hair); }
.dot { width: 7px; height: 7px; border-radius: 50%; background: currentColor; flex: none; }

/* Segmented status control on a lead card. */
.segset {
  display: flex;
  gap: 4px;
  padding: 4px;
  border-radius: var(--r-pill);
  background: var(--plate-2);
  border: 1px solid var(--hair);
}
.segset button {
  flex: 1 1 0;
  appearance: none;
  border: 0;
  background: none;
  min-height: 44px;
  border-radius: var(--r-pill);
  font-size: var(--fs-xs);
  font-weight: 650;
  color: var(--ink-3);
  cursor: pointer;
  transition: background .25s var(--ease), color .25s;
}
.segset button[aria-pressed="true"] {
  background: var(--plate);
  color: var(--ink);
  box-shadow: 0 1px 3px rgba(6, 26, 70, .16), inset 0 1px 0 var(--glass-spec);
  font-weight: 800;
}

.msg {
  margin-top: var(--sp-4);
  padding: var(--sp-3) var(--sp-4);
  border-radius: var(--r-md);
  font-size: var(--fs-sm);
}
.msg--err { background: var(--crit-bg); color: var(--crit); }
.msg--ok  { background: var(--good-bg); color: var(--good); }


/* --- 8. Callouts ---------------------------------------------------------- */

/* Everything the app wants to say in words rather than in a chart lands here.
   A colour rail carries severity, the first line carries the point, and the
   rest carries the reasoning. */
.note {
  display: flex;
  gap: var(--sp-3);
  align-items: flex-start;
  padding: var(--sp-4) var(--sp-5);
  border-radius: var(--r-md);
  background: var(--plate-2);
}
.note + .note { margin-top: var(--sp-3); }
.note__rail {
  width: 4px;
  flex: none;
  align-self: stretch;
  min-height: 34px;
  border-radius: var(--r-pill);
  background: var(--ink-3);
}
.note__body { min-width: 0; }
.note strong { color: var(--ink); font-weight: 700; }
.note p { font-size: var(--fs-sm); line-height: 1.5; }
.note strong + p { margin-top: var(--sp-1); }

.note--good  { background: var(--good-bg); }
.note--good  .note__rail { background: var(--good); }
.note--watch { background: var(--watch-bg); }
.note--watch .note__rail { background: var(--watch); }
.note--bad,
.note--crit  { background: var(--crit-bg); }
.note--bad   .note__rail,
.note--crit  .note__rail { background: var(--crit); }
/* Under colour blindness the crit tint reads as a plain plate, in dark most of
   all. So crit also gets an outline and a thicker rail: shape, not hue. */
.note--bad,
.note--crit  { box-shadow: inset 0 0 0 2px var(--crit); }
.note--bad   .note__rail,
.note--crit  .note__rail { width: 6px; }
.note--info  .note__rail { background: var(--c2); }

.alert {
  margin-bottom: var(--sp-4);
  padding: var(--sp-4) var(--sp-5);
  border-radius: var(--r-md);
  background: var(--crit-bg);
  color: var(--crit);
  border: 1px solid color-mix(in srgb, var(--crit) 24%, transparent);
  font-size: var(--fs-sm);
  font-weight: 600;
}


/* --- 9. Numbers ----------------------------------------------------------- */

.stat__n { font-size: var(--fs-figure); }
.stat__n,
.tile .figure { display: block; }

.delta { font-weight: 650; font-size: var(--fs-sm); margin-top: var(--sp-2); }
.delta--up   { color: var(--good); }
.delta--down { color: var(--crit); }
.delta--flat { color: var(--ink-3); }

.strip {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--sp-3);
  margin-top: var(--sp-4);
}
.tile { padding: var(--sp-4); border-radius: var(--r-md); }
.tile .figure { font-size: var(--fs-xl); }
.tile .eyebrow { margin-top: var(--sp-1); }

/* Bar rows. The Wix "top traffic sources" shape, kept because it is the one
   chart Ron already knew how to read. */
.brow { padding: var(--sp-2) 0; }
.brow + .brow { border-top: 1px solid var(--hair); }
.brow__top {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--sp-3);
}
.brow__label { font-weight: 650; color: var(--ink); font-size: var(--fs-sm); }
/* A page name that opens the real page in a new tab. Underlined, because on
   this app a plain blue word is a label and an underline is the only honest
   sign that something is a link. */
.pagelink {
  display: inline-flex; align-items: center; gap: 5px;
  color: var(--ink); font-weight: 650; text-decoration: underline;
  text-decoration-color: var(--hair-2); text-underline-offset: 3px;
}
.pagelink svg { color: var(--ink-3); flex: none; }
.pagelink:hover { color: var(--brand-deep); text-decoration-color: currentColor; }
.pagelink:hover svg { color: currentColor; }
.brow__n { font-size: var(--fs-xs); color: var(--ink-3); font-variant-numeric: tabular-nums; }
.brow__track {
  height: 9px;
  margin-top: var(--sp-2);
  border-radius: var(--r-pill);
  background: var(--hair);
  overflow: hidden;
}
.brow__fill {
  height: 100%;
  border-radius: var(--r-pill);
  background: var(--brand);
  transition: width .7s var(--ease);
}


/* --- 10. Charts ----------------------------------------------------------- */

.chartbox { position: relative; margin-top: var(--sp-3); }
.chartbox svg { display: block; width: 100%; overflow: visible; }

.tip {
  position: absolute;
  z-index: 20;
  opacity: 0;
  pointer-events: none;
  transform: translate(-50%, -118%);
  transition: opacity .12s;
  padding: var(--sp-2) var(--sp-3);
  border-radius: var(--r-sm);
  white-space: nowrap;
  font-size: var(--fs-xs);
  line-height: 1.35;
  color: var(--ink);
  background: var(--plate);
  border: 1px solid var(--glass-edge);
  box-shadow: var(--shadow);
  backdrop-filter: blur(20px) saturate(180%);
  -webkit-backdrop-filter: blur(20px) saturate(180%);
}
.tip b { font-weight: 700; }
.tip span { color: var(--ink-3); }

/* A legend is present whenever there are two or more series, so identity is
   never carried by colour alone. */
.legend { display: flex; flex-wrap: wrap; gap: var(--sp-2) var(--sp-4); margin-top: var(--sp-3); }
.legend span { display: inline-flex; align-items: center; gap: var(--sp-2); font-size: var(--fs-xs); color: var(--ink-2); }
.legend i { width: 11px; height: 11px; border-radius: 4px; display: block; flex: none; }

.chart-note { margin-top: var(--sp-3); font-size: var(--fs-xs); color: var(--ink-3); line-height: 1.5; }


/* Long lists, on a phone.
 *
 * A twelve row breakdown is fine on a desktop where it is one glance. On a
 * phone it is a screen and a half of scrolling before you reach the next
 * section, and the rows past the fifth are almost never the reason anybody
 * opened the page. So they are folded away, with a control to see the rest.
 *
 * Done in CSS off a class rather than by rendering fewer rows, so the full list
 * is always in the DOM for a screen reader and for anybody on a desktop, and
 * rotating the phone does not need a re-render. */
.more {
  display: none;
  width: 100%;
  appearance: none;
  margin-top: var(--sp-2);
  padding: var(--sp-2);
  min-height: 44px;
  border: 0;
  border-top: 1px solid var(--hair);
  border-radius: 0 0 var(--r-sm) var(--r-sm);
  background: none;
  color: var(--ink-3);
  font-size: var(--fs-xs);
  font-weight: 650;
  cursor: pointer;
}
.more:hover { color: var(--ink); }
.more svg { display: block; margin: 2px auto 0; transition: transform .3s var(--ease); }
.more[aria-expanded="true"] svg { transform: rotate(180deg); }

@media (max-width: 700px) {
  .more { display: block; }
  .clamp[data-open="false"] .brow:nth-of-type(n+6) { display: none; }
}
/* Tables fold at every width. A search term table runs to 25 rows and the
   first six carry the story; the rest sit behind "Show all". Bar lists only
   fold on a phone, where they are the thing that makes the page long. */
.tablewrap + .more { display: block; }
.clamp--table[data-open="false"] tbody tr:nth-child(n+7) { display: none; }

/* Handing over the Housecall Pro export. A file picker dressed as a control,
   because a bare input[type=file] looks like a bug next to everything else. */
.upload { margin-top: var(--sp-4); }
.upload input[type="file"] { display: none; }
.upload__row { display: flex; flex-wrap: wrap; gap: var(--sp-2); align-items: center; }
.upload__hint { font-size: var(--fs-xs); color: var(--ink-3); line-height: 1.5; margin-top: var(--sp-2); }
.upload__hint ol { margin: var(--sp-2) 0 0; padding-left: 1.1em; list-style: decimal; }
.upload__hint li { margin-bottom: 2px; }

/* --- 11. Tables ----------------------------------------------------------- */

/* Ron reads this on a phone, so a wide table scrolls inside its own panel
   rather than making the whole page scroll sideways. */
.tablewrap { overflow-x: auto; -webkit-overflow-scrolling: touch; margin-top: var(--sp-3); }
.tbl { width: 100%; border-collapse: collapse; font-size: var(--fs-sm); }
.tbl th, .tbl td { padding: var(--sp-3); text-align: left; white-space: nowrap; }
.tbl th {
  color: var(--ink-3);
  font-weight: 650;
  font-size: var(--fs-2xs);
  text-transform: uppercase;
  letter-spacing: .05em;
  border-bottom: 1px solid var(--hair-2);
}
.tbl td { border-bottom: 1px solid var(--hair); color: var(--ink); }
.tbl tr:last-child td { border-bottom: none; }
.tbl .num { text-align: right; }
.tbl .q { white-space: normal; min-width: 190px; font-weight: 650; }


/* --- 12. Leads ------------------------------------------------------------ */

/* On a wide screen js/dashboard.js splits the list into two .leads__col
   columns and drops each card under the shorter one, so cards pack tight like
   clips on a timeline. A plain two column grid shares rows across the columns
   and leaves a hole under every short card. */
.leads { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--sp-4); align-items: start; }
@media (min-width: 980px) {
  .leads { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
.leads__col { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--sp-4); align-items: start; }

.lead { padding: var(--sp-5); }
/* Every block inside a card gets the same breathing room, so a callout under
   the name never sits flush against the labels above it. The two tighter pairs
   are a second callout under a first and the quiet link under the call button. */
.lead > * + * { margin-top: var(--sp-4); }
.lead > .note + .note { margin-top: var(--sp-3); }
.lead > .call + .hcplink { margin-top: var(--sp-3); }
.lead__top {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: var(--sp-3);
}
.lead__name { font-size: var(--fs-lg); font-weight: 700; color: var(--ink); line-height: 1.2; }
.lead__meta { display: flex; flex-wrap: wrap; gap: var(--sp-2); align-items: center; margin-top: var(--sp-2); }
.lead__when { color: var(--ink-3); font-size: var(--fs-xs); white-space: nowrap; }

/* The single most important control in the whole app. */
.call {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-3);
  min-height: 60px;
  margin-top: var(--sp-4);
  padding: 0 var(--sp-5);
  border-radius: var(--r-md);
  text-decoration: none;
  font-size: var(--fs-lg);
  font-weight: 750;
  letter-spacing: .01em;
  color: var(--on-brand);
  background: linear-gradient(170deg, var(--brand), color-mix(in srgb, var(--brand) 62%, #06307A));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .4), 0 6px 18px -6px rgba(6, 48, 122, .65);
  transition: filter .18s, transform .18s var(--ease);
}
.call:hover { filter: brightness(1.06); transform: translateY(-1px); color: var(--on-brand); }
.call:active { transform: translateY(0); }
.call svg { flex: none; }

/* A compact call button, for places where calling is one option among several
   rather than the whole point of the card: a stop in the route list, the pin
   popover. Still 44px, still the only solid blue thing in reach. */
.call--sm {
  width: 100%;
  align-self: stretch;
  min-height: 44px;
  margin-top: var(--sp-3);
  padding: 0 var(--sp-4);
  font-size: var(--fs-sm);
  border-radius: var(--r-sm);
}

.facts {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 3px var(--sp-4);
  margin-top: var(--sp-4);
  font-size: var(--fs-sm);
}
.facts dt { color: var(--ink-3); }
.facts dd { color: var(--ink); }

.quote {
  margin-top: var(--sp-4);
  padding: var(--sp-3) var(--sp-4);
  border-radius: var(--r-sm);
  background: var(--brand-soft);
  color: var(--ink);
  font-size: var(--fs-sm);
  line-height: 1.5;
}

.lead .segset { margin-top: var(--sp-4); }
/* Six statuses since Spam joined (2026-10-08). On a phone they go to two rows
   of three rather than squeezing "Contacted" into 50px. */
@media (max-width: 560px) {
  .lead .segset { flex-wrap: wrap; border-radius: var(--r-md, 18px); }
  .lead .segset button { flex: 1 1 30%; }
}
.hcplink { width: 100%; margin-top: var(--sp-3); }

.lead__tools { display: flex; align-items: center; gap: var(--sp-3); flex: none; }

/* A quiet control. Deliberately not a .btn: editing is a rare, secondary act
   and it should not compete with the phone number. */
.iconlink {
  appearance: none;
  display: grid;
  place-items: center;
  width: 44px; height: 44px;
  flex: none;
  border-radius: var(--r-sm);
  border: 1px solid transparent;
  background: none;
  color: var(--ink-3);
  cursor: pointer;
}
.iconlink:hover { color: var(--ink); background: var(--plate-2); border-color: var(--hair); }

.editform { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--sp-3); margin-top: var(--sp-4); }
.editform label { margin-bottom: var(--sp-1); }
.editform__actions { display: flex; gap: var(--sp-2); margin-top: var(--sp-1); }
.editform__actions .btn { flex: 1 1 auto; }

/* The callback panel links to the filtered list.
 *
 * The panel cannot itself be an anchor, because it contains one: the call
 * button. So the "see all in Leads" line is the real link and a pseudo element
 * stretches it over the whole card. One link, one accessible name, the whole
 * card tappable, and the call button raised above it so it still wins the tap. */
.callback { position: relative; }
.callback__more {
  display: inline-flex; align-items: center; gap: var(--sp-2);
  margin-top: var(--sp-4);
  font-size: var(--fs-xs); font-weight: 650; color: var(--brand-deep);
  text-decoration: none;
}
.callback__more::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
}
.callback__more:hover { text-decoration: underline; }
.callback:has(.callback__more:hover) { background: color-mix(in srgb, var(--plate) 92%, var(--brand)); }
.callback .call,
.callback .strip { position: relative; z-index: 1; }
.callback__more:focus-visible::after { outline: 3px solid var(--brand); outline-offset: 4px; }

/* The waiting ring. Fills as an uncalled lead ages, because speed to call back
   is the difference between an enquiry and a job. */
.ring { position: relative; width: 108px; height: 108px; flex: none; }
.ring svg { width: 100%; height: 100%; transform: rotate(-90deg); }
.ring__mid { position: absolute; inset: 0; display: grid; place-content: center; text-align: center; }
.ring__n { font-size: var(--fs-xl); }
.ring__u {
  margin-top: 2px;
  font-size: var(--fs-2xs);
  font-weight: 700;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--ink-3);
}

.callback { padding: var(--sp-6); }
.callback__row { display: flex; gap: var(--sp-5); align-items: center; margin-top: var(--sp-4); }
.callback__who { font-size: var(--fs-lg); font-weight: 750; color: var(--ink); line-height: 1.2; }
.callback__what { margin-top: 3px; font-size: var(--fs-sm); color: var(--ink-2); }


/* --- 13. Route ------------------------------------------------------------ */

.routegrid { display: grid; gap: var(--sp-4); grid-template-columns: minmax(0, 1fr); align-items: start; }
@media (min-width: 980px) {
  .routegrid { grid-template-columns: minmax(0, 440px) minmax(0, 1fr); }
  .mapcol { position: sticky; top: 84px; }
}
.mapcol { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--sp-4); align-items: start; }

/* The card must not clip, or the pin popover gets cut off at the edge. The map
   clips itself instead. */
.mapcard { padding: 0; position: relative; }
.mapcard svg.map {
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--r-lg);
  clip-path: inset(0 round var(--r-lg));
}

.maphead {
  position: absolute;
  z-index: 5;
  top: var(--sp-3); left: var(--sp-3); right: var(--sp-3);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-4);
  border-radius: var(--r-md);
  background: var(--glass);
  border: 1px solid var(--glass-edge);
  box-shadow: var(--shadow), inset 0 1px 0 var(--glass-spec);
  backdrop-filter: blur(22px) saturate(180%);
  -webkit-backdrop-filter: blur(22px) saturate(180%);
}
.maphead__t { font-size: var(--fs-sm); font-weight: 700; color: var(--ink); }
.maphead__s { font-size: var(--fs-xs); color: var(--ink-3); }

.pinhit { cursor: pointer; }
.leg, .pinhit { transition: opacity .3s; }

/* Which route the map draws, and what that route means. The pressed side takes
   the brand fill, so the choice reads by weight and a text label, not by hue. */
.routemode { padding: var(--sp-3); }
.segset--strong button { padding: var(--sp-1) var(--sp-3); line-height: 1.25; }
.segset--strong button[aria-pressed="true"] { background: var(--brand); color: var(--on-brand); }
.explain { padding: var(--sp-3) var(--sp-2) var(--sp-1); }
.explain__lead { font-size: var(--fs-sm); line-height: 1.5; color: var(--ink-2); }
.explain__foot { margin-top: var(--sp-4); font-size: var(--fs-xs); line-height: 1.5; color: var(--ink-3); }
.xcrew { margin-top: var(--sp-4); }
.xcrew__h { display: flex; align-items: center; gap: var(--sp-2); }
.xcrew__h i { width: 11px; height: 11px; border-radius: 50%; flex: none; display: block; }
.xcrew__h b { font-size: var(--fs-sm); font-weight: 700; color: var(--ink); }
.xcrew p { margin-top: var(--sp-1); font-size: var(--fs-sm); line-height: 1.5; color: var(--ink-2); }
.xorder {
  list-style: none;
  margin: var(--sp-3) 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 6px;
}
.xorder li { display: flex; align-items: center; gap: var(--sp-2); min-width: 0; font-size: var(--fs-sm); color: var(--ink); }
.xorder li > span:nth-child(2) { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.xorder em { margin-left: auto; flex: none; font-style: normal; font-size: var(--fs-xs); color: var(--ink-3); }
.xorder__n {
  width: 22px; height: 22px;
  flex: none;
  display: grid;
  place-items: center;
  border-radius: 50%;
  color: #fff;
  font-size: 11px;
  font-weight: 750;
}

.pop {
  position: absolute;
  z-index: 30;
  width: 236px;
  padding: var(--sp-4);
  border-radius: var(--r-md);
  background: var(--plate);
  border: 1px solid var(--glass-edge);
  box-shadow: var(--shadow-lg), inset 0 1px 0 var(--glass-spec);
  backdrop-filter: blur(24px) saturate(180%);
  -webkit-backdrop-filter: blur(24px) saturate(180%);
  transform: translate(-50%, -108%);
  opacity: 0;
  pointer-events: none;
  transition: opacity .18s var(--ease), transform .18s var(--ease);
}
.pop.on { opacity: 1; pointer-events: auto; transform: translate(-50%, -114%); }
.pop b { display: block; color: var(--ink); font-size: var(--fs-sm); font-weight: 750; }
.pop small { display: block; color: var(--ink-3); font-size: var(--fs-xs); margin-top: 2px; }
.pop .call--sm { width: 100%; align-self: stretch; justify-content: center; }

.crew { display: flex; flex-wrap: wrap; gap: var(--sp-2); padding: var(--sp-3); }
.crewpill {
  appearance: none;
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  min-height: 44px;
  padding: var(--sp-2) var(--sp-4) var(--sp-2) var(--sp-3);
  border-radius: var(--r-pill);
  border: 1px solid var(--hair);
  background: var(--plate);
  color: var(--ink);
  font-size: var(--fs-xs);
  font-weight: 650;
  cursor: pointer;
  box-shadow: inset 0 1px 0 var(--glass-spec);
  transition: opacity .25s var(--ease), transform .18s var(--ease);
}
.crewpill[aria-pressed="false"] { opacity: .4; }
.crewpill:hover { transform: translateY(-1px); }
.crewpill i { width: 12px; height: 12px; border-radius: 50%; flex: none; display: block; }
.crewpill em { font-style: normal; color: var(--ink-3); font-weight: 600; }

/* The day rail. Solid is a job, dotted is time in the truck or waiting. Four
   of these stacked show a gap that no list of times ever will. */
.rail { padding: var(--sp-5) var(--sp-5) var(--sp-6); }
.railrow { margin-top: var(--sp-4); }
.railrow__h { display: flex; align-items: baseline; gap: var(--sp-2); flex-wrap: wrap; margin-bottom: var(--sp-2); }
.railrow__h i { width: 11px; height: 11px; border-radius: 50%; flex: none; display: block; }
.railrow__h b { font-size: var(--fs-sm); font-weight: 700; color: var(--ink); }
.railrow__h em { font-style: normal; font-size: var(--fs-xs); color: var(--ink-3); margin-left: auto; min-width: 0; }
.railtrack {
  position: relative;
  height: 26px;
  border-radius: var(--r-sm);
  background: var(--plate-2);
  border: 1px solid var(--hair);
  overflow: hidden;
}
.railblk { position: absolute; top: 3px; bottom: 3px; border-radius: 5px; }
/* A job with no pin keeps its crew's colour and changes shape instead. Gold
   used to mean "not on the map", and gold is also the second crew. */
.railblk--off {
  border: 2px solid currentColor;
  background: repeating-linear-gradient(135deg, currentColor 0 3px, transparent 3px 7px);
}
.railgap {
  position: absolute; top: 9px; bottom: 9px;
  border-radius: 3px;
  background: repeating-linear-gradient(90deg, var(--hair-2) 0 3px, transparent 3px 7px);
}
.railscale {
  display: flex;
  justify-content: space-between;
  margin-top: var(--sp-2);
  font-size: var(--fs-2xs);
  color: var(--ink-3);
}

/* Suggestions. Each one says what to change, why, and what it is worth. */
.sugg { padding: var(--sp-5); }
.sugg + .sugg { margin-top: var(--sp-3); }
.sugg__top { display: flex; gap: var(--sp-3); align-items: center; flex-wrap: wrap; }
.sugg__when { font-size: var(--fs-md); font-weight: 750; color: var(--ink); }
.sugg p { margin-top: var(--sp-3); font-size: var(--fs-sm); line-height: 1.5; }
.sugg__gain {
  display: inline-flex;
  align-items: baseline;
  gap: var(--sp-2);
  margin-top: var(--sp-4);
  padding: var(--sp-2) var(--sp-4);
  border-radius: var(--r-sm);
  background: var(--good-bg);
  border: 1px solid color-mix(in srgb, var(--good) 22%, transparent);
}
.sugg__gain b {
  font-family: var(--font-display);
  font-variation-settings: "opsz" 60, "SOFT" 40;
  font-weight: 650;
  font-size: var(--fs-md);
  color: var(--good);
  font-variant-numeric: tabular-nums;
}
.sugg__gain span { font-size: var(--fs-xs); color: var(--ink-2); }

.stops { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--sp-3); margin-top: var(--sp-4); }
.stop {
  display: flex;
  gap: var(--sp-4);
  align-items: flex-start;
  padding: var(--sp-4);
  border-radius: var(--r-md);
}
.stop__n {
  flex: none;
  width: 32px; height: 32px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  font-size: var(--fs-xs);
  font-weight: 750;
  color: #fff;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .35);
}
.stop__body {
  min-width: 0;
  /* Fill the row. Without this the body is sized to its own content, so the
     stop with the longest plan name gets a wider body and a visibly longer
     call button than every other stop. */
  flex: 1 1 auto;
}
.stop__line { display: flex; gap: var(--sp-3); align-items: baseline; flex-wrap: wrap; }
.stop__time { font-weight: 750; color: var(--ink); font-size: var(--fs-sm); font-variant-numeric: tabular-nums; }
.stop__who { font-weight: 650; color: var(--ink); font-size: var(--fs-sm); }
.stop__where { color: var(--ink-2); font-size: var(--fs-xs); }
.stop__meta { color: var(--ink-3); font-size: var(--fs-xs); margin-top: 3px; }


/* --- 14. Sign in ---------------------------------------------------------- */

.signin {
  min-height: 100svh;
  display: grid;
  place-items: center;
  padding: var(--sp-6) var(--gutter);
}
.signin .card {
  width: 100%;
  max-width: 460px;
  padding: var(--sp-7) var(--sp-6);
}
.card__logo {
  width: 100%;
  max-width: 260px;
  aspect-ratio: 671 / 96;
  margin-bottom: var(--sp-5);
  margin-inline: auto;
  background: url("../assets/img/logo-on-light.png") no-repeat center / contain;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .card__logo {
    background-image: url("../assets/img/logo-on-dark.png");
  }
}
:root[data-theme="dark"] .card__logo {
  background-image: url("../assets/img/logo-on-dark.png");
}
.signin h1 { font-size: var(--fs-xl); margin-bottom: var(--sp-2); text-align: center; }
.signin .hint { text-align: center; }
.hint { color: var(--ink-3); font-size: var(--fs-sm); margin-bottom: var(--sp-5); }
.signin .btn { width: 100%; margin-top: var(--sp-4); }
.signin label { margin-top: var(--sp-4); }


/* --- 15. Motion and responsive -------------------------------------------- */

.rise { animation: rise .7s var(--ease) both; }
@keyframes rise {
  from { opacity: 0; transform: translateY(14px); }
  to   { opacity: 1; transform: none; }
}

@media (max-width: 560px) {
  .bar__inner { gap: var(--sp-2); padding-inline: var(--sp-3); }
  .tab { padding: var(--sp-2) var(--sp-3); }
  .callback__row { flex-direction: column; align-items: flex-start; gap: var(--sp-4); }
  .strip { gap: var(--sp-2); }
  .tile { padding: var(--sp-3); }
  .tile .figure { font-size: var(--fs-lg); }
  .card, .panel, .lead, .sugg, .rail, .callback { padding: var(--sp-4); }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
}
