:root {
  /* Greys are diil.work's slate scale rather than the ad-hoc blues this
     started with -- same family, so the two products look related. The
     orange stays: it is this site's identity and the one colour that has to
     survive a phone screen in daylight. */
  --bg: #020618;          /* diil ink */
  --surface: #0f172a;
  --surface-2: #1e293b;   /* diil's dark surface */
  --line: #334155;
  --text: #f1f5f9;
  --muted: #94a3b8;       /* diil uses this for secondary text throughout */
  --accent: #ff7a1a;
  --ok: #22c55e;
  --queue: #f5a623;
  --out: #ef4444;
  --unknown: #64748b;
  --radius: 12px;
  /* How much of the window the on-screen keyboard has taken; 0 the rest of
     the time. Written by syncKeyboardInset(). Declared here so every
     `var(--kb)` resolves -- an undefined variable makes `bottom` compute to
     `auto`, which would silently unpin the bottom sheet. */
  --kb: 0px;
  /* Everything stacked above the map, measured at runtime -- those bars wrap
     differently on every screen width, so a constant here was always going
     to be wrong somewhere. The fallback matches a typical phone. */
  --chrome-h: 240px;
}

@media (prefers-color-scheme: light) {
  :root {
    --bg: #f8fafc;        /* diil surface */
    --surface: #ffffff;
    --surface-2: #f1f5f9;  /* diil surface-2 */
    --line: #e2e8f0;       /* diil border */
    --text: #020618;       /* diil ink */
    --muted: #64748b;      /* diil secondary text */
    --unknown: #94a3b8;
  }
}

* { box-sizing: border-box; }

/* The UA's `[hidden] { display: none }` is a bare attribute selector, so any
   class rule that sets `display` beats it -- which silently un-hides every
   flex container we toggle with the hidden attribute. Win explicitly. */
[hidden] { display: none !important; }

/* GIP -- the face diil.work uses. Subset to Cyrillic + Latin by
   tools/build_fonts.py; the .otf originals are 100 KB each and carry
   languages this site never renders.

   swap, not block: on a slow connection in a fuel queue, text in the system
   font now beats no text at all while 23 KB arrives. */
@font-face {
  font-family: "GIP";
  src: url("/static/fonts/GIP-Regular.woff2") format("woff2");
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "GIP";
  src: url("/static/fonts/GIP-Medium.woff2") format("woff2");
  font-weight: 500; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "GIP";
  src: url("/static/fonts/GIP-SemiBold.woff2") format("woff2");
  font-weight: 600; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "GIP";
  src: url("/static/fonts/GIP-Bold.woff2") format("woff2");
  font-weight: 700; font-style: normal; font-display: swap;
}

