/* Score — restaurant health inspections.
   Colour roles come from a validated palette: grade badges use the reserved
   status palette (good/warning/serious/critical) and are always rendered with
   the letter itself, so colour never carries the meaning alone. */

:root {
  color-scheme: light;
  --surface:      #fcfcfb;
  --plane:        #f9f9f7;
  --ink:          #0b0b0b;
  --ink-2:        #52514e;
  --ink-muted:    #6f6d66;
  --grid:         #e1e0d9;
  --axis:         #c3c2b7;
  --border:       rgba(11, 11, 11, .10);
  --shadow:       0 1px 2px rgba(11,11,11,.05), 0 8px 24px rgba(11,11,11,.07);
  /* Nudged down from #2a78d6, which was 4.19:1 on the page background -- the
     footer's source link is 12.5px, and small text needs 4.5. Dark mode's
     accent already clears it. */
  --accent:       #2670cc;
  --accent-wash:  rgba(38, 112, 204, .10);
  --wash:         rgba(11, 11, 11, .045);

  /* Status palette — fixed, never themed.
     A is darker than it looks like it wants to be: the score under the letter
     is 10px, which needs 4.5:1, and white on a brighter green tops out at
     3.35. The letter alone would have been fine; the number is what sets the
     floor, and dropping the number was never an option. */
  --grade-a: #088408;
  --grade-b: #fab219;
  --grade-c: #ec835a;
  --grade-u: #d03b3b;

  --r: 14px;
  --tap: 44px;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
    --surface:     #1a1a19;
    --plane:       #0d0d0d;
    --ink:         #ffffff;
    --ink-2:       #c3c2b7;
    --ink-muted:   #898781;
    --grid:        #2c2c2a;
    --axis:        #383835;
    --border:      rgba(255, 255, 255, .10);
    --shadow:      0 1px 2px rgba(0,0,0,.5), 0 8px 24px rgba(0,0,0,.45);
    --accent:      #3987e5;
    --accent-wash: rgba(57, 135, 229, .16);
    --wash:        rgba(255, 255, 255, .06);
  }
}

* { box-sizing: border-box; }

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

body {
  margin: 0;
  background: var(--plane);
  color: var(--ink);
  font: 400 16px/1.45 system-ui, -apple-system, "Segoe UI", sans-serif;
  overscroll-behavior-y: none;
  padding-bottom: env(safe-area-inset-bottom);
}

body.sheet-open { overflow: hidden; }

a { color: var(--accent); }

/* ------------------------------------------------------------- top bar --- */

/* The header is identity, not controls, so on a phone it scrolls away with the
   content instead of holding 150px of a 667px screen forever. Everything you
   actually operate lives in the dock at the bottom, where your thumb is. */
.topbar {
  z-index: 20;
  background: transparent;
  padding: max(10px, env(safe-area-inset-top)) 14px 10px;
}

.topbar-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}
/* Without this the subtitle refuses to shrink and shoves the button off the
   right edge of a 375px screen instead of ellipsising itself. */
.topbar-row > div { min-width: 0; }

.topbar h1 {
  margin: 0;
  font-size: 21px;
  font-weight: 640;
  letter-spacing: -.02em;
}

/* "Score" alone never said what was being scored. There is room for the answer
   now that the row is not competing with a search field and seven chips. */
.topbar-sub {
  margin: 1px 0 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  color: var(--ink-muted);
  font-size: 12.5px;
  letter-spacing: .005em;
}

.locate {
  flex: 0 0 auto;
  white-space: nowrap;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: var(--tap);
  padding: 0 12px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  font-size: 14px;
  font-weight: 550;
  cursor: pointer;
}
.locate:active { background: var(--wash); }
.locate[data-state="on"] { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 40%, var(--border)); }
.locate[data-state="busy"] svg { animation: spin 1s linear infinite; }
.locate svg { width: 17px; height: 17px; fill: none; stroke: currentColor; stroke-width: 1.9; stroke-linecap: round; }

@keyframes spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .locate[data-state="busy"] svg { animation: none; } }

#search {
  width: 100%;
  height: var(--tap);
  padding: 0 14px 0 38px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  font-size: 16px; /* 16px keeps iOS from zooming on focus */
}
#search:focus { outline: 2px solid var(--accent); outline-offset: -1px; }
#search::-webkit-search-cancel-button { -webkit-appearance: none; }

/* In the sheet now, so they can wrap and be the size a finger needs. The row
   in the header was 32px tall against this file's own --tap: 44px, and 189px
   of it hung off the right edge of a 375px screen. */
.chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 0 0 4px;
}
.chips::-webkit-scrollbar { display: none; }

.chip {
  min-height: var(--tap);
  padding: 0 15px;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--surface);
  color: var(--ink-2);
  font: inherit;
  font-size: 14.5px;
  font-weight: 550;
  white-space: nowrap;
  cursor: pointer;
}
.chip.is-on {
  background: var(--accent-wash);
  border-color: color-mix(in srgb, var(--accent) 42%, var(--border));
  color: var(--accent);
}
.chip-star { width: 13px; height: 13px; fill: none; stroke: currentColor; stroke-width: 1.9; stroke-linejoin: round; }
.chip.is-on .chip-star { fill: currentColor; }
.chips-sep { flex: 0 0 auto; width: 1px; margin: 6px 3px; background: var(--border); }

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

main { padding: 0 14px; }

.status, .empty {
  margin: 14px 2px;
  color: var(--ink-2);
  font-size: 14px;
}
.empty { text-align: center; padding: 32px 0; }

