:root {
  --fg: #1a1a1a;
  --fg-muted: #555;
  --bg: #fafafa;
  --accent: #2a5d9f;
  --accent-hover: #1f4a82;
  --border: #d8d8d8;
  --error: #b00020;
  --radius: 4px;
  --max-width: 48rem;
}

* {
  box-sizing: border-box;
  /* Kill iOS Safari's translucent tap overlay; it persists on borderless
     buttons like Edit and reads as a stuck dark-blue selection. */
  -webkit-tap-highlight-color: transparent;
}

html, body {
  margin: 0;
  padding: 0;
}

body {
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  font-size: 16px;
  line-height: 1.5;
  color: var(--fg);
  background: var(--bg);
}

main {
  max-width: var(--max-width);
  margin: 0 auto;
  /* Honour iOS safe-areas (notch / home indicator) when added to home screen
     or viewed in a PWA-style frame; falls back to 1rem on classic browsers. */
  padding:
    calc(env(safe-area-inset-top, 0px) + 1rem)
    max(env(safe-area-inset-right, 0px), 1rem)
    calc(env(safe-area-inset-bottom, 0px) + 1.5rem)
    max(env(safe-area-inset-left, 0px), 1rem);
}

header h1 {
  margin: 0;
  font-size: 1.75rem;
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
}

.brand-mark {
  flex-shrink: 0;
  color: var(--accent);
  /* Slightly larger than the h1 text so the windsock reads as a brand mark
     rather than a tiny inline glyph. 1.2em → ~33.6px at the h1's 1.75rem. */
  width: 1.2em;
  height: 1.2em;
}

/* Reference sections (NOAA / KING 5) get a lighter chrome so the primary
   METAR/TAF section reads as the action. */
section:not(:first-of-type) {
  background: #fbfbfd;
}

.tagline {
  margin: 0.25rem 0 0.75rem;
  color: var(--fg-muted);
  font-size: 0.9rem; /* keeps the one-liner inside ~320px screens */
}

section {
  margin: 0.4rem 0;        /* was 0.75rem — tighten the gap between sections */
  padding: 0.5rem 0.75rem; /* keep horizontal padding, halve the vertical breathing room */
  background: #fff;
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

h2 {
  margin: 0 0 0.5rem;
  font-size: 0.95rem;
  color: var(--fg-muted);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-weight: 700;
}

a {
  color: var(--accent);
  text-decoration: none;
}

a:focus {
  color: var(--accent-hover);
  text-decoration: underline;
}
@media (hover: hover) {
  a:hover {
    color: var(--accent-hover);
    text-decoration: underline;
  }
}

/* KING 5 radar/satellite links — 4×4 grid matching king5.com/radar order,
 * collapsing to 2 columns below iPhone width. */
.radar-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 0.4rem 0.6rem;
  font-size: 0.9rem;
}

.radar-grid a {
  text-align: center;
}