html, body {
  margin: 0;
  height: 100%;
  background: var(--bg);
  color: var(--text);
  /* GIP has no ◎ and no emoji, so the system stack stays behind it. */
  font-family: "GIP", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

a { color: inherit; }

/* Form controls do not inherit font-family -- the UA gives them its own.
   Without this every button, input and select on the site stayed in Arial
   while the rest of the page switched to GIP. */
button, input, select, textarea, optgroup {
  font-family: inherit;
  font-size: inherit;
}

/* ---------- topbar ----------
   The map has no header of its own any more, but /admin and /company still
   do -- these rules are theirs. */
.topbar {
  /* min-height, not height: a long title wraps to a second line on a narrow
     phone rather than being clipped. */
  min-height: 62px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 8px 16px;
  background: var(--surface);
  border-bottom: 1px solid var(--line);
  position: relative;
  z-index: 1000;
}
.brand {
  display: flex; align-items: center; gap: 10px;
  min-width: 0; text-decoration: none; color: inherit;
}
.logo { width: 30px; height: 30px; flex: 0 0 auto; border-radius: 50%; display: block; }
.topbar h1 {
  margin: 0; font-size: 17px; line-height: 1.2; letter-spacing: -0.2px;
}
/* Without this the header actions stack instead of sitting in a row. */
.topbar-actions { display: flex; gap: 8px; flex: 0 0 auto; }

/* ---------- buttons ---------- */
.btn {
  border: 1px solid var(--line);
  background: var(--surface-2);
  color: var(--text);
  padding: 9px 16px;
  border-radius: 999px;
  font-size: 13.5px;
  font-weight: 600;
  cursor: pointer;
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  transition: transform .08s ease, filter .15s ease;
}
.btn:active { transform: scale(.97); }
.btn-primary { background: var(--accent); border-color: var(--accent); color: #1a0e03; }
.btn-primary:disabled { opacity: .45; cursor: not-allowed; }
.btn-ghost { background: transparent; }
/* Directions out of a station nobody has reported is the only useful thing
   on that screen, and as a ghost button it looked like the thing you were
   meant to ignore. Still quieter than .btn-primary, so it does not compete
   with filing a report. Alpha over --surface: correct in both schemes. */
.btn-route {
  background: rgba(255,122,26,.1); border-color: rgba(255,122,26,.45);
  color: var(--accent); font-weight: 700;
}
.btn-sm { padding: 5px 10px; font-size: 15px; }
.btn-block { width: 100%; justify-content: center; padding: 13px; font-size: 15px; }
.pin { font-size: 15px; }

/* ---------- promo banner ----------
   A placed advert for diil.work. Deliberately light on this dark page: an
   advert dressed as part of the map would be the dishonest version, and the
   contrast is also what makes it legible in one glance.

   Palette is diil's yellow, not benzin's orange, for the same reason -- it
   should read as somebody else's brand. */
.promo {
  position: relative;
  /* Equal gap above and below: with no bottom margin the card sat flush on
     the CTA bar and read as attached to it, which an advert should not. */
  margin: 10px 12px;
  border-radius: 18px;
  /* Deliberately light on a dark page. That inversion is the whole reason an
     advert gets noticed here, and it is honest too: it reads as placed rather
     than as part of the map.

     What looked wrong the first time was not the light card, it was a
     clinical white one sitting flat on near-black with nothing between them.
     So: a warm off-white, a rim in diil's yellow, and a soft glow that gives
     the card an edge to sit on instead of a hard cut.

     Fixed colours, not theme variables -- this panel stays light whichever
     way the rest of the page goes. */
  background: linear-gradient(135deg, #fffdf6 0%, #f4f2ea 100%);
  border: 1px solid rgba(255,216,77,.55);
  box-shadow: 0 0 0 1px rgba(0,0,0,.35), 0 6px 22px rgba(255,216,77,.14);
  overflow: hidden;
}
/* Half the height it started at. The three feature bullets went, and the
   rest tightened: this sits above the plate rule, which is the line that
   decides whether the rest of the page is any use to a driver today, so
   every pixel here is one that line does not get. */
.promo-link {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 14px;
  text-decoration: none; color: #17171c;
}
.promo-text { flex: 1; min-width: 0; }
.promo-tag {
  display: inline-block;
  background: #ffd84d; color: #2b2b30;
  padding: 1px 8px; border-radius: 999px;
  font-size: 9.5px; font-weight: 700; letter-spacing: .3px;
  text-transform: uppercase;
}
.promo-title {
  margin: 4px 0 2px;
  font-size: 15px; line-height: 1.2; font-weight: 700; letter-spacing: -.2px;
}
/* The highlighted phrase. Dark amber, not diil's yellow: the card is light in
   both themes now, and yellow on off-white measures about 1.4:1 -- invisible.
   Same hue, carried down until it passes. */
.promo-title b { color: #7a5800; font-weight: 700; }
.promo-sub { margin: 0; font-size: 11.5px; color: #6b6b76; line-height: 1.35; }
.promo-art { flex: 0 0 54px; width: 54px; height: auto; }
/* On a narrow phone the drawing is the first thing to go: the headline is
   the point and it should not have to share the width. */
@media (max-width: 460px) {
  .promo-art { display: none; }
  .promo-link { padding: 8px 12px; }
  .promo-title { font-size: 14px; }
  .promo-sub { font-size: 11px; }
}

/* ---------- today's plate rule ----------
   One sentence: everyone knows their own plate, so all the page owes them is
   which parity is being served today. */
/* ---------- primary action ----------
   The map is only as good as the reports people file, so the ask is the
   first thing on the page: a question, then how easy the answer is, then
   the button. */
.cta-bar {
  padding: 14px 16px 16px;
  background: var(--surface);
  border-bottom: 1px solid var(--line);
  position: relative; z-index: 999;
  text-align: center;
}
.cta-q {
  /* 12px, not the 4px this had: the subline underneath used to carry the
     gap down to the button, and it is gone. */
  margin: 0 0 12px; font-size: 15.5px; font-weight: 700; line-height: 1.35;
}
/* Still used by the mobile app demo (/app), which is why this stays after
   the subline came off the map page. */
.cta-sub {
  margin: 0 0 12px; font-size: 13px; color: var(--muted); line-height: 1.4;
}
/* Reporting and the talon board sit together: both are things you come here
   to do, as opposed to the map, which is what you come here to read. Stacked
   full-width rather than side by side -- two labels this different in length
   never balanced well as a pair, and full-width makes each one an easier tap
   target on a phone. */
.cta-row {
  display: grid; grid-template-columns: 1fr; gap: 8px;
}
.cta-row > .btn {
  min-width: 0;
  justify-content: center;
}
/* One button now, so desktop centres it at its own width rather than
   letting it stretch the whole bar. .btn-talon and .btn-aldar lived here
   until the row held three; the two boards they opened are reached from the
   sidebar tabs, which is where their only remaining styling is. */
@media (min-width: 781px) {
  .cta-row {
    display: flex; justify-content: center; gap: 10px;
  }
  .cta-row > .btn {
    flex: 0 0 auto;
    min-width: 210px;
    padding-left: 26px; padding-right: 26px;
  }
}

.btn-cta {
  /* min-width here is moot -- .cta-row > .btn overrides it to 0 and the
     single-column grid stretches the button full width regardless. Left in
     case this class is ever used somewhere not inside .cta-row. */
  min-width: min(320px, 100%);
  justify-content: center;
  padding: 12px 22px;
  font-size: 14.5px; font-weight: 700;
  border-radius: 999px;
  box-shadow: 0 4px 14px rgba(255,122,26,.24);
}
.btn-cta .pin { font-size: 15px; }

/* ---------- camera ----------
   Full screen so the viewfinder is the whole decision: point it at the
   forecourt and shoot. */
.camera {
  position: fixed; top: 0; right: 0; bottom: 0; left: 0; inset: 0; z-index: 2000;
  background: #000;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
}
.camera video {
  width: 100%; height: 100%; object-fit: cover;
  position: absolute; top: 0; right: 0; bottom: 0; left: 0; inset: 0;
}
.camera-hint {
  position: absolute; top: 18px; left: 50%; transform: translateX(-50%);
  margin: 0; padding: 8px 14px; max-width: calc(100% - 32px);
  background: rgba(0,0,0,.55); color: #fff; border-radius: 999px;
  font-size: 13px; text-align: center;
}
.camera-error {
  position: relative; z-index: 1;
  margin: 0 24px; padding: 16px 18px;
  background: var(--surface); color: var(--text);
  border: 1px solid var(--out); border-radius: 12px;
  font-size: 13.5px; line-height: 1.5; text-align: center;
}
.camera-bar {
  position: absolute; left: 0; right: 0; bottom: 0;
  padding: 20px 24px calc(24px + env(safe-area-inset-bottom));
  display: flex; align-items: center; justify-content: space-between;
  background: linear-gradient(to top, rgba(0,0,0,.75), transparent);
}
.camera-shoot {
  width: 72px; height: 72px; border-radius: 50%;
  background: #fff; border: 5px solid rgba(255,255,255,.45);
  cursor: pointer; flex: 0 0 auto;
}
.camera-shoot:active { transform: scale(.94); }
.camera-side {
  background: rgba(0,0,0,.4); border: none; color: #fff;
  font-size: 15px; padding: 10px 14px; border-radius: 10px; cursor: pointer;
  min-width: 72px;
}

/* in-app browser escape */
.geo-inapp {
  background: var(--surface-2); border-radius: 16px;
  padding: 13px 15px; margin-bottom: 16px;
  font-size: 13px; line-height: 1.5; text-align: left;
}
.geo-inapp p { margin: 0 0 6px; }
.geo-inapp p:last-child { margin-bottom: 0; color: var(--muted); }
.geo-escape-hint {
  margin: 10px 0 0; font-size: 12px; line-height: 1.5;
  color: var(--accent); text-align: left;
}

/* ---------- filters ---------- */
.filters {
  background: var(--surface);
  border-bottom: 1px solid var(--line);
  padding: 10px 16px;
  position: relative;
  z-index: 999;
}
.fuel-tabs { display: flex; gap: 7px; overflow-x: auto; padding-bottom: 8px; scrollbar-width: none; }
.fuel-tabs::-webkit-scrollbar { display: none; }
.fuel-tab {
  flex: 0 0 auto;
  border: 1px solid var(--line);
  background: var(--surface-2);
  color: var(--muted);
  padding: 7px 13px;
  border-radius: 999px;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
}
.fuel-tab.active { background: var(--accent); border-color: var(--accent); color: #1a0e03; }

/* One line, on every phone down to 320px. Nothing wraps: the three controls
   shrink instead, and the two that can lose characters without becoming
   unreadable are the ones allowed to. */
.filter-row { display: flex; align-items: center; gap: 6px; flex-wrap: nowrap; }
/* The chevron. Drawn here rather than left to the platform so the control
   ends the same distance from its own edge as the pills beside it do. */
.select-wrap { position: relative; display: inline-flex; min-width: 64px; flex: 0 1 auto; }
.select-wrap::after {
  content: ""; position: absolute; right: 11px; top: 50%;
  width: 7px; height: 7px; margin-top: -5px;
  border-right: 2px solid var(--muted); border-bottom: 2px solid var(--muted);
  transform: rotate(45deg); pointer-events: none;
}
.select {
  -webkit-appearance: none; -moz-appearance: none; appearance: none;
  background: var(--surface-2);
  color: var(--text);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 7px 26px 7px 12px;
  font: inherit; font-size: 12.5px; font-weight: 600;
  line-height: 1.35;
  /* Shrinks before the chips do. A company name is the one label here that
     is already a proper noun the reader can finish from its first word, and
     it is the only control of the three whose own text tells you what it
     currently holds -- so losing its tail costs the least. */
  flex: 0 1 auto; min-width: 0; width: 100%; max-width: 168px;
  text-overflow: ellipsis;
  cursor: pointer;
}
.select-wrap:focus-within::after { border-color: var(--accent); }
/* Orders the photo list. A segmented pill rather than a dropdown: two
   options, and the one in force should be readable without opening it. */
.segmented {
  display: inline-flex; padding: 3px; gap: 2px;
  background: var(--surface-2); border: 1px solid var(--line);
  border-radius: 999px;
}
.seg {
  border: none; background: none; cursor: pointer;
  padding: 5px 13px; border-radius: 999px;
  font-size: 12.5px; font-weight: 600; color: var(--muted);
  white-space: nowrap;
}
.seg.active { background: var(--accent); color: #1a0e03; }
.seg:disabled { opacity: .45; cursor: not-allowed; }

/* The plain one, unchanged. admin.html carries two -- a label with a real
   tickbox in it -- and they are not chips, so the chip below is scoped to
   the filter bar rather than taking `.toggle` with it. */
.toggle { display: flex; align-items: center; gap: 6px; font-size: 13px; color: var(--muted); cursor: pointer; }
.toggle input { accent-color: var(--accent); width: 16px; height: 16px; }

/* Зөвхөн бензин өгч байгааг, as a chip. The input stays and does the work;
   only its rendering moves to the span, so the label still toggles it and
   the keyboard still reaches it. */
.filter-row .toggle { display: inline-flex; align-items: center; gap: 0; min-width: 0; flex: 0 0 auto; }
.filter-row .toggle input {
  position: absolute; width: 1px; height: 1px;
  opacity: 0; margin: 0; pointer-events: none;
}
.toggle-pill {
  display: inline-flex; align-items: center;
  padding: 7px 12px;
  border: 1px solid var(--line); border-radius: 999px;
  background: var(--surface-2); color: var(--muted);
  font-size: 12.5px; font-weight: 600; line-height: 1.35;
  white-space: nowrap;
  transition: color .16s ease, border-color .16s ease, background-color .16s ease;
}
.toggle-text { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
/* On, said with fill alone -- the way `.fuel-tab.active` and `.seg.active`
   on this same bar have always said it. There used to be a tick box in here
   as a second, colour-free signal; it cost 20px a chip, which at 320px is
   exactly what the third control needed to stay on the line. The state still
   reaches a screen reader, because the thing being toggled is still a real
   checkbox. */
.toggle input:checked + .toggle-pill {
  color: var(--accent); border-color: var(--accent);
  background: rgba(255,122,26,.12);
}
/* Keyboard focus has to land somewhere visible now that the input itself is
   not drawn. Written as :focus first and withdrawn again for pointers,
   because :focus-visible does not exist in the Facebook webview's engine --
   asking for it alone would leave the oldest browsers here with no ring at
   all, which is the one case this rule is for. */
.toggle input:focus + .toggle-pill {
  outline: 2px solid var(--accent); outline-offset: 2px;
}
.toggle input:focus:not(:focus-visible) + .toggle-pill { outline: none; }
.toggle-pill:active { transform: scale(.97); }
/* Stood down while the legend filter is in charge. */
.toggle.muted { opacity: .4; cursor: not-allowed; }
.toggle.muted .toggle-pill { cursor: not-allowed; }

/* The readout under the controls. Muted and small: it is the bar answering
   the filters, not another thing to press. */
.stat {
  margin: 8px 0 0; font-size: 12.5px; color: var(--muted); line-height: 1.4;
}
@media (prefers-reduced-motion: reduce) {
  .toggle-pill { transition: none; }
  .toggle-pill:active { transform: none; }
}

/* Clicking a district polygon or a place label leaves the browser's focus
   ring on it -- a blue rectangle around the bounding box of a shape that is
   not rectangular, which reads as a rendering bug rather than a selection.
   The selection has its own colour; that is the feedback. */
.leaflet-interactive:focus,
.leaflet-marker-icon:focus,
.leaflet-container path:focus { outline: none; }

/* ---------- place names on the map ----------
   The clickable target for a named place (data/geo/locations.json). Plain
   text over the tiles, not a bordered chip: at this density the borders
   read as a grid of boxes and fight the station pins. A white halo keeps
   it legible over roads and parkland instead. */
.loc-label {
  display: inline-block; white-space: nowrap;
  transform: translate(-50%, -50%);
  padding: 1px 4px;
  color: #475569; font-size: 11px; font-weight: 600;
  cursor: pointer;
  text-shadow:
     1px  1px 2px var(--surface),  -1px  1px 2px var(--surface),
     1px -1px 2px var(--surface),  -1px -1px 2px var(--surface);
}
.loc-label:hover { color: var(--accent); }
.loc-label.on {
  background: var(--accent); color: #1a0e03;
  border-radius: 999px; font-weight: 700; text-shadow: none;
}
@media (prefers-color-scheme: dark) {
  .loc-label { color: #cbd5e1; }
}

/* ---------- layout ---------- */
.layout {
  display: flex;
  height: calc(100vh - var(--chrome-h));
  height: calc(100dvh - var(--chrome-h));
  min-height: 380px;
}
#map { flex: 1; height: 100%; background: var(--surface-2); }

.sidebar {
  width: 370px;
  flex: 0 0 370px;
  background: var(--surface);
  border-left: 1px solid var(--line);
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.sidebar-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px;
  padding: 12px 14px 8px;
}
.sidebar-head h2 { margin: 0; font-size: 14.5px; }
/* The sort can have the room; the refresh button is a single glyph. */
.sidebar-head .segmented { flex: 1 1 auto; }
.sidebar-head .seg { flex: 1; text-align: center; }
.sidebar-head #refresh-btn { flex: 0 0 auto; }
.legend {
  display: flex; flex-wrap: wrap; gap: 6px;
  padding: 0 14px 10px;
  font-size: 11.5px; color: var(--muted);
  border-bottom: 1px solid var(--line);
}
.legend span { display: inline-flex; align-items: center; gap: 5px; }

/* Key and filter at once. Pressed ones carry their own status colour, so
   the row still reads as a key while showing what is being filtered. */
.leg {
  display: inline-flex; align-items: center; gap: 6px;
  /* 26px high was a comfortable label and a poor button. These get tapped
     with a thumb in a car; 36 stays comfortable across a 375px phone. */
  min-height: 36px;
  padding: 8px 13px; border-radius: 999px; cursor: pointer;
  background: var(--surface-2); border: 1px solid var(--line);
  color: var(--muted); font-size: 12px; font-weight: 600;
  transition: border-color .15s ease, color .15s ease;
}
.leg:hover { color: var(--text); }
.leg[aria-pressed="true"] { color: var(--text); }
.leg[aria-pressed="true"][data-status="available"] { border-color: var(--ok);      background: rgba(34,197,94,.14); }
.leg[aria-pressed="true"][data-status="queue"]     { border-color: var(--queue);   background: rgba(245,166,35,.14); }
.leg[aria-pressed="true"][data-status="out"]       { border-color: var(--out);     background: rgba(239,68,68,.13); }
.leg[aria-pressed="true"][data-status="unknown"]   { border-color: var(--unknown); background: rgba(100,116,139,.18); }
/* Dim the ones not chosen, so the active filter is obvious at a glance. */
.legend.filtering .leg[aria-pressed="false"] { opacity: .45; }
.leg-clear { color: var(--accent); }
/* The fixed 370px sidebar (desktop) is 8px too narrow for the three at
   their phone padding, which pushed "Дууссан" onto its own line. Trimming
   the horizontal padding is enough to fit all three in one row. */
@media (min-width: 781px) {
  .legend { flex-wrap: nowrap; }
  .legend .leg:not(.leg-clear) { padding-left: 9px; padding-right: 9px; }
}
.dot { width: 9px; height: 9px; border-radius: 50%; display: inline-block; }
.dot.ok { background: var(--ok); }
.dot.queue { background: var(--queue); }
.dot.out { background: var(--out); }
.dot.unknown { background: var(--unknown); }

/* ---------- талон board ---------- */
.sidebar-head.thin { padding: 0 14px 10px; }
.talon-list {
  overflow-y: auto; flex: 1; padding: 10px 12px 16px;
  display: flex; flex-direction: column; gap: 10px;
}
.ad {
  display: flex; gap: 12px; padding: 11px;
  background: var(--surface-2); border: 1px solid var(--line); border-radius: 18px;
}
.ad-thumb {
  flex: 0 0 74px; width: 74px; height: 74px; border-radius: 14px;
  background: var(--bg); overflow: hidden;
  display: flex; align-items: center; justify-content: center; font-size: 26px;
}
.ad-thumb img { width: 100%; height: 100%; object-fit: cover; }
.ad-body { flex: 1; min-width: 0; }
.ad-kind {
  display: inline-block; padding: 2px 9px; border-radius: 999px;
  font-size: 10px; font-weight: 800; letter-spacing: .3px; margin-bottom: 5px;
}
.ad-kind.sell { background: rgba(34,197,94,.16); color: var(--ok); }
.ad-kind.buy  { background: rgba(79,107,246,.20); color: #8fa4ff; }
.ad-msg { margin: 0 0 6px; font-size: 13px; line-height: 1.4; word-break: break-word; }
.ad-foot { display: flex; align-items: center; gap: 8px; }
.ad-phone { font-size: 13.5px; font-weight: 700; color: var(--accent); text-decoration: none; }
.ad-when { font-size: 10.5px; color: var(--muted); margin-left: auto; }
/* Only shown on ads this browser posted. The server checks ownership too --
   this is the affordance, not the guard. */
.ad-del {
  background: none; border: none; cursor: pointer; padding: 2px 4px;
  color: var(--muted); font-size: 14px; line-height: 1;
}
.ad-del:hover { color: var(--out); }
/* The glyph is an inline SVG rather than 🗑 -- an emoji renders as a
   different picture on every platform and ignores the hover colour. Block
   display drops the baseline gap an inline SVG would otherwise sit on. */
.ad-del { display: inline-flex; align-items: center; }
.ad-del svg { display: block; }
/* "Зарагдсан" / "Олдсон": the poster closing their own ad. Shown only on
   ads this browser posted, and only while they are still open. */
.ad-sold {
  background: rgba(34,197,94,.14); border: 1px solid rgba(34,197,94,.4);
  color: var(--ok); cursor: pointer;
  padding: 3px 10px; border-radius: 999px;
  font-size: 11.5px; font-weight: 700; line-height: 1.4;
}
.ad-sold:hover { background: rgba(34,197,94,.24); }
.ad-sold:disabled { opacity: .5; cursor: default; }
/* A closed ad stays readable -- it is still evidence that coupons move at
   this price -- but it should not look like something to ring up. */
.ad.sold { opacity: .62; }
.ad.sold .ad-phone { color: var(--muted); text-decoration: line-through; }
.ad-done {
  display: inline-block; margin-left: 6px; padding: 2px 8px;
  border-radius: 999px; background: var(--surface);
  border: 1px solid var(--line); color: var(--muted);
  font-size: 10px; font-weight: 700; letter-spacing: .4px;
}
/* The ad a shared link points at, so it is findable in a board of sixty. */
.ad.focused {
  border-color: var(--accent);
  box-shadow: 0 0 0 1px var(--accent), 0 6px 20px rgba(0,0,0,.35);
}
.talon-warn {
  margin: 14px 0 0; padding: 11px 13px; border-radius: 14px;
  background: rgba(245,166,35,.12); border: 1px solid rgba(245,166,35,.35);
  font-size: 11.5px; color: var(--queue); line-height: 1.5;
}

/* ---------- алдаршуулах board ----------
   Who drove past the whole line, and who did not. Two things carry this
   board and nothing else does: a plate and a photograph. So the plate is
   drawn as the physical object rather than as another line of body text,
   and the thumb is 96px where талон gives its coupon 74 -- the picture is
   the only evidence a reader has that any of this happened.

   Everything else borrows: .ad's surface and rounding, .spot-age's
   colours, .react-pill for the row underneath. A third board inventing its
   own card would read as a third product. */
.aldar-list {
  overflow-y: auto; flex: 1; padding: 10px 12px 16px;
  display: flex; flex-direction: column; gap: 10px;
}
/* The search sits on its own line rather than squeezing in beside the
   filter: it is a plate-shaped field and half a sidebar is not enough
   room to type one into. */
.aldar-search-row { padding: 0 14px 10px; }
.aldar-search {
  width: 100%; padding: 7px 12px; border-radius: 999px;
  background: var(--surface-2); border: 1px solid var(--line);
  color: var(--text); font: inherit; font-size: 13px;
  letter-spacing: .06em; text-transform: uppercase;
}
.aldar-search::placeholder { color: var(--muted); letter-spacing: normal; text-transform: none; }
.aldar-search:focus { outline: none; border-color: var(--accent); }

.aldar {
  display: flex; gap: 12px; padding: 11px;
  background: var(--surface-2); border: 1px solid var(--line); border-radius: 18px;
}
.aldar-thumb {
  flex: 0 0 96px; width: 96px; height: 96px; border-radius: 14px;
  background: var(--bg); overflow: hidden; cursor: zoom-in;
  display: flex; align-items: center; justify-content: center; font-size: 26px;
}
.aldar-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.aldar-thumb.skel { border-radius: 14px; }
.aldar-body { flex: 1; min-width: 0; }
.aldar-top {
  display: flex; align-items: center; flex-wrap: wrap; gap: 7px; margin-bottom: 5px;
}

/* The plate, drawn as the plate. White field, black digits, the blue band
   down the left -- somebody scanning a board of sixty for one number finds
   it in the shape they already know far faster than in a bolded span.
   Deliberately not themed: a number plate is white in a dark room too, and
   a token here would make it a surface. */
.plate {
  display: inline-flex; align-items: stretch; gap: 5px;
  padding: 3px 8px 3px 3px; border-radius: 5px;
  background: #f8fafc; border: 1px solid #0f172a;
  color: #020618; font-size: 14px; font-weight: 800;
  letter-spacing: .07em; font-variant-numeric: tabular-nums;
  white-space: nowrap; line-height: 1.35;
}
.plate::before {
  content: "MGL";
  display: flex; align-items: center; padding: 0 3px; border-radius: 3px;
  background: #1e3a8a; color: #fff;
  font-size: 7px; font-weight: 700; letter-spacing: .02em;
}
/* Bigger, on its own line, when the plate is the thing being entered. */
.plate.lg { font-size: 20px; padding: 6px 12px 6px 5px; }
.plate.lg::before { font-size: 9px; padding: 0 4px; }

.aldar-kind {
  display: inline-block; padding: 2px 9px; border-radius: 999px;
  font-size: 10px; font-weight: 800; letter-spacing: .3px;
}
.aldar-kind.ork  { background: rgba(239,68,68,.16); color: var(--out); }
.aldar-kind.sain { background: rgba(34,197,94,.16); color: var(--ok); }
/* Same three states as an очерь ad, and for the same reason: yesterday's
   queue is a different queue. Nothing here expires, it just cools. */
.aldar-age { margin-left: auto; font-size: 10.5px; color: var(--muted); }
.aldar-age.stale { color: var(--queue); }
.aldar-age.old   { color: var(--out); }
.aldar-where {
  margin: 0 0 5px; font-size: 11.5px; color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.aldar-msg { margin: 0 0 8px; font-size: 13px; line-height: 1.4; word-break: break-word; }
.aldar-foot { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
/* Takes the room that is left so the pills wrap inside it -- without this
   the flag gets pushed onto a line of its own and sits out there alone. */
.aldar-foot .comment-react { flex: 1 1 0%; min-width: 0; }
/* The flag and the bin travel together at the right end. Grouped rather
   than two `margin-left: auto` siblings, which wrap apart the moment the
   pills fill the line. */
.aldar-acts { display: flex; align-items: center; gap: 2px; flex: 0 0 auto; }
/* Both live at the right end, and the flag comes first so the destructive
   one is never the button a thumb lands on by accident. */
.aldar-flag, .aldar-del {
  display: inline-flex; align-items: center; gap: 4px;
  background: none; border: none; cursor: pointer; padding: 2px 4px;
  color: var(--muted); font: inherit; font-size: 11.5px; line-height: 1;
}
.aldar-flag:hover { color: var(--queue); }
.aldar-del:hover { color: var(--out); }
.aldar-del svg { display: block; }

/* .skel paints itself in --surface-2, which is also this card's background.
   On the sheet's --surface bubbles that reads fine; here it would be an
   invisible skeleton, so the pieces move one step up the scale. */
.aldar .skel,
.aldar .react-pill.skel-pill-sm { background: var(--line); }

.aldar-empty {
  font-size: 13px; color: var(--muted); text-align: center;
  padding: 30px 14px; line-height: 1.65;
}
.aldar-empty b { color: var(--text); }

/* ---------- алдаршуулах form ---------- */
/* Four digits and three letters are two different keyboards, so they are
   two fields. Typing them into one box means every поster picks their own
   spacing and the search stops matching. */
.plate-fields { display: flex; align-items: center; gap: 8px; }
.plate-fields .input {
  min-width: 0;
  text-align: center; text-transform: uppercase;
  font-size: 19px; font-weight: 800; letter-spacing: .16em;
  padding-left: 6px; padding-right: 6px;
}
.plate-num { flex: 0 0 118px; }
.plate-let { flex: 0 0 106px; }
.plate-preview { margin-left: auto; }
/* The line that keeps this board from becoming a place to publish a
   person. Warmer than .spot-warn because it is a rule, not a caveat. */
.aldar-warn {
  margin: 14px 0 0; padding: 11px 13px; border-radius: 14px;
  background: rgba(239,68,68,.10); border: 1px solid rgba(239,68,68,.32);
  font-size: 11.5px; color: var(--text); line-height: 1.55;
}
.aldar-warn b { color: var(--out); }

@media (max-width: 780px) {
  /* Three boards now share the row that fitted two, and at 375px the third
     label is the one that gets cut. The padding gives up a few pixels
     instead. */
  #side-view .seg { padding-left: 8px; padding-right: 8px; }
  .aldar-thumb { flex-basis: 78px; width: 78px; height: 78px; }
  /* `flex: 1 1 auto` would take its basis from .input's own width: 100%
     and put two full-width fields on one row. */
  .plate-num, .plate-let { flex: 1 1 0%; }
  .plate-preview { display: none; }
}

/* ---------- photo feed ----------
   Separate cards rather than rows divided by hairlines: each one is a
   distinct thing somebody saw, and the rounding makes that legible at a
   glance the way a flat list never did. */
.station-list {
  list-style: none; margin: 0; padding: 10px 12px 16px;
  overflow-y: auto; flex: 1;
  display: flex; flex-direction: column; gap: 10px;
}
.shot-card {
  display: grid;
  grid-template-columns: 84px 1fr auto;
  gap: 12px;
  align-items: start;
  padding: 10px;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: 18px;
  cursor: pointer;
  transition: transform .1s ease, border-color .15s ease;
}
.shot-card:hover { border-color: var(--accent); transform: translateY(-1px); }
.shot-card:active { transform: scale(.99); }
.shot-thumb {
  width: 84px; height: 84px; border-radius: 14px;
  background: var(--bg); overflow: hidden; flex: 0 0 auto;
}
.shot-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.shot-body { min-width: 0; }
.shot-verdict { margin: 4px 0 0; font-size: 12.5px; font-weight: 700; }
.shot-verdict.ok { color: var(--ok); }
.shot-verdict.queue { color: var(--queue); }
.shot-verdict.out { color: var(--out); }
.shot-verdict.unknown { color: var(--muted); }
.shot-note {
  margin: 4px 0 0; font-size: 12.5px; color: var(--text); line-height: 1.4;
  overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
}
.shot-when { margin: 4px 0 0; font-size: 11px; color: var(--muted); }
/* The five answers on a wall card -- the same five, from the same list, that
   the station thread and the room draw; see REACTIONS in app.js.

   Spanning the whole card rather than sitting in the body column: five pills
   in a column 84px narrower than the card wrap to three lines on a phone.
   Carries `.comment-react` too, so the row's flow and gap come from the one
   definition the thread already uses and there is no third board inventing
   its own. Nothing about the pills themselves is set here any more -- they
   are the same pills, so their size lives with .comment-react below. */
.shot-react {
  grid-column: 1 / -1;
  margin-top: 2px;
  /* Clear of Гомдол мэдүүлэх, which is absolute in the same corner. */
  padding-right: 30px;
}

.station-item {
  padding: 12px 14px;
  border-bottom: 1px solid var(--line);
  cursor: pointer;
  display: grid;
  grid-template-columns: 10px 1fr auto;
  gap: 10px;
  align-items: start;
}
.station-item:hover { background: var(--surface-2); }
.station-item .bar { width: 4px; height: 100%; min-height: 34px; border-radius: 3px; }
.bar.ok { background: var(--ok); }
.bar.queue { background: var(--queue); }
.bar.out { background: var(--out); }
.bar.unknown { background: var(--unknown); }
.st-name { font-size: 14px; font-weight: 650; margin: 0 0 2px; }
.st-meta { font-size: 11.5px; color: var(--muted); margin: 0 0 6px; }
.st-dist { font-size: 12.5px; font-weight: 700; color: var(--accent); white-space: nowrap; }
.fuel-pills { display: flex; flex-wrap: wrap; gap: 4px; }
.pill {
  font-size: 10.5px; font-weight: 700;
  padding: 2.5px 7px; border-radius: 999px;
  border: 1px solid transparent;
}
.pill.ok { background: rgba(34,197,94,.15); color: var(--ok); border-color: rgba(34,197,94,.35); }
.pill.queue { background: rgba(245,166,35,.15); color: var(--queue); border-color: rgba(245,166,35,.35); }
.pill.out { background: rgba(239,68,68,.13); color: var(--out); border-color: rgba(239,68,68,.3); }
.empty { padding: 28px 16px; text-align: center; color: var(--muted); font-size: 13px; }

/* ---------- map markers ----------
   The pin body stays coloured by availability -- that is the question the map
   exists to answer, so it must survive at a glance. The brand logo sits in a
   white disc inside it rather than replacing it. */
.marker {
  width: 34px; height: 34px; border-radius: 50% 50% 50% 5px;
  transform: rotate(-45deg);
  border: 2px solid rgba(255,255,255,.9);
  box-shadow: 0 2px 7px rgba(0,0,0,.45);
  display: flex; align-items: center; justify-content: center;
}
.marker .brand-badge { transform: rotate(45deg); }
.marker.ok { background: var(--ok); }
.marker.queue { background: var(--queue); }
.marker.out { background: var(--out); }
.marker.unknown { background: var(--unknown); }
/* Out of fuel is not what you are looking for -- let those recede. */
.marker.out, .marker.unknown { opacity: .82; }

/* A station you can look at right now.

   The pin body is already saying whether there is fuel, and that is the
   question the map exists for -- so this does not touch the body. It rides
   on the shoulder instead, in slate, which is not one of the availability
   colours and so cannot be misread as one.

   The pin is a square rotated -45deg, so what faces up-and-right is the
   middle of an edge rather than a corner: 50% +/- 35.4% of the icon box on
   each axis. As a percentage it holds wherever the pin is drawn -- the
   public map's fixed 34px and the admin map's traffic-sized 24-46px both.
   A sibling of `.marker`, never a child, so the -45deg never reaches it and
   there is nothing to counter-rotate. */
.cam-badge {
  position: absolute;
  left: 85.4%; top: 14.6%;
  transform: translate(-50%, -50%);
  /* Big enough to be read as a camera rather than as a smudge, and stopping
     there: the admin map draws pins as small as 24px, and a badge much past
     this covers the company mark on those instead of sitting beside it. */
  width: 18px; height: 18px;
  border-radius: 50%;
  background: #0f172a;
  border: 1.5px solid rgba(255,255,255,.92);
  box-shadow: 0 1px 4px rgba(0,0,0,.5);
  display: flex; align-items: center; justify-content: center;
  font-size: 11px; line-height: 1;
}

/* ---------- brand badge ---------- */
.brand-badge {
  position: relative;
  display: inline-flex; align-items: center; justify-content: center;
  flex: 0 0 auto;
  border-radius: 50%;
  background: #fff;
  overflow: hidden;
  vertical-align: -3px;
}
.brand-badge img {
  position: absolute; top: 0; right: 0; bottom: 0; left: 0; inset: 0;
  width: 100%; height: 100%;
  object-fit: contain;
  padding: 12%;
  background: #fff;
}
.brand-letter {
  font-size: 10px; font-weight: 800; line-height: 1;
  color: var(--brand, #64748b);
}
.st-name .brand-badge { margin-right: 6px; }
.popup h4 .brand-badge { margin-right: 6px; }
.me-marker {
  width: 16px; height: 16px; border-radius: 50%;
  background: #2f7cf6; border: 3px solid #fff;
  box-shadow: 0 0 0 6px rgba(47,124,246,.25);
}
.leaflet-popup-content-wrapper { background: var(--surface); color: var(--text); border-radius: 16px; }
.leaflet-popup-tip { background: var(--surface); }
/* The gauge is positioned against this; without it the absolute would
   escape to the page and land in the corner of the viewport. */
.popup { position: relative; }
.popup h4 { margin: 0 0 3px; font-size: 14px; }
.popup p { margin: 0 0 8px; font-size: 11.5px; color: var(--muted); }
.popup .btn { width: 100%; justify-content: center; padding: 8px; }

/* ---------- bottom sheet ---------- */
.sheet-backdrop {
  position: fixed; top: 0; right: 0; bottom: 0; left: 0; inset: 0; background: rgba(0,0,0,.55); z-index: 1500;
}
/* Header and composer are fixed; only the middle scrolls. The close button
   used to be absolutely positioned inside the scroll area, so on a phone it
   scrolled out of reach and the sheet could not be dismissed. */
.sheet {
  position: fixed;
  z-index: 1600;
  left: 50%; top: 50%;
  /* Centred in what is *visible*, not in the layout viewport. A phone held
     landscape is wider than the 780px breakpoint, so it gets this centred
     dialog rather than the bottom sheet -- and a keyboard there would cover
     the lower half, composer included. Half the inset re-centres it. */
  transform: translate(-50%, calc(-50% - var(--kb, 0px) / 2));
  width: min(460px, calc(100% - 24px));
  max-height: 88vh;
  max-height: min(88dvh, calc(100dvh - var(--kb, 0px) - 24px));
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 16px;
  overflow: hidden;
  box-shadow: 0 24px 60px rgba(0,0,0,.5);
}
/* Desktop is a centred dialog, which nothing is dragged from -- the grip is
   a phone affordance and appears with the bottom-sheet layout. */
.sheet-grip { display: none; }
.sheet-head {
  flex: 0 0 auto;
  display: flex; align-items: flex-start; gap: 10px;
  padding: 14px 14px 12px 18px;
  border-bottom: 1px solid var(--line);
}
.sheet-head-text { flex: 1; min-width: 0; }
.sheet-head h3 { margin: 0; font-size: 17px; }
.sheet-head .modal-close { position: static; transform: none; flex: 0 0 auto; }
.sheet-sub { margin: 2px 0 0; font-size: 12.5px; color: var(--muted); }
/* Basis `auto`, emphatically not 0. The sheet's height is `auto` capped by a
   max-height, so its natural size is the sum of its children's bases -- a
   basis of 0 here contributes nothing at all, and the sheet collapses to
   header plus composer with the thread a clipped sliver.

   The lopsided shrink factor is the other half. Flex shrinks children in
   proportion to their base sizes, so a footer sharing shrink 1 with a long
   thread lost a slice of itself on every constrained layout -- the send
   button clipped off the bottom in landscape. At 999 the thread absorbs
   effectively all of it, and the composer only gives way once the thread is
   gone. `min-height: 0` is what permits that. */
.sheet-body {
  flex: 1 999 auto; min-height: 0; overflow-y: auto; padding: 14px 18px;
}
.sheet-foot {
  /* Never shrinks -- the thread above it is what gives way, because the
     composer is the thing being used. But head and foot are both fixed
     inside an `overflow: hidden` column, so in landscape with a keyboard up
     the footer alone can exceed the whole sheet and the send button would be
     clipped off the bottom with no way to reach it. The cap is a safety
     valve: inert in portrait, and in landscape it scrolls the footer instead
     of losing it. */
  /* Shrinks only after the thread has given up everything it has (see the
     999 above), and scrolls inside itself rather than being clipped by the
     sheet's `overflow: hidden` when even that is not enough. */
  flex: 0 1 auto;
  overflow-y: auto; overscroll-behavior: contain;
  /* The home-indicator strip is underneath the keyboard, so reserving space
     for it while the keyboard is up wastes ~34px in exactly the state where
     the budget is tightest. */
  padding: 10px 14px calc(10px + max(0px, env(safe-area-inset-bottom) - var(--kb, 0px)));
  border-top: 1px solid var(--line);
  background: var(--surface);
}

/* fuel availability, at a glance.
   One headline answer first -- the same verdict, in the same colour, that the
   pin on the map is already showing -- then the per-grade detail under it.
   A row of pills alone made the reader assemble the answer themselves. */
.fuel-summary { margin-bottom: 16px; }
.summary-head {
  display: flex; align-items: center; flex-wrap: wrap; gap: 7px;
  margin: 0 0 10px; font-size: 17px; line-height: 1.2;
}
.summary-head b { font-weight: 700; }
.summary-head small { font-size: 12px; font-weight: 500; color: var(--muted); }
.summary-head .wait-tag { margin-left: 0; }
.summary-dot {
  width: 10px; height: 10px; border-radius: 50%; flex: 0 0 auto;
  background: currentColor;
}
.summary-head.ok      { color: var(--ok); }
.summary-head.queue   { color: var(--queue); }
.summary-head.out     { color: var(--out); }
.summary-head.unknown { color: var(--muted); }

.fuel-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.fuel-chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 7px 11px; border-radius: 999px;
  font-size: 13px; font-weight: 700;
  border: 1px solid transparent;
}
.fuel-chip small { font-weight: 500; font-size: 11px; opacity: .8; }
.fuel-chip.ok    { background: rgba(34,197,94,.15);  color: var(--ok);    border-color: rgba(34,197,94,.35); }
.fuel-chip.queue { background: rgba(245,166,35,.15); color: var(--queue); border-color: rgba(245,166,35,.35); }
.fuel-chip.out   { background: rgba(239,68,68,.13);  color: var(--out);   border-color: rgba(239,68,68,.3); }
.fuel-chip.unknown { background: var(--surface-2); color: var(--muted); }
.summary-empty { font-size: 13px; color: var(--muted); margin: 0 0 16px; }

/* The end of the photo wall: the way on to the next page, or the honest end
   of the window. Centred and quiet -- it is a footer, not a card. */
.feed-more, .feed-end {
  list-style: none; text-align: center; padding: 4px 0 10px;
}
.feed-end { font-size: 12px; color: var(--muted); }
.feed-more .btn { min-width: 140px; }

/* An estimate, everywhere it is shown back: amber like a queue, and never
   the loudest thing in its row -- it is one person's guess at how long a
   line is, not a measurement. */
.wait-tag {
  display: inline-block; margin-left: 6px;
  padding: 1px 7px; border-radius: 999px;
  background: rgba(245,166,35,.15); color: var(--queue);
  font-size: 11px; font-weight: 700; white-space: nowrap;
}
.popup-wait {
  margin: 4px 0 0; font-size: 12px; font-weight: 700; color: var(--queue);
}

/* comments */
.comments { display: flex; flex-direction: column; gap: 14px; }
/* This heading anchors the whole lower half of the sheet; at 11px uppercase
   it read as a label on something else. The count tells you whether the
   thread is worth scrolling before you scroll it. */
.comments-head {
  font-size: 13.5px; font-weight: 700; color: var(--text); margin: 0;
}
.comments-count {
  display: inline-block; margin-left: 4px; padding: 1px 7px;
  border-radius: 999px; background: var(--surface-2); color: var(--muted);
  font-size: 11.5px; font-weight: 700;
}
.comment { display: flex; gap: 9px; }
.comment .avatar { width: 32px; height: 32px; font-size: 15px; }
.comment-body { flex: 1; min-width: 0; }
/* `inline-block` shrink-wrapped the bubble around its shortest line, so a
   photo sat in a narrow column with the rest of the row left empty. */
.comment-bubble {
  background: var(--surface-2); border-radius: 18px; padding: 10px 13px;
  display: block; max-width: 100%;
}
.comment-who {
  display: flex; align-items: baseline; flex-wrap: wrap; gap: 6px;
  font-size: 12.5px; font-weight: 700; margin: 0 0 3px;
}
.comment-verdict { font-size: 13px; font-weight: 700; margin: 0; }
/* Colour alone loses in sunlight and to colour blindness; the dot repeats the
   verdict as a shape. */
.verdict-dot {
  display: inline-block; width: 8px; height: 8px; border-radius: 50%;
  margin-right: 6px; background: currentColor; vertical-align: middle;
}
.verdict-note {
  display: block; margin-top: 2px;
  font-size: 11px; font-weight: 500; color: var(--muted);
}
.comment-verdict.ok { color: var(--ok); }
.comment-verdict.queue { color: var(--queue); }
.comment-verdict.out { color: var(--out); }
.comment-text { margin: 3px 0 0; font-size: 13.5px; line-height: 1.4; word-wrap: break-word; }
/* The photo is the evidence the report rests on -- it was the smallest thing
   in the thread. */
.comment img.comment-photo-img {
  margin-top: 7px; width: 100%; max-height: 300px; object-fit: cover;
  border-radius: 14px; border: 1px solid var(--line); display: block;
}
.comment-when { font-size: 11px; font-weight: 500; color: var(--muted); margin: 0; }
.comment .src-tag { margin-left: 0; }

/* Reactions. A reader who acted on a report -- drove there because someone
   said the queue was short -- had no way to say so back; these five say it
   in one tap. Ghost pills sit outside the bubble so they read as the
   reader's voice, not as part of the reporter's message. */
/* The reaction pills and the reply button are one row of things you can do
   about a report, so they wrap together rather than each keeping a line of
   its own on a narrow phone. At 320px five pills plus Хариулах take two
   lines, which is what the wrap is for. */
.comment-actions {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-top: 6px;
}
.comment-react { display: flex; flex-wrap: wrap; gap: 6px; }
/* Tighter than a labelled pill, because these carry a glyph where a labelled
   one carries a sentence. `.glyph-react` and not `.comment-react`: the wall
   and the thread both draw the five out of reactionPillHtml and both carry
   it, but алдаршуулах's row is a .comment-react as well and its pills keep
   their words -- 👀 Харсан, 😤 Ичмээр -- so it must not be reached from
   here. Хариулах and Гомдол мэдүүлэх are safe either way; they are siblings
   of the row rather than inside it. */
.glyph-react .react-pill { gap: 4px; padding: 3px 9px; font-size: 13px; }
.glyph-react .react-count { font-size: 11px; }
.react-pill {
  display: inline-flex; align-items: center; gap: 5px;
  font: inherit; font-size: 12px; font-weight: 700; line-height: 1.35;
  padding: 4px 10px; border: 1px solid var(--line); border-radius: 999px;
  background: transparent; color: var(--muted); cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: color .16s ease, border-color .16s ease,
              background-color .16s ease, transform .12s ease;
}
.react-pill.on {
  border-color: var(--accent); color: var(--accent);
  background: rgba(255,122,26,.12);
}
/* The press itself, before any network. In a fuel queue on a bad connection
   the POST can take seconds, and a pill that does nothing until it lands
   gets tapped again. */
.react-pill:active { transform: scale(.93); }
.react-count { font-variant-numeric: tabular-nums; }
.react-emoji { display: inline-block; }
/* `pop` is added by the click handler, never by the renderer -- keyed off
   `.on` it would fire on every card each time the thread repaints, so
   opening a station you had already reacted at would set the whole thread
   twitching. The class then stays put; paintReaction replays the animation
   by removing it, forcing a reflow and adding it back. */
.react-pill.pop { animation: react-flash .5s ease-out; }
.react-pill.pop .react-emoji { animation: react-beat .5s ease-out; }
.react-pill.pop .react-count { animation: react-rise .34s ease-out; }
@keyframes react-beat {
  0%   { transform: scale(1); }
  30%  { transform: scale(1.45) rotate(-9deg); }
  55%  { transform: scale(.92); }
  80%  { transform: scale(1.1); }
  100% { transform: scale(1); }
}
@keyframes react-flash {
  from { box-shadow: 0 0 0 0 rgba(255,122,26,.45); }
  to   { box-shadow: 0 0 0 9px rgba(255,122,26,0); }
}
@keyframes react-rise {
  from { opacity: 0; transform: translateY(7px); }
  to   { opacity: 1; transform: none; }
}
/* Bends the opacity-only rule the skeletons keep, the way #report-done does,
   so the cancel lives here with the component -- a media query carries no
   specificity, and placed up in the shared allow-list it would lose on
   source order to the rules above. */
@media (prefers-reduced-motion: reduce) {
  .react-pill,
  .react-pill.pop,
  .react-pill.pop .react-emoji,
  .react-pill.pop .react-count { animation: none; transition: none; }
  .react-pill:active { transform: none; }
}
/* Carries `.react-pill` itself, so padding, border and line-height come from
   the rule above and the placeholder row matches the live row's height by
   construction rather than by a copied magic number. The width is the one
   thing that cannot come from the live rule, because the live pill is sized
   by its content and a placeholder has none: 116px stands in for a labelled
   pill -- Хариулах -- and .skel-pill-glyph for one of the five reactions,
   which since 0015 carry a glyph and a count and nothing else. The five sit
   inside a real .comment-react, so they pick up the tightened padding the
   live row has and the placeholder does not drift from it. */
.react-pill.skel-pill-sm {
  width: 116px; border-color: transparent; color: transparent;
  background: var(--surface-2);
  animation: skel-pulse 1.2s ease-in-out infinite;
}
.react-pill.skel-pill-glyph { width: 40px; }
@media (prefers-reduced-motion: reduce) {
  .react-pill.skel-pill-sm { animation: none; opacity: .6; }
}
/* Replies. A report is a claim about a station; a reply is the conversation
   under it -- text only, one level deep, and nothing the map ever reads. The
   button that opens one has no rules of its own: it carries `.react-pill`,
   so it lines up with the two pills beside it by construction rather than by
   a second set of numbers kept in step by hand.

   The rail is what says "under", not "after". An indent alone is read as a
   margin on a 390px screen, where there is not enough width left to spend on
   one deep enough to be unmistakable. */
.comment-replies {
  margin: 7px 0 0 6px; padding-left: 11px;
  border-left: 2px solid var(--line);
  display: flex; flex-direction: column; gap: 8px;
}
.reply-who {
  display: flex; align-items: baseline; flex-wrap: wrap; gap: 6px;
  font-size: 11.5px; font-weight: 700; margin: 0 0 2px; color: var(--muted);
}
.reply-when { font-size: 10.5px; font-weight: 500; color: var(--muted); }
/* No bubble: a reply nested inside the rail inside the thread would be a
   third box on one line of text, and the rail already says whose turn it is. */
.reply-text { margin: 0; font-size: 13px; line-height: 1.4; word-wrap: break-word; }
.reply-form { display: flex; align-items: flex-end; gap: 7px; }
.reply-input {
  flex: 1; min-width: 0; resize: none; overflow-y: auto;
  font: inherit; font-size: 13px; line-height: 1.4;
  padding: 7px 11px; border: 1px solid var(--line); border-radius: 16px;
  background: var(--surface-2); color: var(--text);
}
.reply-input:focus { outline: none; border-color: var(--accent); }
.reply-input::placeholder { color: var(--muted); }
.reply-send {
  flex: none; font: inherit; font-size: 12px; font-weight: 700;
  padding: 7px 13px; border: 0; border-radius: 999px;
  background: var(--accent); color: #fff; cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
/* aria-disabled rather than disabled, the same way #report-submit does it: a
   tap still reaches the handler, so an empty box can say why nothing
   happened instead of swallowing the tap in silence. */
.reply-send[aria-disabled="true"] { opacity: .45; }

.comments-empty { font-size: 13px; color: var(--muted); text-align: center; padding: 18px 0; }

/* ---------- skeletons ----------
   The sheet now goes up on the tap and fills when the thread arrives, so
   something has to hold the shape in between.

   These are the only animated rules in the file, and they pulse opacity
   rather than cross-fading a second colour: opacity needs no palette of its
   own, so the light override at the top of this file covers it for free.
   Everything is scoped under .skel -- nothing here may reach .chip, whose
   deliberate lack of a transition is what makes a tap feel like it landed. */
.skel {
  display: block; background: var(--surface-2); border-radius: 999px;
  animation: skel-pulse 1.2s ease-in-out infinite;
}
.skel-line { height: 10px; margin: 5px 0; }
.skel-pill { height: 32px; width: 116px; }
.skel-bubble { width: 100%; }
.skel-text {
  display: inline-block; min-width: 140px; border-radius: 999px;
  background: var(--surface-2); color: transparent;
  animation: skel-pulse 1.2s ease-in-out infinite;
}
.w20 { width: 20%; } .w30 { width: 30%; } .w40 { width: 40%; }
.w70 { width: 70%; } .w-sm { width: 78px; }

@keyframes skel-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .45; } }
@keyframes hint-flash { 0%, 100% { opacity: 1; } 25%, 75% { opacity: .25; } }

/* ---------- after Илгээх ----------
   These two bend the opacity-only rule above, using `transform` to bring the
   thank-you and the +1 in. Scoped entirely under #report-done so neither can
   reach .chip. The in-flight state does not bend it at all -- it reuses
   skel-pulse. Colour comes from the tokens, so both schemes are covered
   without a second palette. */
@keyframes done-beat { 0%, 100% { transform: scale(1); }
                       22%      { transform: scale(1.3); }
                       40%      { transform: scale(1); }
                       58%      { transform: scale(1.18); } }
@keyframes done-rise { from { opacity: 0; transform: translateY(10px); }
                         to { opacity: 1; transform: none; } }
@keyframes done-plus { 0%   { opacity: 0; transform: translateY(6px) scale(.8); }
                       30%  { opacity: 1; transform: translateY(-6px) scale(1.15); }
                       100% { opacity: 0; transform: translateY(-24px) scale(1); } }

/* An allow-list, not a blanket rule: anything new that animates has to be
   named here. Note also that it must sit *after* the rules it cancels --
   a media query carries no specificity of its own, so an equal-specificity
   override placed earlier in the file simply loses on source order. The
   #report-done entries therefore live down with that component. */
@media (prefers-reduced-motion: reduce) {
  .skel, .skel-text, .form-hint.nudge, .form-label.nudge { animation: none; }
  .skel, .skel-text { opacity: .6; }
}

/* ---------- demo sketches ----------
   Deliberately striped and labelled. These panels show invented numbers, so
   a screenshot of one must never pass for the real product. */
.demo-block {
  margin-bottom: 14px; padding: 12px 14px; border-radius: 18px;
  background: repeating-linear-gradient(
    45deg, rgba(245,166,35,.07), rgba(245,166,35,.07) 10px,
    transparent 10px, transparent 20px);
  border: 1.5px dashed var(--queue);
}
.demo-tag {
  margin: 0 0 8px; font-size: 10px; font-weight: 800; letter-spacing: .8px;
  color: var(--queue);
}
.demo-title { margin: 0 0 10px; font-size: 14px; font-weight: 700; }
.demo-loading { margin: 0; font-size: 12.5px; color: var(--muted); }
.demo-note {
  margin: 10px 0 0; font-size: 11px; color: var(--muted); line-height: 1.45;
}
.tank { display: flex; align-items: center; gap: 9px; margin-bottom: 6px; }
.tank-name { flex: 0 0 84px; font-size: 12px; }
.tank-bar {
  flex: 1; height: 9px; border-radius: 999px;
  background: var(--surface-2); overflow: hidden;
}
.tank-bar i { display: block; height: 100%; border-radius: 999px; }
.tank-bar i.ok  { background: var(--ok); }
.tank-bar i.mid { background: var(--queue); }
.tank-bar i.low { background: var(--out); }
.tank-pct { flex: 0 0 38px; text-align: right; font-size: 12px; font-weight: 700; }
.tank-pct.ok { color: var(--ok); }
.tank-pct.mid { color: var(--queue); }
.tank-pct.low { color: var(--out); }
.cctv-strip { display: flex; gap: 6px; overflow-x: auto; padding-bottom: 4px; }
.cctv-strip figure { margin: 0; flex: 0 0 auto; width: 108px; }
.cctv-strip img {
  width: 108px; height: 76px; object-fit: cover; border-radius: 10px;
  display: block; background: var(--surface-2);
}
.cctv-strip figcaption {
  font-size: 9.5px; color: var(--muted); text-align: center; margin-top: 3px;
}

/* the same sketches, inside a map popup */
/* The camera is now the one sketch with a button, so it gets the width. */
.btn-cam {
  margin-top: 6px; font-size: 13.5px; padding: 11px 8px !important;
  color: var(--queue); border-color: rgba(245,166,35,.55);
  background: rgba(245,166,35,.10);
}

/* Tank gauge beside the station name. `right` clears Leaflet's own close
   button, which sits in the same corner. */
.tank-mini {
  position: absolute; top: 0; right: 24px;
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  width: 46px;
}
.tank-glass {
  position: relative; width: 20px; height: 30px;
  border: 1.5px solid var(--line); border-radius: 5px 5px 7px 7px;
  background: var(--surface-2); overflow: hidden;
}
.tank-glass i {
  position: absolute; left: 0; right: 0; bottom: 0;
  display: block; background: var(--unknown);
  transition: height .25s ease;
}
.tank-mini.ok  .tank-glass i { background: var(--ok); }
.tank-mini.mid .tank-glass i { background: var(--queue); }
.tank-mini.low .tank-glass i { background: var(--out); }
.tank-mini-pct { font-size: 9.5px; font-weight: 800; line-height: 1; white-space: nowrap; }
.tank-mini.ok  .tank-mini-pct { color: var(--ok); }
.tank-mini.mid .tank-mini-pct { color: var(--queue); }
.tank-mini.low .tank-mini-pct { color: var(--out); }
.tank-mini-tag {
  font-size: 7px; font-weight: 800; letter-spacing: .5px; color: var(--queue);
}
/* Keep the station name clear of the gauge. */
/* The gauge occupies 46px starting 24px from the right edge, so the text
   has to clear 70px of it, not 58. */
.popup h4, .popup p { padding-right: 74px; }
.popup-demo {
  margin-top: 8px; padding: 9px 10px; border-radius: 14px;
  border: 1.5px dashed var(--queue);
  background: rgba(245,166,35,.07);
}
.popup-demo .demo-tag { margin: 0 0 7px; font-size: 9px; }
.popup-demo .demo-note { margin: 7px 0 0; font-size: 10px; }
.popup-demo .tank { gap: 6px; margin-bottom: 4px; }
.popup-demo .tank-name { flex: 0 0 62px; font-size: 10.5px; }
.popup-demo .tank-pct { flex: 0 0 32px; font-size: 10.5px; }
.popup-demo .cctv-strip figure { width: 76px; }
.popup-demo .cctv-strip img { width: 76px; height: 56px; }

/* composer */
/* This block and the chips below it were dropped by accident when the
   station sheet was rebuilt as a comment thread. The markup kept using the
   classes, so the compose form has been rendering with browser defaults
   since -- and choosing АИ-92 applied a class nothing styled, which is why
   it gave no feedback at all. */
.form-label {
  font-size: 12px; color: var(--muted); margin: 0 0 8px;
  text-transform: uppercase; letter-spacing: .6px;
}
.form-label .req { color: var(--accent); }

.photo-slot { margin-bottom: 14px; }
.photo-cta {
  width: 100%; cursor: pointer;
  display: flex; flex-direction: column; align-items: center; gap: 3px;
  padding: 18px 14px;
  background: var(--surface-2);
  border: 1.5px dashed var(--line);
  border-radius: 18px;
  color: var(--text);
}
.photo-cta:hover { border-color: var(--accent); background: rgba(255,122,26,.07); }
.photo-icon { font-size: 26px; line-height: 1; }
.photo-cta-text { font-size: 14px; font-weight: 700; }
.photo-cta-sub { font-size: 11.5px; color: var(--muted); }

.photo-preview { margin-bottom: 14px; }
.photo-preview img {
  width: 100%; max-height: 210px; object-fit: cover;
  border-radius: 18px; border: 1px solid var(--line); display: block;
}
.photo-meta {
  display: flex; align-items: center; gap: 10px;
  margin-top: 7px; font-size: 11.5px; color: var(--muted);
}
.photo-meta .btn { margin-left: auto; }
.photo-busy { font-size: 12.5px; color: var(--muted); text-align: center; padding: 14px; }

.chip-row { display: flex; flex-wrap: wrap; gap: 7px; margin-bottom: 10px; }
.chip {
  border: 1px solid var(--line); background: var(--surface-2); color: var(--text);
  padding: 9px 15px; border-radius: 999px;
  font-size: 13px; font-weight: 600; cursor: pointer;
  /* No transition: a tap should land the instant it is made. Fading the
     selection in over 120ms is precisely the thing that reads as "did that
     work?" on a phone in a queue. */
}
.chip:hover { border-color: var(--muted); }
/* Selection has to be obvious at arm's length in a car: colour, fill and a
   ring, not one subtle border change. */
.chip.selected {
  border-color: var(--accent); background: rgba(255,122,26,.16); color: var(--accent);
  box-shadow: 0 0 0 3px rgba(255,122,26,.15);
}
.chip.status.selected[data-status="available"] {
  border-color: var(--ok); background: rgba(34,197,94,.18); color: var(--ok);
  box-shadow: 0 0 0 3px rgba(34,197,94,.15);
}
.chip.status.selected[data-status="queue"] {
  border-color: var(--queue); background: rgba(245,166,35,.18); color: var(--queue);
  box-shadow: 0 0 0 3px rgba(245,166,35,.15);
}
.chip.status.selected[data-status="out"] {
  border-color: var(--out); background: rgba(239,68,68,.18); color: var(--out);
  box-shadow: 0 0 0 3px rgba(239,68,68,.15);
}
.status-row { margin-bottom: 12px; }
/* The wait chips share the status palette rather than the orange used for
   every other selection: the question is about the queue, and answering it
   should read as part of the same answer. */
.chip.wait.selected {
  border-color: var(--queue); background: rgba(245,166,35,.18); color: var(--queue);
  box-shadow: 0 0 0 3px rgba(245,166,35,.15);
}
/* `hidden` alone loses to the display rules above and below -- a flex row is
   still a flex row to the cascade -- so a row nobody was asked for has to be
   switched off explicitly. */
.chip-row[hidden], .form-label[hidden] { display: none; }

/* Collapsed until the composer is engaged, so the comment thread keeps the
   room. Tapping the input or the camera opens it. */
.sheet-foot .chip-row.tight { display: none; gap: 8px; margin-bottom: 14px; }
.sheet-foot .form-label { display: none; }
.sheet-foot.open .form-label { display: block; }
/* `hidden` has to keep winning over both rules above, exactly as it does for
   the row -- this is what keeps the wait question off an `out` report the
   server would reject. */
.sheet-foot.open .form-label[hidden] { display: none; }

/* Every option on screen, wrapped onto as many lines as it takes.
   These rows briefly scrolled sideways to save vertical space. That is the
   wrong trade for the people filing these reports -- drivers in a fuel queue,
   many of them older -- because a sideways row gives no reliable sign that
   more choices exist, and swiping a narrow strip accurately is a fine-motor
   task. Six fuel grades, four ever visible. Height is the cheaper thing to
   spend. */
.sheet-foot.open .chip-row.tight {
  display: flex; flex-wrap: wrap;
}
.sheet-foot.open .chip-row.tight[hidden] { display: none; }
/* The hint costs a line the footer cannot spare now that it is competing
   with a keyboard, and the labels above already say which answer is still
   missing. So it stays out of the way until someone actually asks -- taps
   the send arrow before the form is ready -- and then stays for the rest of
   that draft. See nudgeToMissing(). */
.sheet-foot .form-hint { display: none; }
.sheet-foot.open .form-hint.show { display: block; }
.chip-row.tight::-webkit-scrollbar { display: none; }
/* white-space matters as much as flex here: without it the chips compress and
   wrap their own label onto a second line instead of overflowing to scroll.
   The size is a thumb target -- these are tapped one-handed, standing in a
   queue, and at 29px they were half of what that needs. */
.chip-row.tight .chip {
  flex: 0 0 auto; padding: 15px 18px; font-size: 16px; line-height: 1.25;
  white-space: nowrap;
}
/* Sentence case at 14px in the body colour. It was 10.5px uppercase in the
   muted grey -- the least legible text in the app, on the labels that say
   what is being asked. Uppercase Cyrillic is harder to read than uppercase
   Latin, and small-and-grey compounds it. */
.sheet-foot .form-label {
  font-size: 14px; font-weight: 600; letter-spacing: 0; text-transform: none;
  color: var(--text); margin: 0 0 7px;
}
/* Which question is still outstanding. Replaces the ring that used to sit
   around the row -- see syncSubmit(). Not colour alone: accent orange on the
   light scheme's white is about 2.6:1 at this size, so the weight and the
   marker are what actually carry the state. */
.sheet-foot .form-label.is-next { color: var(--accent); font-weight: 700; }
.sheet-foot .form-label.is-next::before { content: "▸ "; }
.form-label.nudge { animation: hint-flash .5s ease; }
/* Stacked, full width, each one labelled. Three shapes in a row -- round
   icon, wide field, round arrow -- put the required action in the smallest
   box and left the primary action as an unlabelled glyph. */
.comment-bar { display: grid; gap: 10px; }

/* Collapsed, the bar is a single tappable line whose placeholder says what
   it is for. The photo and send have nothing to do until it is open. */
.sheet-foot:not(.open) .comment-photo,
.sheet-foot:not(.open) .comment-send,
.sheet-foot:not(.open) .comment-photo-preview { display: none; }

.comment-photo, .comment-send {
  width: 100%; min-height: 52px; padding: 12px 16px; border-radius: 26px;
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  font-size: 16px; font-weight: 700; cursor: pointer;
  border: 1px solid var(--line); background: var(--surface-2); color: var(--text);
}
/* A photo is required, and a small round camera said the opposite. Tinted in
   the accent while it is outstanding; once taken the preview below replaces
   it, so the row never shows both. Alpha over --surface, so the light scheme
   needs no separate rule. */
.comment-photo {
  border-color: rgba(255,122,26,.5); background: rgba(255,122,26,.1);
  color: var(--accent);
}
.comment-photo.has { display: none; }
.comment-photo.is-next { box-shadow: 0 0 0 3px rgba(255,122,26,.16); }

.comment-send { background: var(--accent); border-color: var(--accent); color: #1a0e03; }
/* Not ready reads as *not ready*, not as broken: the old 40% opacity over
   orange came out a dull brown that looked like a rendering fault. */
.comment-send:disabled,
.comment-send[aria-disabled="true"] {
  background: var(--surface-2); border-color: var(--line); color: var(--muted);
}
.comment-send.sending { opacity: .55; }

/* A textarea, so it can grow. 16px because that is the legible size and also
   the threshold below which iOS zooms the page on focus. resize:none because
   autoGrowNote() owns the height. */
.comment-input {
  width: 100%; min-width: 0; padding: 14px 16px;
  background: var(--surface-2); border: 1px solid var(--line);
  border-radius: 26px; color: var(--text);
  font-size: 16px; line-height: 1.35;
  min-height: 52px; max-height: 104px;
  resize: none; overflow-y: auto;
}
.comment-input:focus { outline: none; border-color: var(--accent); }

/* Shown in place of the camera button once there is a photo -- the proof,
   its size, and the way to drop it and take another. */
.comment-photo-preview {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 10px; border-radius: 18px;
  background: rgba(34,197,94,.1); border: 1px solid rgba(34,197,94,.35);
  font-size: 12px; color: var(--muted);
}
.comment-photo-preview img {
  width: 46px; height: 46px; object-fit: cover;
  border-radius: 14px; border: 1px solid var(--line);
}
.comment-photo-preview .btn { margin-left: auto; }
/* `.form-hint` never had a size of its own, so it inherited 16px body text
   and was the loudest thing in the composer -- louder than the labels it is
   supplementing. It is a footnote. */
.sheet-foot .form-hint {
  margin: 7px 0 0; font-size: 12px; line-height: 1.4; color: var(--muted);
}
/* The hint already knew exactly which of the four answers was missing; it
   just had no way to point at it. The chip rows are pointed at by their own
   labels (`.form-label.is-next` above); the photo button has no label, so it
   keeps the accent ring, borrowed from .chip.selected. */
.form-hint[data-step="ready"] { color: var(--ok); }
.form-hint.nudge { animation: hint-flash .5s ease; }
.comment-photo.is-next { box-shadow: 0 0 0 3px rgba(255,122,26,.16); }

/* ---------- compose modal (Facebook-style post box) ---------- */
.modal-backdrop { position: fixed; top: 0; right: 0; bottom: 0; left: 0; inset: 0; background: rgba(0,0,0,.6); z-index: 1500; }
.modal {
  position: fixed; z-index: 1600;
  left: 50%; top: 50%;
  transform: translate(-50%, calc(-50% - var(--kb, 0px) / 2));
  width: min(520px, calc(100% - 24px));
  max-height: 90vh;
  max-height: min(90dvh, calc(100dvh - var(--kb, 0px) - 24px));
  display: flex; flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 20px;
  box-shadow: 0 24px 70px rgba(0,0,0,.55);
  overflow: hidden;
}
.modal-head {
  display: flex; align-items: center; justify-content: center;
  padding: 14px 16px; border-bottom: 1px solid var(--line); position: relative;
}
.modal-head h3 { margin: 0; font-size: 16.5px; }
.modal-close {
  position: absolute; right: 12px; top: 50%; transform: translateY(-50%);
  width: 32px; height: 32px; border-radius: 50%;
  background: var(--surface-2); border: none; color: var(--muted);
  font-size: 21px; line-height: 1; cursor: pointer;
}
.modal-body { padding: 16px; overflow-y: auto; flex: 1; }
.modal-foot { padding: 12px 16px 16px; border-top: 1px solid var(--line); }

/* phone sign-in.

   The entry point is a text button rather than a filled one on purpose: an
   account is optional here, and a button that looks like the primary action
   would suggest the site wants one before you can report. */
.linkish {
  background: none; border: none; padding: 4px 8px; cursor: pointer;
  font: inherit; font-size: 13px; color: var(--muted);
  text-decoration: underline; text-underline-offset: 3px;
}
.linkish:hover { color: var(--text); }
.auth-modal { width: min(380px, calc(100% - 24px)); }
.auth-body { padding: 18px 18px 20px; }
/* The code is six characters somebody is copying off another screen, so it
   gets the spacing that makes a mistyped digit visible. */
#auth-code {
  text-align: center; letter-spacing: 8px; font-size: 22px;
  font-variant-numeric: tabular-nums;
}
.auth-who {
  margin: 0 0 6px; font-size: 17px; font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.auth-err {
  margin: 12px 0 0; font-size: 13px; line-height: 1.45;
  color: var(--out);
}

/* location primer */
.geo-primer { width: min(400px, calc(100% - 24px)); }
.geo-body { padding: 26px 22px 22px; text-align: center; }
.geo-icon { font-size: 40px; line-height: 1; margin-bottom: 10px; }
.geo-primer h3 { margin: 0 0 8px; font-size: 19px; }
.geo-text { margin: 0 0 16px; font-size: 13.5px; color: var(--muted); line-height: 1.5; }
.geo-text b { color: var(--text); }
.geo-points {
  list-style: none; margin: 0 0 20px; padding: 0;
  text-align: left; display: flex; flex-direction: column; gap: 9px;
}
.geo-points li {
  font-size: 12.5px; color: var(--text); line-height: 1.4;
  background: var(--surface-2); border-radius: 14px; padding: 9px 11px;
}
/* "You blocked this, here is the way back." Numbered rather than ticked like
   .geo-points above: these are steps to follow in order, not benefits to read,
   and the number is what someone keeps their place with while they are three
   menus deep in another app. */
.geo-blocked { margin-bottom: 16px; text-align: left; }
.geo-blocked-lead {
  margin: 0 0 12px; font-size: 13px; line-height: 1.5; color: var(--text);
}
.geo-steps {
  margin: 0; padding: 0 0 0 22px;
  display: flex; flex-direction: column; gap: 9px;
}
.geo-steps li {
  font-size: 12.5px; color: var(--text); line-height: 1.45;
  background: var(--surface-2); border-radius: 14px; padding: 9px 11px;
  /* The marker sits in the gutter the padding-left above reserves, so a
     wrapped step stays aligned under its own text rather than under itself. */
  padding-inline-start: 11px;
}
.geo-steps li::marker { color: var(--accent); font-weight: 700; }
.geo-steps b { color: var(--accent); }
.geo-blocked-alt {
  margin: 12px 0 0; font-size: 12px; line-height: 1.5; color: var(--muted);
}
.geo-blocked-alt:empty { display: none; }

.geo-primer .btn-block + .btn-block { margin-top: 8px; }
.geo-primer .form-hint { margin-top: 12px; }

.avatar {
  width: 40px; height: 40px; border-radius: 50%;
  background: var(--surface-2); border: 1px solid var(--line);
  display: flex; align-items: center; justify-content: center; font-size: 19px;
}

.picker-geo {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  background: rgba(255,122,26,.1); border: 1px solid rgba(255,122,26,.35);
  border-radius: 16px; padding: 11px 14px; margin-bottom: 10px;
  font-size: 12.5px; color: var(--text);
}
.picker-geo span { flex: 1; min-width: 180px; }
.picker-head {
  margin: 0 0 6px; font-size: 11px; font-weight: 700;
  letter-spacing: .5px; text-transform: uppercase; color: var(--muted);
}

/* pick-a-station map */
.picker-map-wrap { position: relative; margin-bottom: 12px; }
#picker-map {
  height: 230px; border-radius: 18px;
  border: 1px solid var(--line);
  background: var(--surface-2);
  z-index: 0;                 /* keep Leaflet panes under the modal chrome */
}
.picker-map-hint {
  position: absolute; left: 50%; bottom: 8px; transform: translateX(-50%);
  z-index: 500; pointer-events: none;
  background: rgba(15,20,25,.82); color: #fff;
  font-size: 11px; font-weight: 600;
  padding: 4px 10px; border-radius: 999px;
  white-space: nowrap;
}
/* station pins inside the picker */
.pick-pin {
  width: 30px; height: 30px; border-radius: 50%;
  background: #fff; border: 2px solid rgba(255,255,255,.9);
  box-shadow: 0 2px 6px rgba(0,0,0,.45);
  display: flex; align-items: center; justify-content: center;
  cursor: pointer; transition: transform .12s ease;
}
.pick-pin:hover { transform: scale(1.15); }
.pick-pin.selected {
  border-color: var(--accent);
  box-shadow: 0 0 0 4px rgba(255,122,26,.35), 0 2px 6px rgba(0,0,0,.45);
  transform: scale(1.2);
}
.pick-label {
  background: rgba(15,20,25,.9); color: #fff;
  font-size: 10.5px; font-weight: 700;
  padding: 2px 6px; border-radius: 5px; white-space: nowrap;
  border: none; box-shadow: none;
}
.pick-label::before { display: none; }

/* No inner scroll: a scroll region inside a scrolling modal is miserable to
   use. The map covers browsing; this list stays short. */
.picker-list { list-style: none; margin: 0 0 12px; padding: 0; }
.picker-more {
  padding: 8px 11px; font-size: 11.5px; color: var(--muted);
  text-align: center; cursor: default;
}
.picker-more:hover { background: none; }
.picker-list li {
  padding: 9px 11px; border-radius: 14px; cursor: pointer;
  display: flex; align-items: center; gap: 8px; font-size: 13.5px;
}
.picker-list li:hover, .picker-list li.active { background: var(--surface-2); }
.picker-list .p-name { font-weight: 650; }
.picker-list .p-meta { font-size: 11.5px; color: var(--muted); margin-left: auto; text-align: right; }
.picker-list .p-km { color: var(--accent); font-size: 12.5px; }
.picker-empty { padding: 14px 4px; font-size: 12.5px; color: var(--muted); text-align: center; }


.textarea {
  width: 100%; min-height: 92px; resize: vertical;
  padding: 12px; margin-bottom: 14px;
  background: transparent; border: 1px solid var(--line);
  border-radius: 10px; color: var(--text);
  font-size: 15px; font-family: inherit; line-height: 1.45;
}
.textarea::placeholder { color: var(--muted); }

/* Only a :focus rule existed here, so these fields fell back to the browser
   default -- a white box with black text, unreadable on the dark surface. */
.input {
  width: 100%; padding: 12px 16px; margin-bottom: 12px;
  background: transparent; border: 1px solid var(--line);
  border-radius: 14px; color: var(--text);
  font-size: 15px; font-family: inherit;
}
.input::placeholder { color: var(--muted); }
select.input { color: var(--text); }
select.input option { background: var(--surface); color: var(--text); }
.textarea:focus, .input:focus { outline: none; border-color: var(--accent); }
.modal .form-label { margin-top: 4px; }
.modal-foot .form-hint { margin: 0 0 10px; }

/* locate control on the map */
/* Sits above Leaflet's attribution corner and clear of the map edge. */
.leaflet-control.locate-ctrl { border: none; box-shadow: none; margin: 0 12px 14px 0; }
.locate-ctrl button {
  height: 38px; padding: 0 13px; border-radius: 999px;
  background: var(--surface); color: var(--text);
  border: 1px solid var(--line); cursor: pointer;
  box-shadow: 0 2px 10px rgba(0,0,0,.3);
  display: flex; align-items: center; gap: 7px;
  font-size: 13px; font-weight: 650; white-space: nowrap;
  font-family: inherit;
}
.locate-icon { font-size: 15px; line-height: 1; }
.locate-ctrl button.busy { opacity: .55; }
.locate-ctrl button.on { color: var(--accent); border-color: var(--accent); }

/* ---------- toast ---------- */
/* ---------- after Илгээх ----------
   Sits over the sheet while a report is in flight and while it is being
   thanked for. Rides var(--kb) like the toast does, because this fires at
   exactly the moment the keyboard is most likely still up. */
.done-backdrop { position: fixed; top: 0; right: 0; bottom: 0; left: 0; inset: 0; background: rgba(0,0,0,.6); z-index: 1900; }
.report-done {
  position: fixed; z-index: 1950;
  left: 50%; bottom: auto; top: 50%;
  transform: translate(-50%, calc(-50% - var(--kb, 0px) / 2));
  width: min(360px, calc(100% - 32px));
  max-height: calc(100vh - var(--kb, 0px) - 24px);
  max-height: calc(100dvh - var(--kb, 0px) - 24px); overflow-y: auto;
  padding: 28px 22px 22px;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: 24px; box-shadow: 0 24px 60px rgba(0,0,0,.5);
  text-align: center;
}

.done-mark { width: 84px; height: 84px; margin: 0 auto; }
.done-mark svg { width: 100%; height: 100%; display: block; }
/* A quiet track. It used to be a gapped arc chasing its own tail; the tick
   is the only thing that moves now. */
.done-ring {
  fill: none; stroke-width: 3; stroke: var(--line);
  stroke-dasharray: 145; stroke-dashoffset: 0; stroke-linecap: round;
  transition: stroke .3s ease;
}
.report-done.done .done-ring { stroke: var(--ok); }

/* The tick IS the loader.

   It draws on a long decelerating curve toward a mark it never quite
   reaches -- stopping around 97% -- because it cannot honestly claim to be
   finished while the server has not answered. When the reply lands it closes
   the remaining sliver and turns green.

   A transition rather than a keyframe, and that is the whole trick: a
   transition interrupts and continues from wherever the value currently is.
   A reply at 300ms completes a barely-started tick smoothly; one at five
   seconds closes an almost-finished one. A keyframe would restart from its
   own `from` and jump. */
.done-tick {
  fill: none; stroke: var(--accent); stroke-width: 4;
  stroke-linecap: round; stroke-linejoin: round;
  stroke-dasharray: 40; stroke-dashoffset: 40;
  transition: stroke-dashoffset 2.4s cubic-bezier(0, .65, .2, 1), stroke .3s ease;
}
.report-done.sending .done-tick { stroke-dashoffset: 6; }
.report-done.done .done-tick {
  stroke-dashoffset: 0; stroke: var(--ok);
  transition-duration: .28s;      /* cycles across both properties */
}

/* Once the tick has slowed to a stop it would sit there looking frozen on a
   genuinely slow upload. Reusing the skeleton's pulse keeps it alive without
   a second keyframe -- and puts the in-flight signal back inside this file's
   opacity-only rule, which the spinner's transform was breaking. */
.report-done.sending .done-mark { animation: skel-pulse 1.6s ease-in-out infinite; }

.done-sending { margin: 16px 0 0; font-size: 16px; font-weight: 600; color: var(--muted); }
.report-done.done .done-sending { display: none; }

.done-body { display: none; }
.report-done.done .done-body {
  display: block;
  animation: done-rise .3s .35s ease both;
}
.done-body h3 { margin: 18px 0 8px; font-size: 22px; }
.done-body p { margin: 0 0 6px; font-size: 15px; line-height: 1.45; color: var(--muted); }
/* Sized to the line it sits in, and orange because every other colour in
   this app already means something -- green is "гайгүй", red is "дууссан",
   amber is "дараалалтай". The brand accent is the one that reads as us
   thanking them rather than as a status. Two soft beats, then it rests;
   a permanent loop would pull the eye off the score. */
/* Tied to the last word so it can never wrap onto a line by itself. */
.done-body .nowrap { white-space: nowrap; }
.done-heart {
  width: 1.15em; height: 1.15em; margin-left: .3em; vertical-align: -.2em;
  display: inline-block; fill: var(--accent); transform-origin: 50% 55%;
}
.report-done.done .done-heart { animation: done-beat 1s .8s ease 2 both; }

/* The score. The number is the reward, so it gets the size, and the +1
   flies off it rather than the total simply arriving already changed. */
.done-score {
  /* Top padding is the runway the +1 rises through -- without it the two
     numbers sit on top of each other for the length of the animation. */
  position: relative; margin: 18px 0 20px; padding: 30px 12px 14px;
  border-radius: 18px;
  background: rgba(34,197,94,.1); border: 1px solid rgba(34,197,94,.3);
}
.done-plus {
  position: absolute; top: 6px; left: 50%; margin-left: -14px;
  font-size: 17px; font-weight: 800; color: var(--ok);
  opacity: 0; pointer-events: none;
}
.report-done.done .done-plus { animation: done-plus 1.1s .45s ease forwards; }
.done-total { display: block; font-size: 34px; font-weight: 800; color: var(--ok); line-height: 1.1; }
.done-total-label { display: block; margin-top: 4px; font-size: 12.5px; color: var(--muted); }

/* Placed here, not up with the other reduced-motion rules, because it has to
   come after the animations it is switching off -- see the note there. */
@media (prefers-reduced-motion: reduce) {
  .report-done.sending .done-mark,
  .report-done.done .done-body,
  .report-done.done .done-heart,
  .report-done.done .done-plus { animation: none; }
  /* The tick is a transition now, so `animation: none` would not touch it. */
  .done-tick, .done-ring { transition: none; }
  /* Undrawn while sending -- snapping instantly to a 97%-drawn check reads
     as a bug, and "Илгээж байна…" already says what is happening -- then
     complete once it lands. */
  .report-done.sending .done-tick { stroke-dashoffset: 40; }
  .report-done.done .done-tick { stroke-dashoffset: 0; }
  .report-done.done .done-body { opacity: 1; transform: none; }
  .report-done.done .done-plus { opacity: 1; transform: none; }
}

.toast {
  /* Riding above the keyboard too: submitReport() toasts at exactly the
     moment the keyboard is most likely still up, and the confirmation for
     the action just taken is the one toast that has to be seen. */
  position: fixed; bottom: calc(24px + var(--kb, 0px)); left: 50%; transform: translateX(-50%);
  background: var(--surface-2); color: var(--text);
  border: 1px solid var(--line); border-left: 4px solid var(--accent);
  padding: 12px 18px; border-radius: 10px; font-size: 13.5px;
  z-index: 2000; box-shadow: 0 8px 30px rgba(0,0,0,.4);
  max-width: 90vw;
}
.toast.ok { border-left-color: var(--ok); }
.toast.err { border-left-color: var(--out); }

.attrib {
  padding: 8px 16px; font-size: 10.5px; color: var(--muted);
  background: var(--surface); border-top: 1px solid var(--line); text-align: center;
}

/* ---------- mobile ---------- */
@media (max-width: 780px) {
  .topbar h1 { font-size: 14px; }
  .logo { width: 24px; height: 24px; }
  .layout { flex-direction: column; height: auto; }
  #map { height: 52vh; min-height: 300px; }
  .sidebar { width: 100%; flex: 1 1 auto; border-left: none; border-top: 1px solid var(--line); max-height: none; }
  .station-list { overflow-y: visible; }
  .stat { margin-left: 0; width: 100%; }
  /* `bottom: var(--kb)` is the keyboard fix. A position:fixed sheet is not
     moved by the on-screen keyboard, it is simply covered by it, so the
     composer used to slide under the thing being typed into. syncKeyboardInset()
     publishes how much of the window the keyboard has taken; the sheet lifts by
     that much and gives back the same height, so the thread gets shorter
     instead of the bar disappearing. */
  /* `dvh` does not react to the keyboard on either platform -- that is the
     whole reason --kb has to be measured. Subtracting it from a dvh base is
     therefore sound, but `88dvh - kb` throws away the 12% too: with a 300px
     keyboard that left the sheet 100px shorter than the space it had, and a
     dead band of backdrop above it. min() takes the 88% cap when there is no
     keyboard and the real remaining height when there is. */
  .sheet { top: auto; bottom: var(--kb, 0px); left: 0; transform: none; width: 100%;
           border-radius: 24px 24px 0 0;
           max-height: 88vh;
           max-height: min(88dvh, calc(100dvh - var(--kb, 0px) - 12px)); }
  /* Says "this panel came up from the bottom and goes back down" without
     claiming to be draggable in any way the code does not honour. */
  .sheet-grip {
    display: block; flex: 0 0 auto;
    width: 40px; height: 4px; margin: 8px auto 0;
    border-radius: 999px; background: var(--line);
  }
  .sheet-head { padding-top: 10px; }

  /* Filling the form is a task, not reading, so the sheet claims the screen
     while it is open. `height`, not `max-height`, and deliberately: a
     percentage max-height on a flex child only resolves against a parent
     whose own height is definite, and the footer cap below needs it to.
     Mobile only -- on desktop the sheet is a centred dialog and an
     unconditional height would balloon it. */
  .sheet.composing {
    height: min(96vh, calc(100vh - var(--kb, 0px) - 12px));
    height: min(96dvh, calc(100dvh - var(--kb, 0px) - 12px));
    max-height: none;
  }
  /* Same problem as .sheet, same fix: the station picker, talon and
     new-station forms are all bottom-anchored on a phone and all contain text
     inputs, so the keyboard covers the thing being typed into.

     This used to be two .modal rules in this block -- one setting
     `bottom: var(--kb)`, and this one, further down, setting `bottom: 0`.
     Same selector, same specificity, so this one won and the keyboard
     accommodation above it never applied to a single modal. Merged, with the
     --kb values kept. With no keyboard up --kb is 0px, so nothing moves. */
  .modal { top: auto; bottom: var(--kb, 0px); left: 0; transform: none;
           width: 100%; border-radius: 24px 24px 0 0;
           max-height: 90vh;
           max-height: min(90dvh, calc(100dvh - var(--kb, 0px) - 12px)); }
  /* Equal width instead of auto-by-label -- "Очерь гайгүй" being the
     longest text was stretching that pill wide and leaving "Дууссан"
     looking like an afterthought. The container's own 14px padding is the
     "little space on the right" -- flex-basis 0 fills exactly up to it. The
     clear button (hidden until a filter is active) stays its own size and
     wraps to its own line instead of squeezing the three. */
  .legend .leg:not(.leg-clear) {
    flex: 1 1 0;
    justify-content: center;
    padding-left: 6px;
    padding-right: 6px;
  }
}

/* With an overlay open the page behind must not scroll: on iOS dragging it
   carried the sheet -- and its close button -- out of the visible area. */
body.overlay-open { overflow: hidden; }

/* ---------- queue prompt ----------
   Appears when the phone puts you at a forecourt. Deliberately a card, not a
   modal: someone sitting in a queue should be able to ignore it. */
.queue-prompt {
  position: fixed; z-index: 1400;
  left: 50%; bottom: 16px; transform: translateX(-50%);
  width: min(420px, calc(100% - 24px));
  background: var(--surface);
  border: 1px solid var(--accent);
  border-radius: 20px;
  padding: 16px 16px 14px;
  box-shadow: 0 14px 40px rgba(0,0,0,.5);
}
.queue-close {
  position: absolute; top: 8px; right: 10px;
  background: none; border: none; color: var(--muted);
  font-size: 22px; line-height: 1; cursor: pointer;
}
.queue-where { margin: 0 0 4px; font-size: 12px; color: var(--accent); font-weight: 700; }
.queue-prompt h3 { margin: 0 0 8px; font-size: 16px; padding-right: 24px; }
.queue-estimate {
  margin: 0 0 12px; font-size: 13px; color: var(--text);
  background: var(--surface-2); border-radius: 14px; padding: 9px 11px;
}
.queue-estimate b { font-size: 16px; color: var(--accent); }
.queue-estimate small { display: block; color: var(--muted); font-size: 11px; margin-top: 3px; }
.queue-actions { display: flex; gap: 8px; flex-wrap: wrap; }
.queue-actions .btn { flex: 1 1 40%; justify-content: center; }
/* «Үгүй» is the way out, not a third answer: it drops to its own row rather
   than sitting level with the two that file a report. */
.queue-actions #queue-no { flex: 1 1 100%; }
.btn-out {
  background: var(--out); border-color: var(--out); color: #fff;
}
.btn-out:hover:not(:disabled) { filter: brightness(1.08); }
.queue-photo-note {
  margin: 8px 0 0; font-size: 12px; color: var(--muted); text-align: center;
}

/* ---------- live queue view ----------
   The screen of the person actually queuing. Same footprint as the prompt it
   replaces, so confirming does not make the map jump. */
.queue-mode {
  position: fixed; z-index: 1400;
  left: 50%; bottom: 16px; transform: translateX(-50%);
  width: min(420px, calc(100% - 24px));
  background: var(--surface);
  border: 1px solid var(--queue);
  border-radius: 20px;
  padding: 14px 16px;
  box-shadow: 0 14px 40px rgba(0,0,0,.5);
}
.qm-head { display: flex; align-items: flex-start; gap: 10px; }
.qm-head > div { flex: 1; min-width: 0; }
.qm-where {
  margin: 0; font-size: 14.5px; font-weight: 700;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.qm-sub { margin: 2px 0 0; font-size: 12px; color: var(--muted); }
.qm-cars {
  margin: 12px 0 0; font-size: 15px;
  background: var(--surface-2); border-radius: 14px; padding: 12px;
  text-align: center;
}
.qm-cars b { font-size: 26px; color: var(--queue); vertical-align: -2px; }
.qm-private {
  margin: 9px 0 12px; font-size: 11.5px; color: var(--muted);
  line-height: 1.4; text-align: center;
}

/* Placing your own position by hand. Same footprint as the queue banner it
   can never appear alongside, so the two never need separate layout
   reasoning. Not a modal: tapping the map is the whole point of it. */
.manual-loc {
  position: fixed; z-index: 1400;
  left: 50%; bottom: 16px; transform: translateX(-50%);
  width: min(420px, calc(100% - 24px));
  background: var(--surface);
  border: 1px solid var(--accent);
  border-radius: 20px;
  padding: 12px 16px;
  box-shadow: 0 14px 40px rgba(0,0,0,.5);
  display: flex; align-items: center; gap: 10px;
}
.ml-title { margin: 0; flex: 1; font-size: 13.5px; font-weight: 650; line-height: 1.35; }
/* A crosshair says "this tap means something" without spending a dialog. */
.leaflet-container.picking { cursor: crosshair; }

#sheet-route { margin: 0 0 14px; }

/* Second action on a map pin: reporting a queue is a one-tap thing, so it
   sits beside the button that opens the whole station. */
.btn-queue {
  width: 100%; margin-top: 6px; justify-content: center;
  background: var(--surface-2); color: var(--queue);
  border: 1px solid var(--queue);
}

.newstation-where {
  margin: 0 0 14px; font-size: 12px; color: var(--muted);
  background: var(--surface-2); border-radius: 8px; padding: 8px 10px;
  font-variant-numeric: tabular-nums;
}

/* ---------- очерь board ----------
   Selling your place in the line at one station. It lives inside that
   station's sheet rather than on a board of its own, because a queue spot
   without a station is not a thing anybody can buy -- which also means the
   form needs no station picker at all.

   Deliberately its own .spot* family rather than reusing .ad*: the coupon
   card reserves 74px for a photo and carries neither a countdown, a price,
   nor a facts line. Sharing the classes would mean a future талон tweak
   silently reshaping this. The recipe below is copied value for value, so
   the two still read as the same product. */

/* Third action in the sheet, under "явах зам". The orange there is taken
   and the amber belongs to талон, so this uses the blue .ad-kind.buy
   already established rather than inventing a fourth hue. */
/* Button and its freshness line move together, so the gap below stays 14px
   whether or not there is an offer to date. */
/* "Fill this one next" marker. The report composer already has this, but
   scoped to .sheet-foot (see .sheet-foot .form-label.is-next) -- the очерь
   form lives in a modal, so the class would silently do nothing without
   saying it again here. Same arrow, same accent. */
.modal .form-label.is-next { color: var(--accent); font-weight: 700; }
.modal .form-label.is-next::before { content: "\25B8  "; }

.spot-entry { margin: 0 0 14px; }
.spot-entry .btn-spot { margin: 0; }

.btn-spot {
  margin: 0 0 14px;
  color: #8fa4ff; border-color: rgba(79,107,246,.45);
  background: rgba(79,107,246,.14);
  font-weight: 700;
}
/* How long ago the newest offer was posted. The count alone cannot say
   whether two offers are live or ten minutes from expiring, and this board
   turns over in 90 minutes -- the same reason every status on this site
   carries its age. Relative, like the rest of them: "14 мин өмнө" answers
   "is this worth ringing" and a wall-clock time does not. */
.spot-latest {
  margin: 5px 0 0; text-align: center;
  font-size: 11px; color: var(--muted); font-variant-numeric: tabular-nums;
}

/* Sits on the button itself: the count is the reason to tap it, so it has to
   survive being read at arm's length in a car. */
.spot-count {
  background: var(--accent); color: #1a0e03;
  border-radius: 999px; padding: 1px 7px;
  font-size: 11px; font-weight: 800; line-height: 1.5;
}

/* Back out of the form to the list. Mirror of .modal-close, on the other
   side -- the × still closes the whole modal, which is the one thing a
   person tapping the corner always means. */
.modal-back {
  position: absolute; left: 12px; top: 50%; transform: translateY(-50%);
  width: 32px; height: 32px; border-radius: 50%;
  background: var(--surface-2); border: none; color: var(--muted);
  font-size: 22px; line-height: 1; cursor: pointer;
}
.modal-back:hover { color: var(--text); }

/* Which station these offers are at. The modal is opened from that station's
   sheet, but the sheet is behind a backdrop by then and a phone shows none
   of it, so the name is repeated rather than assumed. */
.spot-station {
  margin: 0 0 12px; font-size: 12.5px; color: var(--muted);
  font-weight: 600;
}
/* Spans the modal the way the sidebar's segmented spans its column. Bare
   .segmented is inline-flex, which inside a modal body leaves a short pill
   group hanging off the left edge rather than reading as tabs. */
.spot-tabs { display: flex; width: 100%; }
.spot-tabs .seg { flex: 1; text-align: center; }

.spot-list {
  display: flex; flex-direction: column; gap: 10px;
  margin-top: 12px;
}

.spot {
  display: flex; gap: 10px;
  padding: 11px;
  background: var(--surface-2); border: 1px solid var(--line);
  border-radius: 18px;
}
/* A sell offer cannot be posted without a photo of the queue, and until now
   that photo was written to storage and drawn nowhere -- proof the poster
   was there, invisible to the person deciding whether to ring them. Same
   74px slot as the талон card, so the two boards read alike. A buy offer
   carries no photo and gets no slot rather than an empty box. */
.spot-thumb {
  flex: 0 0 74px; width: 74px; height: 74px; border-radius: 14px;
  background: var(--bg); overflow: hidden;
  display: flex; align-items: center; justify-content: center; font-size: 26px;
}
.spot-thumb img { width: 100%; height: 100%; object-fit: cover; }
.spot-thumb.skel { border-radius: 14px; }
.spot-body { flex: 1; min-width: 0; }
.spot-top {
  display: flex; align-items: center; gap: 8px; margin-bottom: 6px;
}
.spot-kind {
  display: inline-block; padding: 2px 9px; border-radius: 999px;
  font-size: 10px; font-weight: 800; letter-spacing: .3px;
}
.spot-kind.sell { background: rgba(34,197,94,.16); color: var(--ok); }
.spot-kind.buy  { background: rgba(79,107,246,.20); color: #8fa4ff; }
/* Nothing expires here -- an offer stands until the person who posted it
   takes it down. That makes age the only thing telling you whether a place
   in a queue still exists, so it is stated up top rather than tucked in the
   footer, and it colours as it goes off: muted while fresh, amber past an
   hour, red past three. A four-hour-old offer is not removed, it just stops
   looking like something to ring. */
.spot-age {
  margin-left: auto; font-size: 10.5px; font-weight: 700; color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.spot-age.stale { color: var(--queue); }
.spot-age.old   { color: var(--out); }
/* Grade, position and price on one line, joined by the · this site uses
   everywhere else to join metadata. */
.spot-facts {
  margin: 0 0 5px; font-size: 13px; line-height: 1.45;
  font-variant-numeric: tabular-nums;
}
.spot-price { color: var(--accent); font-weight: 800; }
.spot-msg {
  margin: 0 0 7px; font-size: 12.5px; line-height: 1.4;
  color: var(--muted); word-break: break-word;
}
.spot-foot { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.spot-phone {
  font-size: 13.5px; font-weight: 700; color: var(--accent); text-decoration: none;
}
/* Not yet a number. Dashed on purpose: solid it read as something you could
   already dial, and the tap that follows was a surprise. Metrics are
   .spot-sold's, the pill it stands next to. */
.spot-reveal {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 3px 10px; border-radius: 999px;
  border: 1px dashed rgba(255,122,26,.55);
  background: rgba(255,122,26,.10); color: var(--accent);
  font: inherit; font-size: 12px; font-weight: 700; line-height: 1.4;
  cursor: pointer; -webkit-tap-highlight-color: transparent;
  transition: background-color .16s ease, border-color .16s ease,
              transform .12s ease;
}
.spot-reveal:hover { background: rgba(255,122,26,.18); border-style: solid; }
.spot-reveal:active { transform: scale(.96); }
.spot-reveal:disabled { opacity: .6; cursor: default; }
.spot-reveal-num { letter-spacing: .04em; }
.spot-reveal-cta { color: var(--muted); font-weight: 500; font-size: 11.5px; }
/* A closed offer has nobody to ring, so its hint is text rather than a
   button -- there is no reason to bank a reveal on a spot that is gone. */
.spot-phone.is-hint {
  color: var(--muted); letter-spacing: .04em; cursor: default;
}
/* How many people reached for the number. Sits opposite the actions; when
   the poster's own buttons are present they follow it rather than claim the
   gap for themselves. */
.spot-views {
  margin-left: auto; font-size: 11.5px; font-weight: 700; color: var(--muted);
  white-space: nowrap;
}
.spot-views ~ .spot-sold, .spot-views ~ .spot-del { margin-left: 0; }
/* Bends nothing the skeletons rely on, but the press has to be cancelled the
   same way the reaction pills cancel theirs -- and the media query has to
   follow the rules it overrides, having no specificity of its own. */
@media (prefers-reduced-motion: reduce) {
  .spot-reveal { transition: none; }
  .spot-reveal:active { transform: none; }
}
/* Deleting your own offer is the only way one ever leaves the board, so this
   is load-bearing rather than a convenience -- but it still sits quiet until
   hovered, because it only appears on offers this browser posted. */
.spot-del-wrap { margin-left: auto; }

/* Shown only on offers this browser posted. The server checks ownership too
   -- this is the affordance, not the guard. */
.spot-sold {
  background: rgba(34,197,94,.14); border: 1px solid rgba(34,197,94,.4);
  color: var(--ok); cursor: pointer;
  padding: 3px 10px; border-radius: 999px;
  font-size: 11.5px; font-weight: 700; line-height: 1.4;
}
.spot-sold:hover { background: rgba(34,197,94,.24); }
.spot-sold:disabled { opacity: .5; cursor: default; }
.spot-del {
  display: inline-flex; align-items: center;
  background: none; border: none; cursor: pointer; padding: 2px 4px;
  color: var(--muted); line-height: 1;
}
.spot-del:hover { color: var(--out); }
.spot-del svg { display: block; }

/* A closed offer stays readable -- that a spot went in ten minutes at this
   price is worth knowing -- but it must not look like something to ring. */
.spot.sold { opacity: .62; }
.spot.sold .spot-phone { color: var(--muted); text-decoration: line-through; }
.spot-done {
  display: inline-block; padding: 2px 8px; border-radius: 999px;
  background: var(--surface); border: 1px solid var(--line); color: var(--muted);
  font-size: 10px; font-weight: 700; letter-spacing: .4px;
}

/* Same warning the талон form carries, for the same reason: a phone number
   on a public board is the only personal data this site publishes. */
.spot-warn {
  margin: 14px 0 0; padding: 11px 13px; border-radius: 14px;
  background: rgba(245,166,35,.12); border: 1px solid rgba(245,166,35,.35);
  font-size: 11.5px; color: var(--queue); line-height: 1.5;
}

/* Гомдол мэдүүлэх on a report -- алдаршуулах's control, reused down to the
   class name so its geometry stays one definition. The placement differs:
   there it sits in a row of pills, here a feed card is a single click target
   that opens the station, so the flag moves into the corner and out of the
   way of the thumb that is aiming at the card. */
.report-flag { font-size: 13px; }
.shot-card { position: relative; }
.shot-card > .report-flag {
  position: absolute; right: 4px; bottom: 4px;
  padding: 6px 8px; border-radius: 10px;
}
.shot-card > .report-flag:hover { background: var(--surface); }
/* Once it is sent, the button says so rather than inviting a second tap that
   the ledger would ignore anyway. */
.aldar-flag.flagged, .report-flag.flagged { color: var(--queue); opacity: .65; }

/* ------------------------------------------------------- live forecourt cam

   A real ЗХУТ traffic-camera stream in a modal. The stage reserves its own
   height with a padding-top ratio box rather than aspect-ratio, which is
   Safari 15+ / Chrome 88+ and so misses this site's floor -- on Safari, the
   one engine that can play the stream at all. Without a reserved box the
   modal opens as a zero-height black bar for the ten to thirty seconds the
   transcoder takes to wake up, which is exactly when there is a message to
   show. 440x360 source, hence 81.8%. */
.livecam-stage {
  position: relative; width: 100%; padding-top: 81.8%;
  background: #000; border-radius: 12px; overflow: hidden;
}
.livecam-stage video {
  position: absolute; top: 0; right: 0; bottom: 0; left: 0;
  width: 100%; height: 100%; display: block;
}
/* Sits over the blanked stage when the twenty seconds are up. Inside the
   ratio box, so it covers the video at every width without a media query --
   and covers the native controls, which are left showing an empty transport
   once `src` is gone. */
.livecam-over {
  position: absolute; top: 0; right: 0; bottom: 0; left: 0; inset: 0;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 4px;
  padding: 16px; text-align: center;
  background: rgba(6,10,20,.82);
}
.livecam-over[hidden] { display: none; }
.livecam-over-head { margin: 0; font-size: 15px; font-weight: 700; color: var(--text); }
.livecam-over-why { margin: 0 0 6px; font-size: 12.5px; color: var(--muted); }
/* .btn-cam carries a margin-top meant for a stack of popup buttons. */
.livecam-over .btn-cam { margin-top: 0; padding: 10px 18px !important; }

.livecam-tabs { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 10px; }
.livecam-note {
  margin: 10px 0 0; font-size: 13px; color: var(--muted); text-align: center;
}
.livecam-src {
  margin: 6px 0 0; font-size: 11.5px; line-height: 1.45;
  color: var(--muted); opacity: .8; text-align: center;
}

/* ---- station status panel -------------------------------------------
   Three lines for somebody reading this at a junction: what it is, how long
   the queue is, and what that rests on. It carried a percentage, a progress
   bar, a sentence about how often such verdicts hold, a section heading and
   a standing caveat -- all true, none of it the language of a taxi driver
   deciding whether to turn left. */
.status-panel {
  border: 1px solid var(--line); border-left: 3px solid var(--unknown);
  border-radius: 12px; padding: 12px 14px; margin-bottom: 12px;
  background: var(--surface-2);
  /* Two columns: the facts, and how sure we are of them. Beside rather than
     among -- down the middle it interrupted the three lines a driver came
     for. */
  display: flex; align-items: flex-start; gap: 12px;
}
.sp-main { flex: 1; min-width: 0; }
.status-panel.ok { border-left-color: var(--ok); }
.status-panel.queue { border-left-color: var(--queue); }
.status-panel.out { border-left-color: var(--out); }
.sp-status { margin: 0; display: flex; align-items: center; gap: 8px; }
.sp-status b { font-size: 22px; line-height: 1.15; }
.status-panel.ok .sp-status b { color: var(--ok); }
.status-panel.queue .sp-status b { color: var(--queue); }
.status-panel.out .sp-status b { color: var(--out); }
/* The number a driver is actually deciding on, so it is the second thing
   read and not buried under a heading. */
.sp-wait { margin: 6px 0 0; font-size: 15px; font-weight: 700; }
.sp-basis { margin: 4px 0 0; font-size: 12.5px; color: var(--muted); }
/* Only drawn when there is something to doubt. Shown every time, it becomes
   the line nobody reads -- which is exactly the line that had to be read. */
.sp-doubt { margin: 6px 0 0; font-size: 12.5px; color: var(--queue); }

/* Баталгаатай эсэх. Narrow on purpose: it is a glance, not a reading. */
.sp-sure {
  flex: 0 0 74px; text-align: right; display: flex;
  flex-direction: column; align-items: flex-end; gap: 2px;
}
.sp-sure-label {
  font-size: 10px; line-height: 1.25; color: var(--muted);
  text-transform: uppercase; letter-spacing: .3px;
}
.sp-sure-pct { font-size: 20px; line-height: 1.1; }
.sp-sure-bar {
  display: block; width: 100%; height: 5px; border-radius: 3px;
  background: var(--line); overflow: hidden;
}
.sp-sure-bar i { display: block; height: 100%; border-radius: 3px; }
.high { color: var(--ok); }
.sp-sure-bar i.high { background: var(--ok); }
.mid { color: var(--queue); }
.sp-sure-bar i.mid { background: var(--queue); }
.low { color: var(--out); }
.sp-sure-bar i.low { background: var(--out); }


/* ---- the app launch poster ------------------------------------------
   A popup is the most interrupting thing this site does. It earns that by
   appearing once per reader and by naming only the two things the app has
   that this page cannot. */
.app-poster { max-width: 380px; text-align: center; }
/* .modal-close is written for a .modal-head, where top:50% is the middle of
   a header bar. This poster has no header, so the same rule parked the × in
   the middle of the card. */
.app-poster .modal-close { top: 12px; transform: none; z-index: 1; }
.app-body { padding: 22px 20px 18px; }
.app-logo { border-radius: 16px; margin: 0 auto 10px; display: block; }
.app-kicker {
  margin: 0; font-size: 11px; letter-spacing: 1.2px; text-transform: uppercase;
  color: var(--accent); font-weight: 700;
}
.app-poster h3 { margin: 4px 0 16px; font-size: 20px; line-height: 1.2; }

/* Prose, under the buttons. These were boxed rows above them, which put the
   two things that are not tappable where the eye lands first and gave them
   the same border and background as the two that are. */
.app-feats {
  list-style: none; margin: 14px 0 0; padding: 0;
  display: grid; gap: 5px; font-size: 12.5px; color: var(--muted);
  line-height: 1.4;
}
.app-feats b { color: var(--text); font-weight: 700; }

.app-stores { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
@media (max-width: 380px) { .app-stores { grid-template-columns: 1fr; } }
.store-btn {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  padding: 10px 12px; border-radius: 12px; text-decoration: none;
  background: var(--surface-2); border: 1px solid var(--line);
  color: var(--text); font-weight: 700;
}
.store-btn:hover { border-color: var(--accent); }
/* The reader's own platform, moved to the front and lit up by
   initAppPoster(). The other stays available -- people share links. */
.store-btn.is-mine { background: var(--accent); border-color: var(--accent);
                     color: #1a0e03; }
/* An <svg> now, not a glyph. flex-shrink:0 because the Mongolian label
   beside it is the part that should give way when the card is narrow. */
.store-ico { width: 19px; height: 19px; flex: 0 0 auto; display: block; }
.store-text { display: flex; flex-direction: column; line-height: 1.15;
              font-size: 13.5px; text-align: left; }
.store-text small { font-size: 10px; font-weight: 400; opacity: .75; }

.app-later {
  margin: 12px auto 0; display: block; background: none; border: 0;
  color: var(--muted); font-size: 13px; padding: 6px 10px; cursor: pointer;
}
.app-later:hover { color: var(--text); text-decoration: underline; }