.list { list-style: none; margin: 12px 0 0; padding: 0; display: grid; gap: 8px; }

.card {
  display: grid;
  grid-template-columns: 46px 1fr auto;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 11px 12px;
  border: 1px solid var(--border);
  border-radius: var(--r);
  background: var(--surface);
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.card:active { background: var(--wash); }

.badge {
  display: grid;
  place-items: center;
  width: 46px; height: 46px;
  border-radius: 12px;
  font-weight: 680;
  font-size: 19px;
  line-height: 1;
  color: #fff;
  letter-spacing: -.01em;
}
.badge small { display: block; font-size: 10px; font-weight: 600; margin-top: 2px; letter-spacing: 0; }
.badge[data-g="A"] { background: var(--grade-a); }
.badge[data-g="B"] { background: var(--grade-b); color: #0b0b0b; }
.badge[data-g="C"] { background: var(--grade-c); color: #0b0b0b; }
.badge[data-g="U"] { background: var(--grade-u); }

.card-main { min-width: 0; }
.card-name {
  display: block;
  font-weight: 580;
  font-size: 15.5px;
  letter-spacing: -.005em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.card-sub {
  display: block;
  margin-top: 2px;
  color: var(--ink-2);
  font-size: 13px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Distance leads. It is what the default sort is on and what the list is for;
   the age is context, so it is sized and coloured as context rather than
   competing at nearly the same weight. */
.card-meta { text-align: right; color: var(--ink-muted); font-size: 12px; }
.card-dist {
  display: block;
  font-weight: 650;
  color: var(--ink);
  font-size: 15px;
  font-variant-numeric: tabular-nums;
  letter-spacing: -.01em;
}
.card-age { display: block; margin-top: 2px; white-space: nowrap; }
/* The uncertainty mark. Deliberately not one of the grade colours -- those are
   fixed and mean a score -- and deliberately not red. This is a caveat about
   where a place is, not a warning about the food. */
.approx-ico {
  width: 1em; height: 1em;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  vertical-align: -.14em;
  flex: 0 0 auto;
}
/* Coarse on purpose. The viewBox is 24 units wide and this renders at 13px, so
   a fine dash pattern lands on sub-pixel gaps and fills in to a solid outline
   -- which reads as an ordinary pin, the opposite of the point. */
.approx-ico .approx-pin { stroke-dasharray: 4 3; }

.card-dist.is-approx { color: var(--ink-muted); font-weight: 500; }
.card-dist.is-approx .approx-ico {
  width: 13px; height: 13px;
  margin-right: 3px;
  stroke-width: 2.3;
}
.card-star { width: 13px; height: 13px; fill: currentColor; stroke: none; color: var(--ink-muted); vertical-align: -1px; margin-right: 3px; }

/* A button that reads as part of the sentence it sits in. */
.linkish {
  padding: 0;
  border: 0;
  background: none;
  color: var(--accent);
  font: inherit;
  font-weight: 600;
  text-decoration: underline;
  text-underline-offset: 2px;
  cursor: pointer;
}
.linkish:disabled { color: var(--ink-muted); text-decoration: none; cursor: default; }

.sentinel { height: 1px; }

/* Hold the page's full height until there are rows to hold it.
   Otherwise the footer renders just under the header, then the first batch of
   cards arrives and shoves it a screen and a half down the page -- which was
   the entire cumulative layout shift on mobile. :empty stops applying the
   moment the first row is appended, so nothing has to remember to undo it. */
#list:empty { display: block; min-height: 100dvh; }

.foot {
  margin: 26px 0 0;
  padding: 18px 16px calc(24px + env(safe-area-inset-bottom));
  border-top: 1px solid var(--border);
  color: var(--ink-muted);
  font-size: 12.5px;
  line-height: 1.5;
}
.foot p { margin: 0 0 6px; }

/* --------------------------------------------------------------- sheet --- */

.sheet { position: fixed; inset: 0; z-index: 50; }
.sheet-scrim { position: absolute; inset: 0; background: rgba(11,11,11,.42); }

.sheet-panel {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  max-height: 92dvh;
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border-radius: 20px 20px 0 0;
  box-shadow: var(--shadow);
  animation: rise .22s cubic-bezier(.22,.8,.3,1);
}
@keyframes rise { from { transform: translateY(14px); opacity: .4; } }
@media (prefers-reduced-motion: reduce) { .sheet-panel { animation: none; } }
@media (min-width: 640px) {
  .sheet-panel { left: 50%; transform: translateX(-50%); width: 560px; border-radius: 20px; bottom: 3dvh; }
}

.sheet-grab { height: 20px; display: grid; place-items: center; flex: 0 0 auto; }
.sheet-grab::before { content: ""; width: 38px; height: 4px; border-radius: 2px; background: var(--axis); }

.sheet-head { display: flex; gap: 12px; align-items: flex-start; padding: 2px 16px 0; }
.sheet-head h2 { margin: 0; font-size: 19px; font-weight: 640; letter-spacing: -.015em; }
.sheet-addr {
  display: inline-block;
  margin: 3px 0 0;
  color: var(--ink-2);
  font-size: 13.5px;
  text-decoration: none;
  border-bottom: 1px solid var(--border);
}
.sheet-sub { margin: 3px 0 0; color: var(--ink-muted); font-size: 13px; }
.sheet-addr::after { content: " ↗"; color: var(--ink-muted); font-size: 11px; }
.sheet-addr:active { color: var(--accent); }

.icon-btn {
  flex: 0 0 auto;
  width: var(--tap); height: var(--tap);
  display: grid; place-items: center;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: transparent;
  color: var(--ink-2);
  cursor: pointer;
}
.icon-btn svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; }

/* Above the actions, not below the map: the point is to be read before
   Directions is tapped, which is the moment the guess stops being harmless. */
.approx-note {
  display: flex;
  gap: 9px;
  align-items: flex-start;
  margin: 10px 16px 0;
  padding: 10px 12px;
  border: 1px solid color-mix(in srgb, var(--accent) 30%, var(--border));
  border-radius: 10px;
  background: var(--accent-wash);
  color: var(--ink-2);
  font-size: 12.5px;
  line-height: 1.45;
}
.approx-note[hidden] { display: none; }
.approx-note .approx-ico { width: 17px; height: 17px; margin-top: 1px; color: var(--accent); }
.approx-note b { color: var(--ink); font-weight: 650; }

.sheet-actions { display: flex; gap: 6px; padding: 12px 16px 4px; }
.action {
  flex: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  min-height: 40px;
  border: 1px solid var(--border);
  border-radius: 11px;
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  font-size: 14px;
  font-weight: 560;
  text-decoration: none;
  cursor: pointer;
}
.action:active { background: var(--wash); }
.action { min-width: 0; }
.action svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.9; stroke-linejoin: round; }
/* The Directions button carries the mark too. Wherever else the caveat is on
   screen, it should also be on the control that acts on it. */
.action svg .dir-q { display: none; stroke-linecap: round; }
.action.is-approx .dir-dot { display: none; }
.action.is-approx svg .dir-q { display: inline; }
.action.is-approx .dir-pin { stroke-dasharray: 4 3; }
.action[aria-pressed="true"] { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 42%, var(--border)); }
.action[aria-pressed="true"] svg { fill: currentColor; }

/* Each grade carries its own colour as a dot, not as the chip's fill: the
   letter is still what says which grade it is, exactly as on the badges. A
   chip that is off keeps the letter and loses the colour. */
.chip-grade { font-variant-numeric: tabular-nums; }
.chip-grade i {
  width: 10px; height: 10px;
  border-radius: 50%;
  background: var(--border);
}
.chip-grade.is-on i[data-g="A"] { background: var(--grade-a); }
.chip-grade.is-on i[data-g="B"] { background: var(--grade-b); }
.chip-grade.is-on i[data-g="C"] { background: var(--grade-c); }
.chip-grade.is-on i[data-g="U"] { background: var(--grade-u); }
.chip-grade:not(.is-on) { color: var(--ink-muted); }

.share-qr {
  display: grid;
  place-items: center;
  padding: 16px;
  border: 1px solid var(--border);
  border-radius: 14px;
  /* Always light: a scanner wants dark-on-light, and inverting a QR code is a
     reliable way to make half of them refuse to read it. */
  background: #fff;
}
.share-qr svg { width: min(240px, 62vw); height: auto; display: block; }
.share-qr p { margin: 0; color: #666; font-size: 13px; }
.share-link {
  margin: 10px 0 0;
  color: var(--ink-muted);
  font-size: 12px;
  line-height: 1.45;
  word-break: break-all;
}
.share-actions { display: flex; gap: 8px; margin-top: 12px; }

/* ---- coverage and crawl status ----------------------------------------- */

.cover-map { position: relative; height: 240px; border: 1px solid var(--border);
             border-radius: 12px; overflow: hidden; background: var(--wash); }
.cover-map-canvas { position: absolute; inset: 0; display: block; }
.cover-box { stroke: var(--accent); fill: var(--accent); }

/* Coverage as one cell per county.
   A bar was the obvious thing and the wrong one: three of 159 is a six-pixel
   sliver on a full-width track, which reads as a rendering fault rather than
   as a number. Cells make the missing 156 the visible part. */
.cover-bar { margin: 4px 0 0; }
.cover-cells { display: flex; flex-wrap: wrap; gap: 3px; }
.cover-cells i {
  width: 9px; height: 9px;
  border-radius: 2px;
  background: var(--wash);
  box-shadow: inset 0 0 0 1px var(--border);
}
.cover-cells i[data-on] {
  background: var(--accent);
  box-shadow: none;
}
.cover-note { margin: 8px 0 0; color: var(--ink-muted); font-size: 12.5px; }
.cover-note b { color: var(--ink); font-weight: 650; }

.run { display: flex; gap: 10px; align-items: flex-start; padding: 10px 0;
       border-bottom: 1px solid var(--border); }
.run:last-child { border-bottom: 0; }
.run-dot { flex: 0 0 auto; width: 9px; height: 9px; margin-top: 6px; border-radius: 50%; background: var(--ink-muted); }
.run-dot[data-s="success"]     { background: var(--grade-a); }
.run-dot[data-s="failure"]     { background: var(--grade-u); }
.run-dot[data-s="in_progress"] { background: var(--accent); animation: pulse 1.4s ease-in-out infinite; }
@keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: .25; } }
@media (prefers-reduced-motion: reduce) { .run-dot[data-s="in_progress"] { animation: none; } }
.run-main { flex: 1 1 auto; min-width: 0; }
.run-title { font-size: 14px; font-weight: 600; }
.run-meta { margin-top: 1px; color: var(--ink-muted); font-size: 12.5px; }
.run a { color: var(--accent); }

/* Steps, so a running crawl shows movement rather than a spinner. */
.steps { margin: 8px 0 0; display: grid; gap: 3px; }
.step { display: flex; gap: 8px; align-items: center; font-size: 12.5px; color: var(--ink-muted); }
.step b { flex: 0 0 auto; width: 14px; text-align: center; font-weight: 700; }
.step[data-s="in_progress"] { color: var(--accent); font-weight: 600; }
.step[data-s="completed"] { color: var(--ink-2); }
.step[data-s="failure"] { color: var(--grade-u); font-weight: 600; }

/* ---- scores in this area ------------------------------------------------ */

.stat-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 4px; }
.stat {
  padding: 12px 14px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--surface);
}
.stat b { display: block; font-size: 26px; font-weight: 660; letter-spacing: -.02em;
          font-variant-numeric: tabular-nums; }