@media (max-width: 374px) {
  .radar-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

.forecast-list {
  margin: 0;
  padding: 0;
  list-style: none;
}

.forecast-list li + li {
  margin-top: 0.25rem;
}

form label {
  display: block;
  margin-top: 0.75rem;
  font-size: 0.95rem;
}

form input[type="text"] {
  width: 100%;
  padding: 0.5rem 0.6rem;
  font: inherit;
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.form-controls {
  margin-top: 0.3rem;     /* tight gap to the search/tile box above */
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.3rem;            /* tighter so Hrs + Decode + Tabular + TAF + METAR fit a phone row */
  font-size: 0.9rem;
}

.ctrl-group {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  white-space: nowrap;
  margin: 0;
}

.ctrl-group label {
  display: inline-flex;
  align-items: center;
  gap: 0.2rem;
  margin: 0;
}

.ctrl-group select {
  font: inherit;
  padding: 0.2rem 0.3rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

/* Toggle switch — checkbox visually rendered as a slider. */
.toggle {
  position: relative;
  cursor: pointer;
  user-select: none;
}

.toggle input[type="checkbox"] {
  /* Invisible but clickable + focusable + in the a11y tree. Covers the
   * whole label so a click anywhere on the toggle hits the input. */
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}

.toggle-track {
  position: relative;
  display: inline-block;
  width: 1.85rem;
  height: 1.1rem;
  background: #c8c8c8;
  border-radius: 999px;
  transition: background 0.15s ease;
  flex-shrink: 0;
}

.toggle-thumb {
  position: absolute;
  top: 0.1rem;
  left: 0.1rem;
  width: 0.9rem;
  height: 0.9rem;
  background: #fff;
  border-radius: 50%;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.25);
  transition: transform 0.15s ease;
}

.toggle input[type="checkbox"]:checked ~ .toggle-track {
  background: var(--accent);
}

.toggle input[type="checkbox"]:checked ~ .toggle-track .toggle-thumb {
  transform: translateX(0.75rem);
}

.toggle input[type="checkbox"]:focus-visible ~ .toggle-track {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* Decode + Tabular + TAF stay together — they wrap as one unit instead of
 * splitting across lines at narrow widths (e.g. 344px Z Fold). Wrapped in a
 * soft pill so the three switches read as one "format" panel. */
.format-toggles {
  gap: 0.4rem;
  flex-wrap: nowrap;
  background: #f3f5f9;
  padding: 0.2rem 0.55rem;
  border-radius: 999px;
}

.form-controls .actions {
  margin-left: auto;
  gap: 0.35rem;
}

.form-controls .actions button {
  padding: 0.45rem 1.1rem;
  font-size: 0.9rem;
  font-weight: 600;
  letter-spacing: 0.02em;
}

.form-controls .actions button:disabled {
  background: #c8c8c8;
  border-color: #c8c8c8;
  cursor: not-allowed;
}

@media (max-width: 480px) {
  .form-controls .actions {
    margin-left: 0;
    width: 100%;
    justify-content: flex-start;
  }
}

button {
  padding: 0.5rem 0.9rem;
  font: inherit;
  background: var(--accent);
  color: #fff;
  border: 1px solid var(--accent);
  border-radius: var(--radius);
  cursor: pointer;
}

@media (hover: hover) {
  button:hover {
    background: var(--accent-hover);
    border-color: var(--accent-hover);
  }
}

.error {
  margin-top: 0.75rem;
  color: var(--error);
  font-size: 0.9rem;
}

/* Label row holds "Airports (N/M)" on the left and the Edit icon button on
   the right, ABOVE the tile box. That frees the tile box of any Edit-related
   padding reservation so tiles can fill the whole row width. The padding-
   right matches the tile-box's border + padding-right so Edit's right edge
   visually aligns with the magnifier's right edge inside the box. */
.ids-label-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  /* Cancel the airports-box's right padding (0.4rem) on this row so Edit's
     right edge reaches the box's inner border — the same horizontal stop the
     magnifier hits inside the query-wrap below. */
  margin: 0 -0.4rem 0.2rem 0;
  padding-right: 0;
}

/* Edit toggle is a bare icon — no border, no background, just the glyph.
   Hover affordance is a colour change rather than a box.
   margin-top removed: with no border, the icon's centred position naturally
   sits at the visual midline of "Airports" rather than 4 px below it. */
.manage-toggle {
  flex-shrink: 0;
  background: transparent;
  color: var(--fg-muted);
  border: none;
  font: inherit;
  width: 1.9rem;
  height: 1.9rem;
  padding: 0;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

@media (hover: hover) {
  .manage-toggle:hover { color: var(--accent); }
}

.manage-icon {
  display: block;
  flex-shrink: 0;
  width: 18px;
  height: 18px;
}

.icao-count {
  color: var(--fg-muted);
  font-size: 0.85rem;
  font-weight: 400;
  margin-left: 0.25rem;
}

.icao-search-external {
  position: relative;        /* anchor for the in-flight spinner overlay */
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 2rem;
  box-sizing: border-box;
  background: transparent;
  color: var(--fg);
  border: 0;
  border-radius: 0;          /* no own rounding — sits inside the wrap's box */
  outline: 0;
  appearance: none;
  -webkit-appearance: none;
  font: inherit;
  font-size: 0.85rem;
  padding: 0 0.55rem;
  cursor: pointer;
  white-space: nowrap;
}
.icao-search-external-icon { display: block; width: 18px; height: 18px; }

@media (hover: hover) {
  .icao-search-external:hover:not(:disabled) { color: var(--accent); }
}
.icao-search-external:focus,
.icao-search-external:focus-visible { outline: 0; }

/* Focus indicator lives on the wrap so the whole search box highlights as
   one unit, not the magnifier as a sub-box. */
.tile-query-wrap:focus-within {
  outline: 2px solid var(--accent);
  outline-offset: -1px;
}

.icao-search-external:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* In-flight state: hide the magnifier icon, swap in the splat animation —
   a 14-step Unicode glyph cycle modelled on the Claude Code "thinking"
   spinner (the one in your CLI screen-recording).
     *  →  ❋  →  ✹ ✹ ✹  →  ❋  →  ·  →  ✿ ✿ ✿ ✿  →  ❀ ❀  →  ·
   Same glyph "held" across multiple frames mimics the observed sustains
   on the big radial burst and the big flower. 14 spans · 1.4s · ~100ms
   per frame. Coral colour pulled from the recording. */
.icao-search-external.is-loading {
  color: transparent;
  pointer-events: none;       /* swallow repeat clicks during the fetch */
}
.icao-search-external .search-splat { display: none; }
.icao-search-external.is-loading .search-splat {
  display: block;
  position: absolute;
  inset: 0;
}
.icao-search-external.is-loading .search-splat > span {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
  color: var(--accent);      /* qmtweb blue (same palette as the rest of the site) */
  opacity: 0;
  animation: search-splat-step 2.1s steps(1, end) infinite;
}
.icao-search-external .search-splat > span:nth-child(1)  { font-size: 14px; animation-delay: 0.00s; }
.icao-search-external .search-splat > span:nth-child(2)  { font-size: 18px; animation-delay: 0.15s; }
.icao-search-external .search-splat > span:nth-child(3)  { font-size: 22px; animation-delay: 0.30s; }
.icao-search-external .search-splat > span:nth-child(4)  { font-size: 22px; animation-delay: 0.45s; }
.icao-search-external .search-splat > span:nth-child(5)  { font-size: 22px; animation-delay: 0.60s; }
.icao-search-external .search-splat > span:nth-child(6)  { font-size: 18px; animation-delay: 0.75s; }
.icao-search-external .search-splat > span:nth-child(7)  { font-size: 10px; animation-delay: 0.90s; }
.icao-search-external .search-splat > span:nth-child(8)  { font-size: 22px; animation-delay: 1.05s; }
.icao-search-external .search-splat > span:nth-child(9)  { font-size: 22px; animation-delay: 1.20s; }
.icao-search-external .search-splat > span:nth-child(10) { font-size: 22px; animation-delay: 1.35s; }
.icao-search-external .search-splat > span:nth-child(11) { font-size: 22px; animation-delay: 1.50s; }
.icao-search-external .search-splat > span:nth-child(12) { font-size: 18px; animation-delay: 1.65s; }
.icao-search-external .search-splat > span:nth-child(13) { font-size: 18px; animation-delay: 1.80s; }
.icao-search-external .search-splat > span:nth-child(14) { font-size: 10px; animation-delay: 1.95s; }
@keyframes search-splat-step {
  0%, 7%     { opacity: 1; }
  7.2%, 100% { opacity: 0; }
}

/* Clear / cancel × button: lives between the query input and the Online
   magnifier. Visible only when there's text OR a search is running. One
   click aborts an in-flight search and clears the input + results. */
.icao-query-clear {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 2rem;              /* match the row */
  width: 1.5rem;
  box-sizing: border-box;
  background: transparent;
  color: var(--fg-muted);
  border: none;
  padding: 0;
  border-radius: 50%;
  cursor: pointer;
}
@media (hover: hover) {
  .icao-query-clear:hover {
    color: var(--fg);
    background: rgba(0, 0, 0, 0.06);
  }
}
.icao-query-clear[hidden] { display: none; }

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

.icao-search-status {
  margin: 0.3rem 0 0;
  font-size: 0.8rem;
  color: var(--fg-muted);
  min-height: 1rem;
  display: flex;
  align-items: center;
  gap: 0.4em;
}

/* Trailing icon appended to status messages (e.g. "try Online" hint). Picks up
 * the surrounding text colour so it follows the .is-notfound / .is-error
 * states automatically. */
.status-trail-icon {
  flex: 0 0 auto;
  margin-left: -0.15em;
  vertical-align: middle;
  color: var(--accent);
}

/* When the status has no message, collapse it so the tile-box doesn't
   reserve a phantom row below the query line. The status only takes up
   space when it actually has something to say (loading / not-found /
   error / interpretation), which is when the layout shift is welcome. */
.icao-search-status:empty {
  display: none;
  margin: 0;
  min-height: 0;
}

/* Status sits as a direct child of .tile-control; the column flex layout
   already gives it a full row, no extra rule needed. */

/* Loading text colour (the spinner itself lives on the Online button — see
   .icao-search-external.is-loading below — which is where the user just
   clicked, so the in-flight cue is right next to the action). */
.icao-search-status.is-loading {
  color: var(--accent);
  font-weight: 500;
}

/* Distinct indicators for "no result" vs. upstream failure. Both clear as
   soon as the user edits the query (input handler resets state). */
.icao-search-status.is-notfound {
  color: var(--accent, #b85);
}
.icao-search-status.is-notfound::before {
  content: "⊘";
  font-weight: 600;
}
.icao-search-status.is-error {
  color: var(--error, #c33);
}
.icao-search-status.is-error::before {
  content: "⚠";
  font-weight: 600;
}

@media (prefers-reduced-motion: reduce) {
  .icao-search-external.is-loading .search-splat > span { animation: none; }
  /* show just one shape statically so users still see "in flight" feedback */
  .icao-search-external.is-loading .search-splat > span:nth-child(3) { opacity: 0.7; }
}

.icao-search-results {
  list-style: none;
  margin: 0.25rem 0 0;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: #fff;
  max-height: 18rem;
  overflow-y: auto;
}

.icao-search-results[hidden] { display: none; }

.icao-search-results li { margin: 0; }

.icao-result {
  display: grid;
  grid-template-columns: 5.5rem 1fr auto;
  align-items: baseline;
  gap: 0.5rem;
  width: 100%;
  padding: 0.4rem 0.6rem;
  background: transparent;
  border: none;
  text-align: left;
  font: inherit;
  cursor: pointer;
  border-bottom: 1px solid #f0f0f0;
  color: var(--fg);
}
/* Same iOS long-press suppression as .tile (see the tile rules). Our touch
 * handler uses long-press for the truncation balloon; without these the
 * browser shows its text-selection magnifier + leaves a word selected on
 * release. Applied to the button AND descendants because iOS sometimes shows
 * the magnifier on the inner <span> even when the button is non-selectable. */
.icao-result,
.icao-result * {
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
}

.icao-search-results li:last-child .icao-result {
  border-bottom: none;
}

.icao-result:focus-visible {
  background: #f4f7fb;
}
@media (hover: hover) {
  .icao-result:hover:not(:disabled) {
    background: #f4f7fb;
  }
}

.icao-result:disabled {
  color: var(--fg-muted);
  cursor: not-allowed;
}

/* Active-state row — toggled-on item the user can click again to toggle off.
 * Visual cue is in the hint text (.icao-result-hint becomes accent-coloured
 * and bold below). The row body keeps full --fg contrast so the hover state
 * stays readable. Previously the "active" cue was `disabled` which painted
 * the row with --fg-muted; combined with the hover/focus light-blue
 * background that produced a low-contrast "washed out" look on rollover. */
.icao-result.is-active .icao-result-hint {
  color: var(--accent);
  font-weight: 600;
}

.icao-result-code em {
  font-style: normal;
  color: var(--fg-muted);
  font-size: 0.85em;
  margin-left: 0.3rem;
}

.icao-result-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 0.9rem;
}

.icao-result-hint {
  font-size: 0.75rem;
  color: var(--fg-muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

/* Online (nearest-METAR) result rows get an accent badge to distinguish them
 * from the bundled-local matches. */
.icao-result-hint.icao-result-online { color: var(--accent); }

/* Truncation balloon for result rows.
 *
 * Lives at document.body (singleton), positioned via JS each show. `position:
 * fixed` so it escapes the dropdown's `overflow-y: auto` clip — an inside-the-
 * scrollbox tooltip would get cropped. Only shown when JS flags a row as
 * `.has-overflow` (its name span actually overflows); non-truncated rows
 * hover silently. */
.result-tooltip {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 100;
  max-width: min(28rem, 90vw);
  padding: 0.35rem 0.6rem;
  background: rgba(28, 32, 40, 0.95);
  color: #fff;
  font-size: 0.82rem;
  line-height: 1.35;
  border-radius: var(--radius);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.18);
  pointer-events: none;
  opacity: 0;
  transform: translateY(2px);
  transition: opacity 0.12s ease, transform 0.12s ease;
  white-space: normal;
}
.result-tooltip.is-visible {
  opacity: 1;
  transform: translateY(0);
}

/* Section divider in the Online results list when an ambiguous query (e.g.
   "Springfield", "King County") resolves to multiple locations — one header
   per group, hidden when only a single group is present. */
.icao-result-group-header {
  list-style: none;
  margin: 0;
  padding: 0.45rem 0.6rem 0.25rem;
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--fg-muted);
  background: #f5f7fa;
  border-bottom: 1px solid var(--border);
}
.icao-result-group-header + .icao-result-group-header { display: none; }
.icao-search-results > .icao-result-group-header:not(:first-child) {
  border-top: 1px solid var(--border);
  margin-top: 0.25rem;
}

/* Group header / status line is a link to openstreetmap.org centred on the
 * resolved location. Subtle styling — inherits the surrounding text colour
 * so it doesn't read as a "blue link"; underline only on real hover so
 * touch/focus doesn't flicker decoration. Mirrors the version-tag link
 * pattern at the bottom of the page. */
.icao-group-link {
  color: inherit;
  text-decoration: none;
  cursor: pointer;
}
.icao-group-link:focus-visible {
  text-decoration: underline;
  outline: none;
}
@media (hover: hover) {
  .icao-group-link:hover {
    text-decoration: underline;
  }
}

.icao-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  padding: 0.25rem 0.75rem 0.5rem;
  border-top: 1px solid var(--border);
  cursor: default;   /* explicit so the gaps don't flash to auto */
}

.icao-actions button {
  background: #fff;
  color: var(--fg);
  border-color: var(--border);
  font-size: 0.85rem;
  padding: 0.3rem 0.6rem;
}

@media (hover: hover) {
  .icao-actions button:hover {
    background: #f0f0f0;
    border-color: var(--border);
  }
}

.icao-hint {
  margin: 0;
  padding: 0 0.75rem 0.6rem;
  font-size: 0.8rem;
  color: var(--fg-muted);
}

footer {
  margin-top: 1rem;
  padding-top: 0.75rem;
  border-top: 1px solid var(--border);
  color: var(--fg-muted);
  /* Smaller than body text — credits are reference info, not primary content.
   * Provider list (Gemini · OpenRouter · Cerebras · Groq) takes a fair bit
   * of horizontal space; smaller helps it wrap less aggressively on mobile. */
  font-size: 0.75rem;
}
footer p { margin: 0; }

/* Keep a credit + its parenthetical aside on the same line — wraps as one
 * unit to the next row instead of breaking between the link and the "(...)"
 * clarification (e.g. "Google Gemini (Online ↗ natural-language)."). */
.nowrap-chunk {
  white-space: nowrap;
}

/* Subtle "Tier-2 fell back" cue on the Gemini credit. Italicises the whole
 * chunk and adds a dotted underline to the link — quiet enough that someone
 * not looking won't notice, but immediately recognisable to anyone curious
 * about why Online ↗ stopped returning multi-group results. Set by JS on a
 * fallback response from resolve.php; cleared on the next successful Tier-2
 * call. Hover the link for a plain-English explanation (set via title). */
/* Separator between adjacent provider links. Standalone <span> rather than
 * a ::before pseudo-element of the next link — the pseudo approach made the
 * link's hover state + underline extend into the gap (the pseudo is part of
 * the link's box, so the link's :hover decoration reached through the
 * separator). The sibling span is outside any link, has pointer-events:none
 * so it never triggers a link's hover, and is hidden from assistive tech
 * since the separator is purely visual. */
.tier2-list .tier2-sep {
  color: var(--fg-muted);
  pointer-events: none;
}

/* Per-provider fallback styling. When the chain rolls forward, JS adds
 * .is-fallback to ONLY the specific <a data-provider="..."> link that was
 * throttled / errored — the other provider names render normally. Italic +
 * dotted underline are subtle enough not to scream "broken" but clear once
 * the user looks. */
.tier2-attribution a.is-fallback {
  font-style: italic;
  text-decoration-style: dotted;
}
/* Small "busy ~7s" chip appended after the throttled provider's link when
 * Tier-2 is in fallback. Quieter than the credit itself so it reads as
 * ancillary info — the kind of thing a user notices on a second look.
 * Stays roman for legibility against the italicised provider name. */
.tier2-busy {
  font-style: normal;
  font-size: 0.95em; /* relative to footer's already-small 0.75rem */
  color: var(--fg-muted);
  opacity: 0.85;
  white-space: nowrap;
}

@media (max-width: 480px) {
  main {
    /* Tighter horizontal padding + a bit more bottom room than the base rule,
       but keep the safe-area-inset envelope so notched phones don't lose
       their inset protection. Mirrors the base main padding shape. */
    padding:
      calc(env(safe-area-inset-top, 0px) + 1rem)
      max(env(safe-area-inset-right, 0px), 0.75rem)
      calc(env(safe-area-inset-bottom, 0px) + 2rem)
      max(env(safe-area-inset-left, 0px), 0.75rem);
  }
}

/* Whole-site version stamp — pinned to the bottom-left of the viewport in
 * very small text. Populated by js/version.js, which also toggles .is-hidden:
 * applied while the page is scrolled away from the bottom (where the fixed tag
 * would float over content), removed when scrolled to the bottom of a
 * scrollable page (or when the page is too short to scroll) so the stamp shows
 * at its home by the footer. pointer-events:none keeps it from intercepting
 * clicks. */
.app-version {
  position: fixed;
  left: 0.5rem;
  bottom: 0.25rem;
  z-index: 10;
  padding: 1px 4px;
  font-size: 10px;
  line-height: 1.4;
  color: var(--fg-muted);
  background: rgba(250, 250, 250, 0.85);
  border-radius: var(--radius);
  letter-spacing: 0.02em;
  pointer-events: none;
  user-select: none;
  transition: opacity 0.15s ease;
}

.app-version.is-hidden {
  opacity: 0;
}

/* The version tag itself is pointer-events:none so it never intercepts clicks
 * on page content behind it. Re-enable hits inside the link so it remains
 * clickable while preserving the inert wrapper. Muted look matches the tag;
 * underline only on real hover so touch / focus doesn't flicker decoration. */
.app-version-link {
  color: inherit;
  text-decoration: none;
  pointer-events: auto;
}
.app-version-link:focus-visible {
  text-decoration: underline;
  outline: none;
}
@media (hover: hover) {
  .app-version-link:hover {
    text-decoration: underline;
  }
}

/* --- Tokenized tile control (prototype) --- */
/* One full-width box: chips flow and wrap, then the query + manage toggle ride
 * on the input's line so there's no dead column to the right of wrapped chips. */
/* Tile-control is the outer container holding two visible boxes (the airports
   box and the query-wrap box) and the search dropdown / status / actions /
   hint. Stacked vertically with a tight gap. In expanded mode the ordering
   shifts so the query line rises to the top. */
.tile-control {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}

/* Airports box: contains the "Airports (N/M)" label + Edit + the tile chips.
   This is the box you see around the airports area at the top. */
.airports-box {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: #fff;
  padding: 0.2rem 0.4rem 0.3rem;   /* tighter top — the label-row doesn't need the breathing room */
}

/* The form-wide rule (form label { margin-top: 0.75rem }) was pushing this
   label ~12px down inside the airports-box; reset it so the label-row sits
   right at the top edge. */
#ids-label {
  margin-top: 0;
  display: inline-flex;
  align-items: baseline;
  gap: 0.25rem;
}

.tile-control.is-open .airports-box {
  background: #fcfcfc;
}

/* Tiles container: now an explicit flex-wrap row (was display:contents when
   it dissolved into the old .tile-box). */
.tiles {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem;
  margin: 0;
  padding: 0;
  list-style: none;
  min-height: 2rem;     /* keep the box from collapsing when empty mid-edit */
}

/* Query + Online live together so they can sit inline (collapsed) or as the
 * top row (expanded). */
/* Flows inline after the last tile, filling the remaining width. flex-basis 0
 * means it doesn't demand space, but its automatic min size (min-content =
 * one-tile input + the nowrap toggle) is what flex-wrap uses to break it to a
 * new line — only when it truly can't fit, never overflowing. NB: do NOT set an
 * explicit min-width here; that overrides the min-content floor and reintroduces
 * the overflow. */
.tile-query-wrap {
  /* The single box: input + clear (×) + magnifier all share this border.
     The individual buttons inside are borderless so the visual unit reads
     as one search field with its action icons. */
  display: flex;
  align-items: center;
  gap: 0.3rem;
  flex: 0 0 100%;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: #fff;
}

/* (Reclaim hack removed — Edit is no longer inside the tile-box, so the
   tile-box has symmetric padding and the query row naturally spans full
   width without negative margin trickery.) */

/* Autocomplete results sit on their own full-width line directly under the
 * query — order -1 puts them above the tiles when expanded; collapsed they
 * naturally fall below the chips/query. */
/* Results dropdown takes its full row naturally as a direct child of
   .tile-control (which is flex-column). */

/* Expanded-mode reorder so the search context sits together at the top of the
   box — query line → status → results → tile list. Otherwise a long tile list
   pushes the search results (and the "couldn't work out a location" message)
   far below the box the user just clicked in. */
.tile-control.is-open .tile-query-wrap {
  order: -3;
  flex: 0 0 auto;     /* natural height in the column layout; full width via parent's align-items: stretch */
}
.tile-control.is-open .icao-search-status { order: -2; }
.tile-control.is-open .icao-search-results { order: -1; }

.tile-query {
  flex: 1;
  min-width: 3.5rem;   /* can shrink to ~one tile before the wrap kicks in */
  height: 2rem;        /* match the Edit / Online / Clear buttons exactly */
  box-sizing: border-box;
  border: 0;
  outline: 0;
  box-shadow: none;    /* kill any browser-default subtle right-edge artifact */
  -webkit-appearance: none;
  appearance: none;
  font: inherit;
  font-size: 16px;    /* keeps iOS from zooming on focus */
  padding: 0 0.4rem;
  background: transparent;
}

.tile-control.is-open .tile-query {
  /* No own border in expanded mode either — the .tile-query-wrap now carries
     the single visible box around input + clear + magnifier. */
  padding: 0.4rem 0.5rem;
}

/* --- Tile: chip (collapsed) --- */
.tile {
  display: inline-flex;
  align-items: center;
  margin: 0;
}

/* Suppress iOS Safari's text-selection magnifier (the word-select on
 * release) and the callout menu (Copy / Look Up) during long-press.
 * Critically: NOT user-drag — iOS uses long-press on a draggable=true
 * element to initiate drag-and-drop reorder, and -webkit-user-drag: none
 * blocks that. We tolerate the iOS drag-preview UI during the hold
 * because it IS the drag-start indicator the user is relying on. */
.tile,
.tile * {
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
}

.tile-check {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  font: inherit;
  font-size: 0.85rem;
  padding: 0.15rem 0.55rem;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: #f3f3f3;
  color: var(--fg-muted);
  cursor: pointer;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.14);
}

.tile.is-active .tile-check {
  background: #eaf1fb;
  border-color: var(--accent);
  color: var(--fg);
}

/* Inactive tiles: same pill shape, lighter/faded shade (seen when expanded). */
.tile:not(.is-active) .tile-check {
  background: #f7f8fa;
  border-color: #e4e4e4;
  color: #9aa0a6;
  box-shadow: none;
}

.tile-code { font-weight: 700; }
.tile-name { color: var(--fg-muted); font-weight: 400; }
.tile-drag { color: var(--fg-muted); cursor: grab; user-select: none; padding: 0 0.15rem; }

.tile-controls { display: inline-flex; gap: 0; flex-shrink: 0; }
.tile-controls button {
  background: transparent;
  border: none;
  color: var(--fg-muted);
  cursor: pointer;
  min-width: 1.6rem;
  min-height: 1.8rem;
  font-size: 0.95rem;
  border-radius: 3px;
}
@media (hover: hover) {
  .tile-controls button:hover:not(:disabled) { background: #fff; color: var(--fg); }
}
.tile-controls button:disabled { opacity: 0.3; cursor: not-allowed; }
.tile-remove { font-size: 1.2rem; font-weight: 700; }
@media (hover: hover) {
  .tile-remove:hover:not(:disabled) { color: var(--error); }
}

/* Collapsed: hide the management chrome (actions/hint); the autocomplete
 * results dropdown may still show as you type. (.icao-actions has its own
 * display rule that outranks the [hidden] attribute, so hide it here.) */
.tile-control:not(.is-open) .icao-actions,
.tile-control:not(.is-open) .icao-hint { display: none; }

/* Collapsed: only active tiles, chips only — no name/controls/drag. */
/* Inactive tiles in collapsed mode render as small bullets, preserving their
   position in the list. Clicking a bullet promotes it back to a full pill
   (same toggleSelected path as the pill itself); the bullets are still
   skipped from the form submit because `selected` controls the hidden #ids,
   not the visible chip count.
   The .tile-check button keeps a generous outer hit-box (the whole 0.95rem
   square) so the bullet is grabbable for drag-reorder; the visible bullet
   is drawn by an inner ::before so it can stay tiny without shrinking the
   click/drag target. */
.tile-control:not(.is-open) .tile:not(.is-active) {
  align-self: center;
}
.tile-control:not(.is-open) .tile:not(.is-active) .tile-check {
  position: relative;
  width: 0.95rem;
  height: 0.95rem;
  min-width: 0;
  padding: 0;
  border-radius: 50%;
  background: transparent;
  border: none;
  color: transparent;       /* hides the ICAO text without removing the click target */
  font-size: 0;             /* collapse the text node so it doesn't enlarge the button */
  line-height: 1;
  gap: 0;
  box-shadow: none;
}
.tile-control:not(.is-open) .tile:not(.is-active) .tile-check::before {
  content: "";
  position: absolute;
  inset: 0;
  margin: auto;
  width: 0.6rem;
  height: 0.6rem;
  border-radius: 50%;
  background: transparent;
  border: 1.5px solid #b0b6c0;
  box-sizing: border-box;
}
@media (hover: hover) {
  .tile-control:not(.is-open) .tile:not(.is-active) .tile-check:hover::before {
    border-color: var(--accent);
    background: rgba(42, 93, 159, 0.12);
  }
}

/* Drag-reorder cues in collapsed mode — the ⋮⋮ handle is hidden but the whole
   pill/bullet is draggable via the <li draggable>. cursor: grab on the active
   pill and inactive bullet hints that to a mouse user; cursor: grabbing while
   the tile is the drag source. (No effect on touch.) */
.tile-control:not(.is-open) .tile-check { cursor: grab; }
.tile-control:not(.is-open) .tile.dragging .tile-check { cursor: grabbing; }

.tile-control:not(.is-open) .tile-name,
.tile-control:not(.is-open) .tile-controls,
.tile-control:not(.is-open) .tile-drag { display: none; }

/* --- Tile: row (expanded) --- */
.tile-control.is-open .tile {
  display: flex;
  width: 100%;
  align-items: center;
  gap: 0.4rem;
  padding: 0.15rem 0.25rem;
  border-radius: 3px;
}
@media (hover: hover) {
  .tile-control.is-open .tile:hover { background: #f4f7fb; }
}
/* The pill stays a chip-sized toggle (same look as collapsed); the airport
 * name takes the rest of the row. */
.tile-control.is-open .tile-check { flex: 0 0 auto; }
.tile-control.is-open .tile-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 0.9rem;
}

.tile.dragging { opacity: 0.4; }

/* Touch-based drag (iOS Safari / Brave on iOS). Native HTML5 drop events are
 * unreliable on touch, so we drive the reorder via raw touch events. While
 * a tile is being touch-dragged it follows the finger via inline `transform`
 * and floats above the rest. Other tiles get a short transform-transition so
 * they slide smoothly into the gap that would open if the user released
 * here. The dragged tile is excluded from the transition — its position
 * tracks the finger 1:1, no easing. */
.tile.touch-dragging {
  opacity: 0.92;
  z-index: 50;
  position: relative;
  cursor: grabbing;
  transition: none !important;
}
.tile:not(.touch-dragging) {
  /* Duration matches the inline transition flipTiles() sets during expand/
   * collapse FLIP animations (0.22s) so that if both rules are in effect
   * (e.g. a setOpen fires while a tile has a leftover transform), the
   * timing doesn't visibly mismatch. Inline transitions win on
   * specificity, but matching the duration keeps behaviour consistent
   * if/when the inline rule is cleared between flipTiles' final-state
   * settle and this CSS rule taking over. */
  transition: transform 0.22s ease;
}
/* Drop indicators — expanded mode stacks tiles as rows, so the bar sits on
   the top/bottom edge of the target. Collapsed mode flows chips horizontally,
   so we override to a vertical bar on the left/right edge of the target. */
.tile.drop-before { box-shadow: inset 0 2px 0 0 var(--accent); }
.tile.drop-after { box-shadow: inset 0 -2px 0 0 var(--accent); }
.tile-control:not(.is-open) .tile.drop-before { box-shadow: inset 2px 0 0 0 var(--accent); }
.tile-control:not(.is-open) .tile.drop-after { box-shadow: inset -2px 0 0 0 var(--accent); }