.stat small { display: block; margin-top: 2px; color: var(--ink-muted); font-size: 12px; }

.dist { display: grid; gap: 8px; }
.dist-row { display: grid; grid-template-columns: 22px 1fr 62px; align-items: center; gap: 10px; }
.dist-key { font-weight: 680; font-size: 15px; text-align: center; }
.dist-key[data-g="A"] { color: var(--grade-a); }
.dist-key[data-g="B"] { color: var(--grade-b); }
.dist-key[data-g="C"] { color: var(--grade-c); }
.dist-key[data-g="U"] { color: var(--grade-u); }
.dist-track { height: 12px; border-radius: 999px; background: var(--wash); overflow: hidden; }
.dist-fill { display: block; height: 100%; border-radius: 999px; min-width: 2px; }
.dist-fill[data-g="A"] { background: var(--grade-a); }
.dist-fill[data-g="B"] { background: var(--grade-b); }
.dist-fill[data-g="C"] { background: var(--grade-c); }
.dist-fill[data-g="U"] { background: var(--grade-u); }
.dist-n { text-align: right; font-size: 13px; font-weight: 600; font-variant-numeric: tabular-nums; }
.dist-n small { margin-left: 5px; color: var(--ink-muted); font-weight: 500; }

/* Wide content scrolls inside its own box rather than the page. */
.table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.area-table { width: 100%; border-collapse: collapse; font-size: 13.5px; }
.area-table th, .area-table td { padding: 9px 8px; text-align: right; white-space: nowrap; }
.area-table th[scope="row"] { text-align: left; font-weight: 550; color: var(--ink); }
.area-table thead th {
  position: sticky; top: 0;
  background: var(--surface);
  color: var(--ink-muted);
  font-size: 11px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase;
  border-bottom: 1px solid var(--border);
}
.area-table tbody tr { border-bottom: 1px solid var(--border); }
.area-table td { font-variant-numeric: tabular-nums; color: var(--ink-2); }
.area-table td b { color: var(--ink); font-weight: 650; }

.menu-row {
  width: 100%;
  min-height: 60px;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 4px;
  border: 0;
  border-bottom: 1px solid var(--border);
  background: none;
  color: var(--ink);
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.menu-row:active { background: var(--wash); }
.menu-row > svg { flex: 0 0 auto; width: 21px; height: 21px; fill: none; stroke: currentColor;
                  stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; color: var(--ink-2); }
.menu-row span { flex: 1 1 auto; min-width: 0; }
.menu-row b { display: block; font-size: 15px; font-weight: 600; }
.menu-row small { display: block; margin-top: 1px; color: var(--ink-muted); font-size: 12.5px; }
.menu-go { flex: 0 0 auto; width: 16px; height: 16px; color: var(--ink-muted); stroke-width: 2; }

.sheet-hint { margin: 12px 0 0; color: var(--ink-muted); font-size: 12.5px; line-height: 1.5; }
.sheet-hint b { color: var(--ink-2); font-weight: 600; }
.sheet-compact .sec-title:first-child { margin-top: 0; }

.sheet-body { overflow-y: auto; -webkit-overflow-scrolling: touch; padding: 14px 16px calc(22px + env(safe-area-inset-bottom)); }

.sec-title {
  margin: 20px 0 8px;
  font-size: 11.5px;
  font-weight: 640;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ink-muted);
}
.sec-title:first-child { margin-top: 4px; }

/* score history chart */
.chart { width: 100%; height: auto; display: block; overflow: visible; touch-action: pan-y; }
.chart .band-label { font-size: 9px; fill: var(--ink-muted); font-weight: 600; }
.chart .tick { font-size: 10px; fill: var(--ink-muted); }
.chart .gridline { stroke: var(--grid); stroke-width: 1; }
.chart .baseline { stroke: var(--axis); stroke-width: 1; }
.chart .trend { fill: none; stroke: var(--ink-2); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.chart .dot { stroke: var(--surface); stroke-width: 2; }
.chart .dot-hit { fill: transparent; cursor: pointer; }
.chart .value-label { font-size: 11px; font-weight: 650; fill: var(--ink); }
.chart .band { opacity: .085; }

.history-note { margin: 6px 2px 0; color: var(--ink-muted); font-size: 12px; }

/* inspection list inside the sheet */
.insp { display: grid; gap: 6px; }
.insp-row {
  display: grid;
  grid-template-columns: 34px 1fr auto;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 9px 10px;
  border: 1px solid var(--border);
  border-radius: 11px;
  background: transparent;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.insp-row[aria-expanded="true"] { background: var(--wash); }
.insp-pill {
  display: grid; place-items: center;
  width: 34px; height: 26px;
  border-radius: 7px;
  font-size: 12.5px; font-weight: 680; color: #fff;
  font-variant-numeric: tabular-nums;
}
.insp-pill[data-g="A"] { background: var(--grade-a); }
.insp-pill[data-g="B"] { background: var(--grade-b); color: #0b0b0b; }
.insp-pill[data-g="C"] { background: var(--grade-c); color: #0b0b0b; }
.insp-pill[data-g="U"] { background: var(--grade-u); }
.insp-date { font-size: 14px; font-weight: 520; }
.insp-hint { color: var(--ink-muted); font-size: 12px; }

.detail { padding: 2px 4px 12px; }
.detail-loading { color: var(--ink-muted); font-size: 13px; padding: 8px 4px; }

.viol { list-style: none; margin: 6px 0 0; padding: 0; display: grid; gap: 5px; }
.viol li {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 8px;
  align-items: baseline;
  padding: 8px 10px;
  border-radius: 9px;
  background: var(--wash);
  font-size: 13.5px;
  line-height: 1.4;
}
.viol li.is-risk { background: color-mix(in srgb, var(--grade-u) 11%, transparent); }
.viol-code {
  font-variant-numeric: tabular-nums;
  font-weight: 650;
  font-size: 12px;
  color: var(--ink-2);
  white-space: nowrap;
}
.viol li.is-risk .viol-code { color: var(--grade-u); }
.viol-occ { color: var(--ink-muted); font-size: 12px; }

.risk-flag {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  margin: 8px 0 2px;
  padding: 7px 10px;
  border-radius: 9px;
  background: color-mix(in srgb, var(--grade-u) 12%, transparent);
  color: var(--grade-u);
  font-size: 12.5px;
  font-weight: 580;
}
.risk-flag svg { width: 14px; height: 14px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; flex: 0 0 auto; }

.clean-flag { color: var(--ink-2); font-size: 13px; padding: 6px 2px; }
.form-link { display: inline-block; margin-top: 10px; font-size: 13px; }

/* alerts */
.alerts {
  margin-top: 18px;
  padding: 12px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--wash);
}
.alerts h3 { margin: 0 0 4px; font-size: 14px; font-weight: 600; }
.alerts p { margin: 0 0 10px; color: var(--ink-2); font-size: 13px; line-height: 1.45; }
.alerts .action { flex: none; padding: 0 14px; }

/* ----------------------------------------------------- location picker --- */

.sheet-compact .sheet-body { padding-top: 16px; }

.action.wide { width: 100%; min-height: 46px; font-size: 15px; }

.loc-or {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 16px 0 12px;
  color: var(--ink-muted);
  font-size: 12.5px;
}
.loc-or::before, .loc-or::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--border);
}

.loc-form { display: flex; gap: 8px; }
.loc-form input {
  flex: 1;
  min-width: 0;
  height: 46px;
  padding: 0 13px;
  border: 1px solid var(--border);
  border-radius: 11px;
  background: var(--plane);
  color: var(--ink);
  font: inherit;
  font-size: 16px; /* keeps iOS from zooming on focus */
}
.loc-form input:focus { outline: 2px solid var(--accent); outline-offset: -1px; }
.loc-form .action { flex: 0 0 auto; padding: 0 18px; min-height: 46px; }

.loc-msg { margin: 10px 2px 0; min-height: 18px; color: var(--ink-2); font-size: 13px; }
.loc-msg.is-error { color: var(--grade-u); }

.freshness { color: var(--ink-2); font-variant-numeric: tabular-nums; }

/* ----------------------------------------------------------------- map --- */
/* Two panes over one dataset: the list and the map render the same filtered,
   sorted rows. On a phone they swap (the floating pill toggles); from 980px up
   there is room for both, so the toggle disappears and they sit side by side.

   --topbar-h is measured in app.js rather than hard-coded: the top bar grows
   with the safe-area inset and with wrapped chips, and the map has to start
   exactly below it or it either overlaps or leaves a gap. */

:root { --topbar-h: 132px; }

main.panes { padding: 0; }
.pane-list { padding: 0 14px; }

/* Full bleed under the dock. The header used to push the map down by 150px on
   a phone, which is 150px of the one thing the map view exists to show. */
.pane-map {
  position: fixed;
  inset: 0 0 calc(var(--dock-h) + env(safe-area-inset-bottom)) 0;
  display: none;
}
.map { width: 100%; height: 100%; background: var(--grid); }

/* Bottom-of-map furniture stacks upward: view pill, home button, note. */
.pane-map { --stack-1: calc(var(--dock-h) + 12px + env(safe-area-inset-bottom));
            --stack-2: calc(var(--dock-h) + 12px + env(safe-area-inset-bottom));
            --stack-3: calc(var(--dock-h) + 66px + env(safe-area-inset-bottom)); }

body[data-view="map"] .pane-list,
body[data-view="map"] .foot { display: none; }
body[data-view="map"] .pane-map { display: block; }
body[data-view="map"] .topbar { display: none; }
body[data-view="map"] { overflow: hidden; }

/* The pill sits over the content rather than in the chips row: the chips strip
   scrolls horizontally, and a primary navigation control must not be able to
   scroll out of reach. Bottom-centre is also the reachable half of a phone. */
/* The dock.
 *
 * Search, filters and the list/map switch, fixed to the bottom edge. The app is
 * used one-handed while standing in front of a restaurant, and on a 667px phone
 * the top 150px is the hardest place on the screen for a thumb to get to -- so
 * the controls that were up there are down here, and the content got the room
 * back. It is a bar rather than a floating pill because a pill overlapped the
 * rows it sat on top of. */
:root { --dock-h: 60px; }

.dock {
  position: fixed;
  z-index: 30;
  left: 0; right: 0; bottom: 0;
  padding: 6px 8px calc(4px + env(safe-area-inset-bottom));
  background: color-mix(in srgb, var(--plane) 92%, transparent);
  backdrop-filter: saturate(180%) blur(14px);
  -webkit-backdrop-filter: saturate(180%) blur(14px);
  border-top: 1px solid var(--border);
}
body.sheet-open .dock { display: none; }

/* Labelled, not just iconographic. The previous version of this bar carried a
   bare map glyph and the first thing it was asked was where the map had gone --
   which is the same complaint this layout was built to answer elsewhere. */
.dock-tabs { display: flex; align-items: stretch; gap: 4px; }

.dock-tab {
  flex: 1 1 0;
  position: relative;
  min-height: var(--tap);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  padding: 4px 2px;
  border: 0;
  border-radius: 10px;
  background: none;
  color: var(--ink-2);
  font: inherit;
  font-size: 11px;
  font-weight: 560;
  letter-spacing: .005em;
  cursor: pointer;
}
.dock-tab:active { background: var(--wash); }
.dock-tab svg { width: 21px; height: 21px; fill: none; stroke: currentColor;
                stroke-width: 1.85; stroke-linecap: round; stroke-linejoin: round; }
.dock-tab[aria-pressed="true"], .dock-tab[aria-expanded="true"] { color: var(--accent); }
.dock-tab .fab-list { display: none; }
.dock-tab[aria-pressed="true"] .fab-list { display: block; }
.dock-tab[aria-pressed="true"] .fab-map { display: none; }

/* A filter or a search that is on but out of sight is the problem this whole
   layout is fixing, so the tab says so. */
.dock-dot {
  position: absolute;
  top: 3px; right: calc(50% - 17px);
  width: 7px; height: 7px;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 0 2px var(--plane);
}
.dock-dot[hidden] { display: none; }

/* The field is summoned rather than permanent: four labelled tabs and a text
   input do not both fit across 375px without the labels going first. */
.dock-search { position: relative; display: flex; align-items: center; margin-bottom: 6px; }
.dock-search[hidden] { display: none; }
.dock-search .search-icon {
  position: absolute;
  left: 12px; top: 50%;
  transform: translateY(-50%);
  width: 18px; height: 18px;
  fill: none; stroke: var(--ink-muted); stroke-width: 2; stroke-linecap: round;
  pointer-events: none;
}
.search-done {
  flex: 0 0 auto;
  min-height: var(--tap);
  padding: 0 14px;
  border: 0;
  background: none;
  color: var(--accent);
  font: inherit;
  font-size: 15px;
  font-weight: 600;
  cursor: pointer;
}

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

/* Clear the dock. The old floating pill sat on top of the last rows instead. */
.foot { padding-bottom: calc(var(--dock-h) + 34px + env(safe-area-inset-bottom)); }

/* A count of what the map is actually showing. The map caps how many pins it
   draws, so this says so out loud rather than quietly dropping places. */
.map-note {
  position: absolute;
  z-index: 900;   /* over Leaflet's own controls, which sit at 800 */
  left: 10px; right: 10px;
  bottom: var(--stack-3);
  margin: 0;
  padding: 8px 12px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: color-mix(in srgb, var(--surface) 92%, transparent);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  box-shadow: var(--shadow);
  color: var(--ink-2);
  font-size: 12.5px;
  line-height: 1.4;
  text-align: center;
  pointer-events: none;
}

@media (min-width: 980px) {
  :root { --list-w: clamp(340px, 34vw, 440px); }
  main.panes {
    display: grid;
    grid-template-columns: var(--list-w) 1fr;
    gap: 0;
    align-items: start;
  }
  .pane-list { padding: 0 16px; }

  /* There is room for both panes here, so the map goes back to sitting beside
     the list rather than covering the screen, and the header -- which has no
     thumb-reach problem on a desktop -- pins to the top again. */
  .topbar {
    position: sticky;
    top: 0;
    background: color-mix(in srgb, var(--plane) 88%, transparent);
    backdrop-filter: saturate(180%) blur(14px);
    -webkit-backdrop-filter: saturate(180%) blur(14px);
    border-bottom: 1px solid var(--border);
  }
  body[data-view="map"] .topbar { display: block; }

  .pane-map, body[data-view="map"] .pane-map {
    display: block;
    position: sticky;
    inset: auto;
    top: var(--topbar-h);
    height: calc(100dvh - var(--topbar-h));
    border-left: 1px solid var(--border);
  }
  body[data-view="map"] .pane-list, body[data-view="map"] .foot { display: block; }
  body[data-view="map"] { overflow: visible; }

  /* The dock becomes a floating bar over the list column it belongs to, rather
     than straddling the seam between the two panes and covering a row of each. */
  .dock {
    left: 16px;
    right: auto;
    transform: none;
    width: min(408px, calc(var(--list-w) - 32px));
    bottom: 18px;
    padding: 8px;
    border: 1px solid var(--border);
    border-radius: 16px;
    box-shadow: var(--shadow);
  }
  .viewtoggle-wrap, #viewtoggle { display: none; }

  .pane-map { --stack-1: 18px; --stack-2: 18px; --stack-3: 72px; }
  .map-note { left: 16px; right: 16px; }
  .foot { grid-column: 1 / -1; padding-bottom: calc(96px + env(safe-area-inset-bottom)); }
}

/* -------------------------------------------------------------- leaflet --- */
/* Leaflet ships its own chrome; these bring it into the app's palette so the
   map doesn't read as a bolted-on iframe. */

.leaflet-container {
  font: inherit;
  font-size: 12px;
  background: var(--grid);
  outline-offset: -2px;
}
.leaflet-container a { color: var(--accent); }

/* OSM's raster tiles are a light-mode design. Inverting the tile pane alone
   (markers live in a different pane and are untouched) is the standard way to
   get a dark basemap without a second tile source and a second set of
   requests. The hue-rotate puts blues back to blue after the invert. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .leaflet-tile-pane {
    filter: invert(1) hue-rotate(180deg) brightness(.93) contrast(.9) saturate(.72);
  }
}

.leaflet-bar,
.leaflet-control-attribution {
  border: 1px solid var(--border) !important;
  border-radius: 10px !important;
  box-shadow: var(--shadow) !important;
  background: color-mix(in srgb, var(--surface) 92%, transparent) !important;
  color: var(--ink-2) !important;
  overflow: hidden;
}
.leaflet-control-attribution {
  padding: 3px 8px;
  font-size: 10.5px;
  border-radius: 8px 0 0 0 !important;
  border-right: 0 !important;
  border-bottom: 0 !important;
}
.leaflet-bar a {
  background: transparent !important;
  color: var(--ink) !important;
  border-bottom-color: var(--border) !important;
  width: 32px !important;
  height: 32px !important;
  line-height: 32px !important;
  font-size: 18px;
}
.leaflet-bar a:hover, .leaflet-bar a:active { background: var(--wash) !important; }
.leaflet-bar a.leaflet-disabled { color: var(--ink-muted) !important; }

/* ----------------------------------------------------------------- pins --- */
/* Same letter-and-colour pairing as the list badges: the grade is written on
   the pin, so colour is never the only thing carrying it. */

.pin {
  display: grid;
  place-items: center;
  width: 28px; height: 28px;
  border-radius: 50% 50% 50% 2px;
  transform: rotate(-45deg);
  border: 1.5px solid var(--surface);
  box-shadow: 0 1px 3px rgba(11,11,11,.4);
  font-weight: 700;
  font-size: 13px;
  line-height: 1;
  color: #fff;
  cursor: pointer;
}
.pin b { transform: rotate(45deg); font-weight: inherit; }
.pin[data-g="A"] { background: var(--grade-a); }
.pin[data-g="B"] { background: var(--grade-b); color: #0b0b0b; }
.pin[data-g="C"] { background: var(--grade-c); color: #0b0b0b; }
.pin[data-g="U"] { background: var(--grade-u); }
.pin.is-approx { border-style: dashed; opacity: .82; }

/* A cluster is a count, not a verdict. Colour carries the grade everywhere else
   in this app, so this one stays neutral -- putting the worst score in the pile
   on it would read as a judgement on every place inside. */
.pin-cluster {
  display: grid;
  place-items: center;
  width: 34px; height: 34px;
  border-radius: 50%;
  border: 2px solid var(--surface);
  background: var(--ink);
  color: var(--surface);
  box-shadow: 0 1px 3px rgba(11,11,11,.4);
  cursor: pointer;
}
.pin-cluster b { font: 700 13px/1 system-ui, -apple-system, sans-serif; font-variant-numeric: tabular-nums; }

/* A county the app knows about but has not fetched. Dashed, because that is
   this app's word for "not confirmed" everywhere else, and hollow so it reads
   as an outline waiting to be filled rather than as data. */
.pin-ghost {
  display: grid;
  place-items: center;
  width: 44px; height: 44px;
  border-radius: 50%;
  border: 1.5px dashed color-mix(in srgb, var(--ink) 45%, transparent);
  background: color-mix(in srgb, var(--surface) 78%, transparent);
  backdrop-filter: blur(2px);
  color: var(--ink-2);
  cursor: pointer;
  transition: background .15s ease;
}
.pin-ghost:hover { background: var(--surface); }
.pin-ghost b { font: 650 12px/1 system-ui, -apple-system, sans-serif; font-variant-numeric: tabular-nums; }
.pin-ghost small { font: 500 8.5px/1.1 system-ui, -apple-system, sans-serif; color: var(--ink-muted);
                   max-width: 40px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.map-locate {
  position: absolute;
  z-index: 900;                   /* over Leaflet's controls, which sit at 800 */
  top: calc(10px + env(safe-area-inset-top));
  right: 10px;
  width: var(--tap); height: var(--tap);
  display: grid; place-items: center;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--surface);
  color: var(--ink);
  box-shadow: var(--shadow);
  cursor: pointer;
}
.map-locate:active { background: var(--wash); }
.map-locate svg { width: 19px; height: 19px; fill: none; stroke: currentColor; stroke-width: 1.9; stroke-linecap: round; }
.map-locate[data-state="on"] { color: var(--accent); }
.map-locate[data-state="busy"] svg { animation: spin 1s linear infinite; }
@media (prefers-reduced-motion: reduce) { .map-locate[data-state="busy"] svg { animation: none; } }
@media (min-width: 980px) { .map-locate { display: none; } }
.pin.is-open { box-shadow: 0 0 0 3px var(--accent), 0 1px 3px rgba(11,11,11,.4); }
.pin-wrap { background: none; border: 0; }

/* Saved places get the star, with the grade written inside it -- bigger than a
   plain pin on purpose, so a handful of them stay findable in a field of
   hundreds. Colour still never carries the grade on its own. */
.pin-star { display: block; cursor: pointer; }
.pin-star svg { display: block; width: 38px; height: 38px; overflow: visible;
                filter: drop-shadow(0 1px 2px rgba(11,11,11,.45)); }
.pin-star path { stroke: var(--surface); stroke-width: 2.2; stroke-linejoin: round; }
.pin-star text { text-anchor: middle; font: 700 15px/1 system-ui, -apple-system, sans-serif; }
.pin-star[data-g="A"] path { fill: var(--grade-a); }
.pin-star[data-g="B"] path { fill: var(--grade-b); }
.pin-star[data-g="C"] path { fill: var(--grade-c); }
.pin-star[data-g="U"] path { fill: var(--grade-u); }
.pin-star[data-g="A"] text, .pin-star[data-g="U"] text { fill: #fff; }
.pin-star[data-g="B"] text, .pin-star[data-g="C"] text { fill: #0b0b0b; }
.pin-star.is-approx path { stroke-dasharray: 3 2.5; }
.pin-star.is-open svg { filter: drop-shadow(0 0 3px var(--accent)) drop-shadow(0 0 3px var(--accent)); }

/* Home: where the sorting is measured from. Draggable, and deliberately not
   blue -- the blue dot below is the device's own idea of where you are. */
.home-pin {
  display: grid;
  place-items: center;
  width: 30px; height: 30px;
  border-radius: 50% 50% 50% 2px;
  transform: rotate(-45deg);
  background: var(--ink);
  border: 2px solid var(--surface);
  box-shadow: 0 1px 4px rgba(11,11,11,.45);
  cursor: grab;
}
.home-pin svg {
  transform: rotate(45deg);
  width: 15px; height: 15px;
  fill: none; stroke: var(--surface);
  stroke-width: 2; stroke-linecap: round; stroke-linejoin: round;
}
.leaflet-drag-target .home-pin { cursor: grabbing; }

/* Leaflet writes stroke/fill as presentation attributes, which can't hold a
   custom property — a class can, and CSS beats a presentation attribute. */
.approx-halo { stroke: var(--accent); fill: var(--accent); }

/* The device's actual fix. Not draggable, not the sort origin -- just a
   standing answer to "and where am I?" while you look somewhere else. */
.you-pin {
  width: 16px; height: 16px;
  border-radius: 50%;
  background: var(--accent);
  border: 3px solid var(--surface);
  box-shadow: 0 0 0 1px var(--border), 0 0 0 7px var(--accent-wash),
              0 1px 5px rgba(11,11,11,.3);
}

/* ---- the centre crosshair, and the button that commits it --------------- */

.crosshair {
  position: absolute;
  z-index: 700;
  left: 50%; top: 50%;
  width: 40px; height: 40px;
  margin: -20px 0 0 -20px;
  pointer-events: none;
}
.crosshair svg {
  width: 100%; height: 100%;
  fill: none;
  stroke: var(--ink);
  stroke-width: 1.6;
  stroke-linecap: round;
  opacity: .62;
  /* A pale outline keeps it legible over dark parkland and pale roads alike. */
  paint-order: stroke;
  filter: drop-shadow(0 0 1.5px var(--surface)) drop-shadow(0 0 1.5px var(--surface));
}
.crosshair .crosshair-dot { fill: var(--ink); stroke: none; }

.sethome {
  position: absolute;
  z-index: 900;
  left: 50%;
  transform: translateX(-50%);
  bottom: var(--stack-2);
  display: inline-flex;
  align-items: center;
  gap: 7px;
  min-height: var(--tap);
  padding: 0 18px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: color-mix(in srgb, var(--surface) 94%, transparent);
  backdrop-filter: saturate(180%) blur(10px);
  -webkit-backdrop-filter: saturate(180%) blur(10px);
  color: var(--ink);
  font: inherit;
  font-size: 13.5px;
  font-weight: 570;
  white-space: nowrap;
  box-shadow: var(--shadow);
  cursor: pointer;
}
.sethome[hidden], .crosshair[hidden] { display: none; }
.sethome:active { background: var(--wash); }
.sethome svg { width: 15px; height: 15px; fill: none; stroke: currentColor;
               stroke-width: 1.9; stroke-linecap: round; stroke-linejoin: round; }
.sethome.is-done { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 42%, var(--border)); }

/* ------------------------------------------------------- sheet mini-map --- */

.sheet-map {
  display: block;
  position: relative;
  height: 150px;
  margin: 0 0 4px;
  border: 1px solid var(--border);
  border-radius: 12px;
  overflow: hidden;
  background: var(--grid);
  text-decoration: none;
}
.sheet-map-canvas { position: absolute; inset: 0; display: block; pointer-events: none; }
.sheet-map-canvas .leaflet-container { background: var(--grid); }
.sheet-map-canvas .leaflet-control-attribution { pointer-events: auto; }
.sheet-map-hit { position: absolute; inset: 0 0 17px 0; display: block; }
.sheet-map-tag {
  position: absolute;
  z-index: 500;
  left: 8px; bottom: 8px;
  padding: 4px 9px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--surface) 92%, transparent);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  border: 1px solid var(--border);
  color: var(--ink-2);
  font-size: 11.5px;
  font-weight: 560;
}
.sheet-map-tag::after { content: " ↗"; color: var(--ink-muted); }
.sheet-map-hit:active { background: var(--accent-wash); }
