/* =====================================================================
   Codanya front end
   =====================================================================

   Mobile-first. Every base rule targets the narrowest supported viewport
   (320px — an iPhone SE in portrait) and every media query is min-width,
   so a phone downloads and applies the least CSS and larger screens layer
   on top. The previous stylesheet worked the other way round, which is why
   a 700px tablet ended up in a half-desktop state no breakpoint owned.

   One breakpoint scale, used by every component. CSS cannot interpolate a
   custom property into a media query, so the literals below are the
   contract — grep for them rather than inventing a new width:

     (base)            320-479   phones, portrait
     sm   480px        large phones, phones in landscape
     md   768px        tablets, portrait
     lg  1024px        tablets landscape, laptops   <- maps.js shares this
     xl  1280px        desktops

   Sections, in cascade order:
     1. Tokens          6. Home
     2. Reset & base    7. Results (web / images)
     3. Utilities       8. Content pages (about, legal, lucky, bot)
     4. Components      9. Maps
     5. App shell      10. Motion, print
   ===================================================================== */

/* =====================================================================
   1. Tokens
   ===================================================================== */

:root {
  /* Brand: Scotland's Saltire blue and white. Tonal blues provide depth
     without introducing a competing multicolour identity. */
  --saltire-blue: #0065bd;
  --saltire-blue-light: #1683d1;
  --saltire-blue-dark: #004b87;
  --saltire-white: #ffffff;
  --blue: var(--saltire-blue);
  --red: #b3261e;
  --yellow: #a16207;
  --green: #166534;

  --brand-gradient: linear-gradient(135deg, #1683d1 0%, #0065bd 55%, #004b87 100%);

  /* Neutral ramp. */
  --gray-50: #f8fafc;
  --gray-100: #f1f5f9;
  --gray-200: #e2e8f0;
  --gray-300: #cbd5e1;
  --gray-400: #94a3b8;
  --gray-500: #64748b;
  --gray-600: #475569;
  --gray-700: #334155;
  --gray-800: #1e293b;
  --gray-900: #0f172a;
  --gray-950: #020617;

  /* Semantic surfaces and text, resolved from the ramp above. Every component
     reads these and never the ramp directly, which is what makes the dark
     scheme below a token swap rather than a second copy of the stylesheet —
     and what makes retuning the ramp a one-line change here. */
  --surface: #ffffff;
  --surface-raised: #ffffff;
  --surface-sunken: var(--gray-50);
  --surface-hover: var(--gray-100);
  --border: var(--gray-200);
  --border-strong: var(--gray-300);
  --border-subtle: rgb(0 101 189 / 0.20);

  --text: var(--gray-800);
  --text-strong: var(--gray-900);
  --text-muted: var(--gray-600);
  --text-subtle: var(--gray-500);

  --link: #0065bd;
  --link-visited: #004b87;
  --accent: #0065bd;
  --accent-soft: #eaf4fb;
  --accent-border: #b8d9ed;

  /* The map-category cyan is a 3.68:1 accent — fine for a border or an icon,
     under the 4.5:1 minimum as small text, so labels use this darker step. */
  --green-text: #0e7490;

  --danger: #b3261e;
  --danger-surface: #fef2f2;
  --danger-border: #fecaca;
  --success: #166534;
  --success-surface: #f0fdf4;
  --success-border: #bbf7d0;

  --focus-ring: #0065bd;

  /* Soft Saltire glow: a crisp Scotland blue close to the surface, fading
     through white into a wider blue halo. */
  --search-glow: 0 0 10px rgb(0 101 189 / 0.20),
                 0 0 20px rgb(255 255 255 / 0.42),
                 0 0 30px rgb(0 101 189 / 0.09);

  /* Fluid type. One clamp per step, so there are no jumps at breakpoints —
     the old scale dropped the wordmark 92px -> 52px at exactly 600px and
     left every width in between unhandled. */
  --text-xs: clamp(0.75rem, 0.74rem + 0.06vw, 0.8125rem);      /* 12 - 13 */
  --text-sm: clamp(0.8125rem, 0.79rem + 0.11vw, 0.875rem);     /* 13 - 14 */
  --text-md: clamp(0.9375rem, 0.92rem + 0.16vw, 1.0625rem);    /* 15 - 17 */
  --text-lg: clamp(1.0625rem, 1.02rem + 0.23vw, 1.1875rem);    /* 17 - 19 */
  --text-xl: clamp(1.1875rem, 1.11rem + 0.38vw, 1.375rem);     /* 19 - 22 */
  --text-2xl: clamp(1.375rem, 1.24rem + 0.68vw, 1.75rem);      /* 22 - 28 */
  --text-3xl: clamp(1.75rem, 1.45rem + 1.5vw, 2.5rem);         /* 28 - 40 */
  /* "Codanya" is a single word, so it cannot wrap and its width sets the
     minimum width of the page. At the old 44px floor the glyphs measured
     295px, which forced the 320px homepage 7px wider than the viewport. The
     ratio below keeps the wordmark at ~80% of the content width at every
     size: 36px at 320px, 43px at 390px, up to the 88px cap. */
  --text-wordmark: clamp(2.25rem, 12vw - 0.25rem, 5.5rem);     /* 36 - 88 */

  /* Any text input below 16px makes iOS Safari zoom the viewport on focus,
     which then has to be pinched back out. Non-negotiable, so it is a token
     rather than a value someone can shave off to fit a tight header. */
  --input-size: 1rem;

  /* Spacing, on a 4px base. */
  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 0.75rem;
  --space-4: 1rem;
  --space-5: 1.25rem;
  --space-6: 1.5rem;
  --space-8: 2rem;
  --space-10: 2.5rem;
  --space-16: 4rem;

  /* Layout. The gutter grows with the viewport so a phone gets 16px of edge
     and a desktop 32px, without a breakpoint to switch between them. */
  --gutter: clamp(1rem, 3.5vw, 2rem);
  --shell-max: 1120px;

  /* Reading measure for prose and result snippets. A line longer than this
     is measurably harder to scan; a grid of thumbnails has no such limit,
     which is why the image grid opts out. */
  --measure: 40rem;      /* ~640px */
  --measure-prose: 44rem;

  /* How wide a results surface (a result card, the local pack, the no-results
     copy, the pagination strip) may run. The reading measure on phones and
     tablets; unbounded on desktop, where the layout is full-bleed. */
  --serp-measure: var(--measure);

  /* The results-page content column — the width of the search box, and so of
     the tab strip and the results beneath it. Under 1024px everything is
     centred on this column, so the wordmark, the box, the tabs and the
     results share one axis. */
  --serp-col: 43.25rem;   /* 692px */

  /* The desktop left rail: the column the wordmark sits in, which the search
     box and the results start after. Zero below 1024px, where the header is
     the centred small-screen layout and there is no rail. Both the header
     grid and the results column read this, so the two cannot drift. */
  --serp-rail: 0px;

  /* Minimum hit area. 44px is Apple's HIG figure and 48dp Material's; 44
     satisfies both once the 8px gap between targets is counted. Applied to
     every tap target rather than to the ones that happened to look small. */
  --tap: 2.75rem;

  --radius-sm: 6px;
  --radius-md: 10px;
  --radius-lg: 14px;
  --radius-xl: 20px;
  --radius-full: 999px;

  --shadow-sm: 0 1px 2px rgb(15 23 42 / 0.06);
  --shadow-md: 0 2px 6px rgb(15 23 42 / 0.08), 0 1px 2px rgb(15 23 42 / 0.04);
  --shadow-lg: 0 10px 24px rgb(15 23 42 / 0.10), 0 2px 6px rgb(15 23 42 / 0.05);
  --shadow-pop: 0 12px 32px rgb(15 23 42 / 0.16);

  --ease: cubic-bezier(0.4, 0, 0.2, 1);
  --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
  --dur-fast: 0.12s;
  --dur: 0.2s;

  /* iOS reports the notch and home-indicator insets through env(); the
     fallback keeps the arithmetic valid on every other browser. */
  --safe-bottom: env(safe-area-inset-bottom, 0px);
  --safe-left: env(safe-area-inset-left, 0px);
  --safe-right: env(safe-area-inset-right, 0px);

  /* Tells the browser to render form controls and scrollbars to match. */
  color-scheme: light;
}

@media (min-width: 1024px) {
    :root {
        --serp-rail: 10.75rem;
        --serp-measure: 60%;
    }
}

/* Dark scheme. Same token names, different values — no component below
   knows which scheme it is in. Surfaces lift as they come forward (the
   dark-UI equivalent of a shadow, which is nearly invisible on dark). */
@media (prefers-color-scheme: dark) {
  :root {
    --surface: var(--gray-900);
    --surface-raised: #1a2438;
    --surface-sunken: #0b1220;
    --surface-hover: #24304a;
    --border: #24304a;
    --border-strong: #33415c;

    --text: var(--gray-200);
    --text-strong: var(--gray-50);
    /* Not --gray-400/-500: those are tuned for dark text on a light surface
       and fall under 4.5:1 the other way round. Lifted until they clear AA
       on --surface-raised, the lightest background either sits on. */
    --text-muted: #a9b6c9;
    --text-subtle: #8b9ab0;

    --link: #7cc4f0;
    --link-visited: #a9dcf7;
    --accent: #7cc4f0;
    --accent-soft: #102f49;
    --accent-border: #1e5f8d;

    --green-text: #67e8f9;

    --danger: #fca5a5;
    --danger-surface: #2a1416;
    --danger-border: #7f1d1d;
    --success: #86efac;
    --success-surface: #06251a;
    --success-border: #14532d;

    --focus-ring: #7cc4f0;

    --shadow-sm: 0 1px 2px rgb(0 0 0 / 0.4);
    --shadow-md: 0 2px 6px rgb(0 0 0 / 0.45);
    --shadow-lg: 0 10px 24px rgb(0 0 0 / 0.5);
    --shadow-pop: 0 12px 32px rgb(0 0 0 / 0.6);

    color-scheme: dark;
  }
}

/* =====================================================================
   2. Reset & base
   ===================================================================== */

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  /* Anchored headings on the legal pages would otherwise land under the
     sticky header. */
  scroll-padding-top: 5rem;
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

body {
  margin: 0;
  font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
    "Helvetica Neue", Arial, sans-serif;
  font-size: var(--text-md);
  line-height: 1.55;
  color: var(--text);
  background: var(--surface);
  -webkit-font-smoothing: antialiased;
  /* A long unbroken URL in a result citation is the one thing on this site
     that can force a horizontal scrollbar on a 320px screen. */
  overflow-wrap: break-word;
}

h1, h2, h3, h4 {
  color: var(--text-strong);
  line-height: 1.25;
  text-wrap: balance;      /* ignored where unsupported */
  margin: 0;
}

p { margin: 0; }

a {
  color: var(--link);
  text-decoration: none;
}

/* Hover styling only where a real pointer exists. Without this guard a
   phone leaves the last-tapped element stuck in its hover state until the
   next tap lands somewhere else. */
@media (hover: hover) {
  a:hover { text-decoration: underline; }
}

img, svg, video {
  max-width: 100%;
  height: auto;
}

button,
input,
select,
textarea {
  font: inherit;
  color: inherit;
}

button { cursor: pointer; }

/* One visible focus style for the whole site, on :focus-visible so it
   appears for keyboard and switch-control users without ringing every
   mouse click. */
:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

::selection {
  background: var(--accent-soft);
  color: var(--text-strong);
}

mark {
  background: transparent;
  color: inherit;
  font-weight: 700;
}

code {
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
  font-size: 0.9em;
  background: var(--surface-hover);
  border-radius: var(--radius-sm);
  padding: 0.1em 0.35em;
  /* A user-agent string or a long meta tag must not widen the page. */
  overflow-wrap: anywhere;
}

/* =====================================================================
   3. Utilities
   ===================================================================== */

/* The one container. Width, centring and gutters — including the notch
   inset on a landscape iPhone, which is the case a plain padding misses. */
.shell {
  width: 100%;
  max-width: var(--shell-max);
  margin-inline: auto;
  padding-inline: calc(var(--gutter) + var(--safe-left))
                  calc(var(--gutter) + var(--safe-right));
}

/* The results pages' container. Under 1024px it behaves exactly like .shell
   and everything inside it is centred on one axis. From 1024px up the cap and
   the auto margins are dropped (see below) so the page runs the full width of
   the viewport instead of sitting in a centred 1120px island with the rest of
   a wide monitor left empty. */
.serp-shell {
  width: 100%;
  max-width: var(--shell-max);
  margin-inline: auto;
  padding-inline: calc(var(--gutter) + var(--safe-left))
                  calc(var(--gutter) + var(--safe-right));
}

/* Full-bleed from 1024px up. Phones and tablets are untouched — the whole
   change lives behind this one query. */
@media (min-width: 1024px) {
  .serp-shell {
    max-width: none;
    margin-inline: 0;
  }
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* Horizontal scroller for tab strips: momentum on iOS, snap so a tab never
   comes to rest half cut off, and no visible scrollbar eating 15px of a
   phone's width. */
.scroll-x {
  overflow-x: auto;
  overflow-y: hidden;
  -webkit-overflow-scrolling: touch;
  scroll-snap-type: x proximity;
  scrollbar-width: none;
  overscroll-behavior-x: contain;
}

.scroll-x::-webkit-scrollbar { display: none; }

/* =====================================================================
   4. Components
   ===================================================================== */

/* --- buttons --------------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: var(--tap);
  padding: 0.625rem 1.25rem;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-full);
  background: var(--surface-raised);
  color: var(--text-strong);
  font-size: var(--text-sm);
  font-weight: 600;
  line-height: 1.2;
  text-align: center;
  text-decoration: none;
  transition: background var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease),
              box-shadow var(--dur) var(--ease);
}

@media (hover: hover) {
  .btn:hover {
    background: var(--surface-hover);
    border-color: var(--border-strong);
    box-shadow: var(--shadow-sm);
    text-decoration: none;
  }
}

.btn:active { transform: translateY(1px); }

.btn-primary {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
}

@media (hover: hover) {
  .btn-primary:hover {
    background: var(--accent);
    filter: brightness(1.12);
    box-shadow: var(--shadow-md);
    color: #fff;
  }
}

@media (prefers-color-scheme: dark) {
  .btn-primary { color: var(--gray-950); }
  .btn-primary:hover { color: var(--gray-950); }
}

/* --- notices --------------------------------------------------------- */

.message {
  padding: var(--space-3) var(--space-4);
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  margin-bottom: var(--space-5);
  font-size: var(--text-sm);
  line-height: 1.5;
}

.message.info { background: var(--accent-soft); color: var(--accent); border-color: var(--accent-border); }
.message.success { background: var(--success-surface); color: var(--success); border-color: var(--success-border); }
.message.error { background: var(--danger-surface); color: var(--danger); border-color: var(--danger-border); }
.message p { margin: var(--space-2) 0 0; }

/* --- pills ----------------------------------------------------------- */

.pill {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 0.15rem 0.5rem;
  border-radius: var(--radius-full);
  background: var(--surface-hover);
  color: var(--text-muted);
  font-size: var(--text-xs);
  font-weight: 600;
  vertical-align: middle;
}

.pill-ok { background: var(--success-surface); color: var(--success); }

/* --- search input (home and results share the internals) ------------- */

.search-icon {
  flex: none;
  width: 1.25rem;
  height: 1.25rem;
}

/* Circular icon buttons inside a search box: mic, clear, submit. All are
   --tap square, which is what sets the height of the box around them. */
.mic-btn,
.serp-clear,
.serp-submit {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--tap);
  height: var(--tap);
  padding: 0;
  border: none;
  border-radius: 50%;
  background: transparent;
  color: var(--text-subtle);
  position: relative;
  transition: background var(--dur-fast) var(--ease);
}

.mic-btn svg,
.serp-clear svg,
.serp-submit svg { display: block; }

.serp-clear[hidden] { display: none; }

@media (hover: hover) {
  .mic-btn:hover,
  .serp-clear:hover,
  .serp-submit:hover {
    background: var(--surface-hover);
    color: var(--text-strong);
  }
}

.mic-btn.listening {
  background: var(--danger-surface);
  animation: mic-pulse 1.4s ease-out infinite;
}

@keyframes mic-pulse {
  0% { box-shadow: 0 0 0 0 rgb(219 39 119 / 0.4); }
  70% { box-shadow: 0 0 0 0.75rem rgb(219 39 119 / 0); }
  100% { box-shadow: 0 0 0 0 rgb(219 39 119 / 0); }
}

/* Why a voice search produced nothing — mic blocked, silent input device,
   offline. Anchored under the box like the suggestion list. */
.mic-hint {
  display: none;
  position: absolute;
  left: 0;
  right: 0;
  margin-top: var(--space-2);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--danger-border);
  border-radius: var(--radius-md);
  background: var(--surface-raised);
  box-shadow: var(--shadow-lg);
  color: var(--danger);
  font-size: var(--text-sm);
  line-height: 1.45;
  z-index: 30;
}

.mic-hint.visible { display: block; }

/* --- suggestions ----------------------------------------------------- */

.suggestions {
  list-style: none;
  position: absolute;
  left: 0;
  right: 0;
  margin: var(--space-2) 0 0;
  padding: var(--space-1);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface-raised);
  box-shadow: var(--shadow-lg);
  z-index: 25;
  /* A long suggestion list on a short landscape phone would otherwise run
     off the bottom of the screen with no way to reach the last item. */
  max-height: min(60vh, 24rem);
  overflow-y: auto;
  overscroll-behavior: contain;
}

.suggestions li {
  display: flex;
  align-items: center;
  min-height: var(--tap);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-md);
  font-size: var(--text-md);
  cursor: pointer;
}

.suggestions li.active { background: var(--surface-hover); }

@media (hover: hover) {
  .suggestions li:hover { background: var(--surface-hover); }
}

/* =====================================================================
   5. App shell — the header and footer every page shares
   ===================================================================== */

/* The wordmark, at three sizes. One rule set, so the letter-spacing and the
   gradient cannot drift between the homepage hero, the results header and
   the content pages. */
.logo,
.serp-logo,
.logo-small {
  font-family: "Product Sans", system-ui, Arial, sans-serif;
  font-weight: 500;
  line-height: 1;
  letter-spacing: -0.045em;
  text-decoration: none;
  user-select: none;
  display: inline-block;
}

.logo { font-size: var(--text-wordmark); margin: 0; }

/* The header wordmarks are links home, so they get a full tap target rather
   than the 24px the glyphs happen to occupy. A step smaller on the narrowest
   phones, which is what lets the wordmark and the whole nav share one row
   from 360px up. */
.serp-logo,
.logo-small {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap);
  font-size: 1.375rem;
}

@media (min-width: 480px) {
  .serp-logo,
  .logo-small { font-size: 1.5rem; }
}

/* Each letter carries the gradient itself, so the hover wave moves colour
   with the letter instead of sliding transparent glyphs over a fixed
   parent background. */
.logo .logo-letter,
.serp-logo .logo-letter,
.logo-small .logo-letter {
  display: inline-block;
  background: var(--brand-gradient);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  transition: transform var(--dur) var(--ease);
}

/* Saltire blue and white. The fine blue stroke keeps the white letters
   recognisable against the page while preserving the flag's two colours. */
.logo .saltire-blue,
.serp-logo .saltire-blue,
.logo-small .saltire-blue {
  background: none;
  color: var(--saltire-blue);
  -webkit-text-fill-color: var(--saltire-blue);
}

.logo .saltire-white,
.serp-logo .saltire-white,
.logo-small .saltire-white {
  background: none;
  color: #fff;
  -webkit-text-fill-color: #fff;
  -webkit-text-stroke: 0.035em var(--saltire-blue);
  paint-order: stroke fill;
}

/* app.js writes --tilt-x/--tilt-y from the cursor position. */
.logo,
.serp-logo {
  transform: perspective(600px) rotateX(var(--tilt-x, 0deg)) rotateY(var(--tilt-y, 0deg));
  transform-style: preserve-3d;
  transition: transform var(--dur-fast) ease-out, filter 0.25s var(--ease);
}

@media (hover: hover) {
  .logo:hover {
    filter: drop-shadow(0 10px 20px rgb(0 101 189 / 0.22))
            drop-shadow(0 0 8px rgb(255 255 255 / 0.85));
  }
  .serp-logo:hover,
  .logo-small:hover { text-decoration: none; }

  .logo:hover .logo-letter,
  .serp-logo:hover .logo-letter,
  .logo-small:hover .logo-letter {
    animation: logo-wave 0.65s var(--ease-spring) both;
    animation-delay: calc(var(--i, 0) * 0.06s);
  }
}

@keyframes logo-wave {
  0%, 100% { transform: translateY(0) scale(1); }
  40% { transform: translateY(-0.18em) scale(1.08); }
}

/* Windows High Contrast (and any forced-colors mode) drops background images,
   including the gradient the wordmark is clipped to — leaving transparent
   text fill over nothing, i.e. an invisible logo and no way to tell you are
   on Codanya. Hand the letters back to the system palette. */
@media (forced-colors: active) {
  .logo .logo-letter,
  .serp-logo .logo-letter,
  .logo-small .logo-letter {
    background: none;
    -webkit-text-fill-color: currentColor;
    color: CanvasText;
  }

  .logo, .serp-logo, .logo-small { forced-color-adjust: none; color: CanvasText; }
}

/* --- site header (content pages) ------------------------------------- */
/* The homepage has its own hero chrome and the results pages their own
   search header; this is the third and last one, shared by About, Bot,
   Privacy, Terms and Lucky. Before, each of those five carried a bare
   wordmark and a single "Back to search" link, which is a dead end on a
   phone — there was no way to reach another page without going home first. */

.site-header {
  position: sticky;
  top: 0;
  z-index: 40;
  background: color-mix(in srgb, var(--surface) 88%, transparent);
  border-bottom: 1px solid var(--border);
  /* Chrome on Android and Safari both support this; where they do not, the
     88% surface above still reads as a solid bar. */
  backdrop-filter: saturate(180%) blur(12px);
  -webkit-backdrop-filter: saturate(180%) blur(12px);
}

@supports not (background: color-mix(in srgb, red, blue)) {
  .site-header { background: var(--surface); }
}

.site-header-inner {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  min-height: 3.5rem;
  padding-block: var(--space-2);
}

.site-header .logo-small { flex: none; }

/* The nav scrolls sideways on a narrow phone rather than wrapping to a
   second row that pushes the content down, and rather than hiding behind a
   menu button that would need JavaScript to open. */
.site-nav {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  margin-left: auto;
  min-width: 0;
  font-size: var(--text-sm);
}

.site-nav a {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap);
  /* Tighter than the usual space-3 so the wordmark and the whole nav fit
     side by side on a 390px phone without the row having to scroll. */
  padding-inline: var(--space-2);
  border-radius: var(--radius-full);
  color: var(--text-muted);
  font-weight: 500;
  white-space: nowrap;
  scroll-snap-align: end;
  transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}

@media (min-width: 480px) {
  .site-nav { gap: var(--space-2); }
  .site-nav a { padding-inline: var(--space-3); }
}

@media (hover: hover) {
  .site-nav a:hover {
    background: var(--surface-hover);
    color: var(--text-strong);
    text-decoration: none;
  }
}

.site-nav a[aria-current="page"] {
  background: var(--accent-soft);
  color: var(--accent);
}

/* --- site footer ----------------------------------------------------- */
/* Now on every page. It used to be included by four of the nine templates,
   so About, Bot, Privacy, Terms and Lucky simply ended — with no route to
   the privacy policy from the page that linked to it. */

.site-footer {
  margin-top: auto;
  background: var(--surface-sunken);
  border-top: 1px solid var(--border);
  color: var(--text-subtle);
  font-size: var(--text-sm);
}

.site-footer-inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-3);
  padding-block: var(--space-5);
  /* Clears the iPhone home indicator, which otherwise sits over the last
     row of links. */
  padding-bottom: calc(var(--space-5) + var(--safe-bottom));
  text-align: center;
}

.footer-nav {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--space-1) var(--space-2);
}

.footer-nav a {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap);
  padding-inline: var(--space-3);
  border-radius: var(--radius-full);
  color: var(--text-muted);
  white-space: nowrap;
}

@media (hover: hover) {
  .footer-nav a:hover {
    background: var(--surface-hover);
    color: var(--text-strong);
    text-decoration: none;
  }
}

.footer-meta {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  color: var(--text-subtle);
}

.footer-meta svg { flex: none; opacity: 0.7; }

@media (min-width: 768px) {
  .site-footer-inner {
    flex-direction: row;
    justify-content: space-between;
    text-align: left;
  }

  .footer-nav { justify-content: flex-end; }
}

/* =====================================================================
   6. Home
   ===================================================================== */

/* Three rows: top bar, hero, footer. min-height uses dvh so the hero is
   measured against the viewport the visitor can actually see — with vh
   alone, mobile Safari and Chrome measure against the taller URL-bar-hidden
   viewport, so the footer sat below the fold and the page shifted while
   scrolling. The vh line first is the fallback for browsers without dvh. */
.home-page {
  display: grid;
  grid-template-rows: auto 1fr auto;
  min-height: 100vh;
  min-height: 100dvh;
  overflow-x: hidden;
}

.home-backgrounds {
  position: fixed;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
  background: var(--surface);
}

.home-background-layer,
.home-background-scrim {
  position: absolute;
  inset: 0;
}

.home-background-layer {
  background-image: var(--bg-desktop);
  background-position: var(--focal-x, 50%) var(--focal-y, 50%);
  background-size: cover;
  background-repeat: no-repeat;
  opacity: 0;
  transform: scale(1.015);
  transition: opacity 1.4s ease;
  will-change: opacity;
}

.home-background-layer.is-visible { opacity: 1; }

@media (max-width: 1024px) {
  .home-background-layer { background-image: var(--bg-tablet, var(--bg-desktop)); }
}

@media (max-width: 600px) {
  .home-background-layer { background-image: var(--bg-mobile, var(--bg-tablet, var(--bg-desktop))); }
}

.home-background-scrim {
  background: linear-gradient(180deg, rgb(255 255 255 / 0.42), rgb(255 255 255 / 0.56));
}

.home-page > :not(.home-backgrounds) {
  position: relative;
  z-index: 1;
}

/* Keep flyouts in the header above the later main/footer stacking contexts.
   This selector deliberately matches the specificity of the rule above. */
.home-page > .home-header {
  z-index: 2;
}

@media (prefers-color-scheme: dark) {
  .home-background-scrim {
    background: linear-gradient(180deg, rgb(15 23 42 / 0.42), rgb(15 23 42 / 0.58));
  }
}

@media (prefers-reduced-motion: reduce) {
  .home-background-layer { transition: none; transform: none; }
}

/* A grid's implicit column is an auto track, and an auto track is never
   narrower than its content's min-content width — so one unbreakable word
   inside makes the whole grid, and therefore the page, wider than the
   viewport. min-width: 0 lets the track hold the viewport width instead. */
.home-page > * { min-width: 0; }

.home-header {
  display: flex;
  justify-content: flex-end;
  padding: var(--space-2) 0;
}

.home-nav {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  font-size: var(--text-sm);
}

.home-nav .home-link {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap);
  padding-inline: var(--space-3);
  border-radius: var(--radius-full);
  color: var(--text-muted);
  font-weight: 500;
  white-space: nowrap;
}

@media (hover: hover) {
  .home-nav .home-link:hover {
    background: var(--surface-hover);
    color: var(--text-strong);
    text-decoration: none;
  }
}

/* Hero. Padded from the top rather than centred in the row: a centred grid
   item on a short landscape phone overflows both edges and clips its own
   top, and the wordmark is the first thing lost. */
.home {
  display: flex;
  flex-direction: column;
  align-items: center;
  /* Reads as optically centred on a phone and a desktop both, without the
     clipping risk of actually centring: a centred flex item on a short
     landscape phone overflows in both directions and loses its own top,
     which is where the wordmark is. */
  padding-top: clamp(1.5rem, 13vh, 7rem);
  padding-bottom: var(--space-8);
}

.home .logo {
  margin-bottom: clamp(1.25rem, 4vh, 2.25rem);
  text-align: center;
}

.search-form {
  position: relative;
  width: 100%;
  max-width: 42.5rem;   /* 680px */
}

.search-box {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  padding-inline: var(--space-4) var(--space-2);
  border: 1px solid transparent;
  border-radius: var(--radius-full);
  background: var(--surface-raised);
  box-shadow: var(--search-glow);
  transition: box-shadow var(--dur) var(--ease), border-color var(--dur) var(--ease);
}

/* Two focus states. The homepage input carries autofocus, so :focus-within
   is true the moment the page paints — a 2px accent ring there is the first
   thing a visitor sees, which is too loud for a resting state. Elevation
   plus an accent border is enough to read as "active".
   The full ring is reserved for :focus-visible, i.e. keyboard and
   switch-control users, who do need an unmissable indicator. */
.search-box:focus-within,
.serp-box:focus-within {
  border-color: var(--accent);
  box-shadow: var(--shadow-md), var(--search-glow);
}

/* Keep a hairline border on the homepage so the white search box doesn't
   disappear against a light hero background when the glow is subtle. */
.search-box {
  border-color: var(--border-subtle);
}

/* On the results/mapps pages the pill sits inside an already-elevated
   surface, so the border is dropped entirely and the glow alone defines it. */

/* A soft halo rather than a hard 2px ring. Chrome treats an autofocused
   field as :focus-visible, so on the homepage this state paints on load —
   a solid accent ring there is the loudest thing on an otherwise calm page.
   The halo plus the accent border is still an unambiguous indicator. */
.search-box:has(input:focus-visible),
.serp-box:has(input:focus-visible) {
  box-shadow: var(--shadow-md),
              0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent),
              var(--search-glow);
}

/* Browsers without color-mix() get the plain ring, which is correct if
   louder — a focus indicator must never be the thing that degrades away. */
@supports not (color: color-mix(in srgb, red, blue)) {
  .search-box:has(input:focus-visible),
  .serp-box:has(input:focus-visible) {
    box-shadow: var(--shadow-md), 0 0 0 2px var(--accent), var(--search-glow);
  }
}

@media (hover: hover) {
  .search-box:hover { box-shadow: var(--shadow-md), var(--search-glow); }
}

.search-box input {
  flex: 1;
  min-width: 0;
  /* Height comes from the --tap icon buttons beside it, so the box is one
     comfortable target rather than a thin strip. */
  min-height: var(--tap);
  padding: 0 var(--space-2);
  border: none;
  background: transparent;
  font-size: var(--input-size);
  color: var(--text-strong);
}

.search-box input:focus { outline: none; }
.search-box input::placeholder { color: var(--text-subtle); }

.home-meta {
  margin-top: var(--space-6);
  max-width: var(--measure);
  font-size: var(--text-sm);
  color: var(--text-subtle);
  text-align: center;
}

.home-warn { color: var(--danger); }

/* --- apps flyout ----------------------------------------------------- */

.home-apps { position: relative; }

.home-apps-btn,
.home-avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--tap);
  height: var(--tap);
  padding: 0;
  border: none;
  border-radius: 50%;
  background: transparent;
  color: var(--text-subtle);
  transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}

.home-apps-btn svg,
.home-avatar svg { display: block; }

.home-avatar img {
  display: block;
  width: 100%;
  height: 100%;
  border-radius: inherit;
  object-fit: cover;
}

.home-avatar { background: var(--surface-hover); }

@media (hover: hover) {
  .home-apps-btn:hover,
  .home-avatar:hover {
    background: var(--surface-hover);
    color: var(--text-strong);
  }
  .home-avatar:hover { box-shadow: var(--shadow-sm); }
}

.home-apps-btn[aria-expanded="true"] {
  background: var(--surface-hover);
  color: var(--text-strong);
}

/* Width is capped against the viewport, not set to a flat 320px: anchored
   to the right edge of a 320px screen, a fixed 320px panel hangs off the
   left side. */
.home-apps-panel {
  position: absolute;
  top: calc(100% + var(--space-2));
  right: 0;
  width: min(20rem, calc(100vw - 2 * var(--gutter)));
  padding: var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface-raised);
  box-shadow: var(--shadow-pop);
  z-index: 30;
}

.home-apps-panel[hidden] { display: none; }

.home-apps-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-1);
}

.home-app {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-1);
  border-radius: var(--radius-md);
  color: var(--text);
  text-align: center;
  transition: background var(--dur-fast) var(--ease);
}

@media (hover: hover) {
  .home-app:hover { background: var(--surface-hover); text-decoration: none; }
}

.home-app-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.75rem;
  height: 2.75rem;
  border-radius: 50%;
  background: var(--surface-sunken);
}

.home-app-label {
  font-size: var(--text-xs);
  color: var(--text-muted);
  line-height: 1.3;
}

.home-apps-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  padding: 0 var(--space-1) var(--space-3);
  border-bottom: 1px solid var(--border);
  margin-bottom: var(--space-3);
}

.home-apps-title {
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--text-strong);
}

.home-apps-edit,
.home-apps-done,
.home-apps-reset {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 2rem;
  padding-inline: var(--space-3);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--text-muted);
  font-size: var(--text-xs);
  font-weight: 600;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}

@media (hover: hover) {
  .home-apps-edit:hover,
  .home-apps-done:hover,
  .home-apps-reset:hover {
    background: var(--surface-hover);
    color: var(--text-strong);
  }
}

.home-apps-grid.is-editing .home-app {
  cursor: move;
  position: relative;
}

.home-app.dragging {
  opacity: 0.5;
  background: var(--surface-hover);
}

.home-app.drop-above,
.home-app.drop-below {
  position: relative;
}

.home-app.drop-above::before,
.home-app.drop-below::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  height: 2px;
  background: var(--accent);
  border-radius: 2px;
}

.home-app.drop-above::before { top: -3px; }
.home-app.drop-below::after { bottom: -3px; }

.home-app-hidden {
  opacity: 0.45;
}

.home-app-hidden .home-app-icon {
  background: var(--surface-hover);
}

.home-app-hidden::after {
  content: "";
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgb(255 255 255 / 0.35);
  border-radius: var(--radius-md);
  pointer-events: none;
}

@media (prefers-color-scheme: dark) {
  .home-app-hidden::after {
    background: rgb(2 6 23 / 0.35);
  }
}

/* --- account flyout -------------------------------------------------- */

.home-account { position: relative; }

.home-account-panel {
  position: absolute;
  top: calc(100% + var(--space-2));
  right: 0;
  width: min(16rem, calc(100vw - 2 * var(--gutter)));
  padding: var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface-raised);
  box-shadow: var(--shadow-pop);
  z-index: 30;
}

.home-account-panel[hidden] { display: none; }

.home-account-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: 0 var(--space-1) var(--space-3);
  border-bottom: 1px solid var(--border);
  margin-bottom: var(--space-2);
}

.home-account-title {
  min-width: 0;
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--text-strong);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.home-account-signout-form {
  flex: 0 0 auto;
  margin: 0;
}

.home-account-signout {
  padding: var(--space-1) var(--space-2);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--text);
  font: inherit;
  font-size: var(--text-xs);
  font-weight: 600;
  line-height: 1.4;
  cursor: pointer;
  transition:
    background var(--dur-fast) var(--ease),
    border-color var(--dur-fast) var(--ease),
    color var(--dur-fast) var(--ease);
}

@media (hover: hover) {
  .home-account-signout:hover {
    border-color: var(--accent);
    background: var(--accent-soft);
    color: var(--accent);
  }
}

.home-account-signout:focus-visible {
  outline: 3px solid var(--focus-ring);
  outline-offset: 2px;
}

.home-account-links {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.home-account-link {
  display: block;
  padding: var(--space-2) var(--space-1);
  border-radius: var(--radius-md);
  color: var(--text);
  font-size: var(--text-sm);
  transition: background var(--dur-fast) var(--ease);
}

@media (hover: hover) {
  .home-account-link:hover { background: var(--surface-hover); text-decoration: none; }
}

.home-avatar[aria-expanded="true"] {
  background: var(--surface-hover);
  color: var(--text-strong);
}

/* =====================================================================
   7. Results — web and images
   ===================================================================== */

.search-page {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  min-height: 100dvh;
}

/* --- results header -------------------------------------------------- */

.serp-header {
  position: sticky;
  top: 0;
  z-index: 40;
  background: var(--surface);
  border-bottom: 1px solid var(--border);
}

/* --- header layout ---------------------------------------------------- */
/* One grid, two arrangements, one set of markup. Every part is a direct grid
   child so the breakpoint can move it to a different row without the HTML
   changing — which is the whole reason the parts are not wrapped in per-row
   containers.

   SMALL SCREENS (the base, under 1024px) — three rows:

       [ menu ]        [ wordmark ]        [ SafeSearch ]
       [ ----------- search box ----------------------- ]
       [ ------------- tab strip ---------------------- ]

   The 1fr side columns are equal, which is what puts the wordmark on the
   page's true centre line rather than centring it in the space the wider
   SafeSearch pill happens to leave over. */
.serp-header-grid {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  row-gap: var(--space-1);
  padding-block: var(--space-1);
}

.serp-menu-btn { grid-area: 1 / 1; justify-self: start; }
.serp-logo { grid-area: 1 / 2; }
.serp-safesearch { grid-area: 1 / 3; justify-self: end; }
.serp-form { grid-area: 2 / 1 / 3 / 4; }
.serp-tabs { grid-area: 3 / 1 / 4 / 4; }

/* Hamburger placeholder. Inert for now — the slot is reserved for the
   light/dark toggle and other display settings. */
.serp-menu-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--tap);
  height: var(--tap);
  padding: 0;
  border: none;
  border-radius: 50%;
  background: transparent;
  color: var(--text-muted);
  transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}

.serp-menu-btn svg { display: block; }

@media (hover: hover) {
  .serp-menu-btn:hover {
    background: var(--surface-hover);
    color: var(--text-strong);
  }
}

.serp-form {
  position: relative;
  min-width: 0;
  /* width: 100% is load-bearing, and not redundant beside max-width. A text
     input's intrinsic width is ~185px, which makes this form's min-content
     ~350px and its max-content about the same. Left to size itself, a grid
     item with auto side margins shrink-to-fits — so the box froze at 350px on
     a tablet and overflowed its track on a 320px phone. An explicit 100%
     makes the width definite: it fills the column, the cap centres it once
     the column is wider than 692px, and the input shrinks inside it. */
  width: 100%;
  max-width: var(--serp-col);
  margin-inline: auto;
}

@media (min-width: 480px) {
  .serp-header-grid { padding-block: var(--space-2); }
}

/* DESKTOP (1024px and up) — the two-row layout this header has always had,
   restored unchanged: the wordmark in a left rail beside the search box, the
   tab strip left-aligned under the box, SafeSearch at the right end of that
   row. The menu button is a small-screen affordance and is dropped here.

       [ rail ][ --------- search box --------- ]
       [ rail ][ tab strip ][ ...  SafeSearch ] */
@media (min-width: 1024px) {
  .serp-header-grid {
    grid-template-columns: var(--serp-rail) auto 1fr;
    row-gap: 0;
  }

  /* Not `visibility: hidden` or opacity — display: none is what also takes it
     out of the tab order and the accessibility tree, so a keyboard or screen
     reader user on a desktop never lands on a control that is not there. */
  .serp-menu-btn { display: none; }

  .serp-logo {
    grid-area: 1 / 1;
    justify-self: start;
    font-size: 1.625rem;
  }

  /* Left-aligned after the rail and running to the right gutter, so the box
     lines up with the full-width results below it rather than stopping short
     at 692px. */
  .serp-form {
    grid-area: 1 / 2 / 2 / 4;
    max-width: none;
    margin-inline: 0;
  }

  .serp-tabs { grid-area: 2 / 2; }

  .serp-safesearch {
    grid-area: 2 / 3;
    align-self: center;
  }
}

.serp-box {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  padding-inline: var(--space-4) var(--space-1);
  border: 1px solid transparent;
  border-radius: var(--radius-full);
  background: var(--surface-raised);
  box-shadow: var(--shadow-sm), var(--search-glow);
  transition: box-shadow var(--dur) var(--ease), border-color var(--dur) var(--ease);
}

@media (hover: hover) {
  .serp-box:hover { box-shadow: var(--shadow-md), var(--search-glow); }
}

.serp-box input {
  flex: 1;
  min-width: 0;
  min-height: var(--tap);
  padding: 0 var(--space-1) 0 0;
  border: none;
  background: transparent;
  /* 16px, always: this input was 15px under 640px, which made iOS Safari
     zoom in every time the visitor tapped the search box on a results
     page — the single most disruptive thing about the old mobile layout. */
  font-size: var(--input-size);
  color: var(--text-strong);
}

.serp-box input:focus { outline: none; }
.serp-box input::placeholder { color: var(--text-subtle); }

/* --- vertical tabs --------------------------------------------------- */

.serp-tabs {
  display: flex;
  gap: var(--space-1);
  min-width: 0;
  /* Room for the focus ring and the active underline inside the scroller. */
  padding-bottom: 1px;
}

/* Small screens: the strip is centred under the centred wordmark. Auto margins
   on the end tabs, not justify-content: center — a centred flex row that
   overflows its scroll container leaves its first item off the scrollable area
   entirely, unreachable at any scroll position. Auto margins centre while
   there is free space and collapse to zero once there is not, so the strip
   centres when it fits and scrolls from the very first tab when it does not. */
.serp-tabs > :first-child { margin-inline-start: auto; }
.serp-tabs > :last-child { margin-inline-end: auto; }

/* Desktop: left-aligned under the search box, as before. */
@media (min-width: 1024px) {
  .serp-tabs > :first-child { margin-inline-start: 0; }
  .serp-tabs > :last-child { margin-inline-end: 0; }
}

.serp-tab {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: var(--tap);      /* "All" is otherwise a 40px-wide target */
  min-height: var(--tap);
  padding-inline: var(--space-3);
  border-bottom: 3px solid transparent;
  color: var(--text-subtle);
  font-size: var(--text-sm);
  font-weight: 500;
  white-space: nowrap;
  scroll-snap-align: start;
  user-select: none;
}

/* A vertical that does not exist yet renders as an inert span. */
span.serp-tab { opacity: 0.55; cursor: default; }

a.serp-tab { text-decoration: none; }

@media (hover: hover) {
  a.serp-tab:hover { color: var(--text-strong); text-decoration: none; }
}

.serp-tab.active,
a.serp-tab.active {
  color: var(--accent);
  border-bottom-color: var(--accent);
  font-weight: 600;
}

/* A pill rather than a bare text link: it reads as a control, and it is a
   44px target instead of a 12px line of text in the corner. Lives in the app
   bar's right-hand slot. */
.serp-safesearch {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--tap);
  padding-inline: var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-full);
  background: var(--surface-raised);
  color: var(--text-muted);
  font-size: var(--text-xs);
  font-weight: 600;
  white-space: nowrap;
}

.serp-safesearch svg { flex: none; }
.serp-safesearch strong { color: var(--accent); }

@media (hover: hover) {
  .serp-safesearch:hover {
    background: var(--surface-hover);
    color: var(--text-strong);
    text-decoration: none;
  }
}

/* The label is the pill's point on a wide screen and noise on a narrow one,
   where the state ("on"/"off") plus the shield carries the meaning. */
.serp-safesearch-label {
  display: none;
}

@media (min-width: 480px) {
  .serp-safesearch-label { display: inline; }
}

/* --- results column -------------------------------------------------- */

/* Small screens: centred on --serp-col, the same column the search box above
   occupies, so a result's left edge sits under the box's left edge.
   Desktop: the rail inset returns, so the column starts under the search box
   beside the wordmark exactly as it did before — same token the header grid
   uses for its first column, so they cannot disagree. */
.serp-main {
  flex: 1;
  max-width: calc(var(--serp-col) + 2 * var(--gutter));
  margin-inline: auto;
  padding-bottom: var(--space-10);
}

/* Desktop: no cap and no auto margins, so the results column starts after the
   rail and runs to the right gutter. --measure (widened at this breakpoint)
   is what governs how far a line of text actually extends inside it. */
@media (min-width: 1024px) {
  .serp-main {
    max-width: none;
    margin-inline: 0;
    padding-left: calc(var(--gutter) + var(--safe-left) + var(--serp-rail));
  }
}

.serp-hint,
.serp-stats {
  padding-top: var(--space-4);
  color: var(--text-subtle);
  font-size: var(--text-sm);
}

.serp-stats {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-bottom: var(--space-6);
}

.serp-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 0.15rem 0.25rem 0.15rem 0.6rem;
  border: 1px solid var(--accent-border);
  border-radius: var(--radius-full);
  background: var(--accent-soft);
  color: var(--accent);
  font-size: var(--text-xs);
  line-height: 1.6;
}

/* --text-muted, not --text-subtle: the subtle tone only reaches 4.34:1 on
   the chip's own tinted background, which is under the 4.5:1 AA minimum for
   text this size. */
.serp-chip-muted {
  background: var(--surface-hover);
  border-color: var(--border);
  color: var(--text-muted);
}

.serp-chip-clear {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.375rem;
  height: 1.375rem;
  border-radius: 50%;
  color: inherit;
  font-size: var(--text-sm);
  line-height: 1;
}

@media (hover: hover) {
  .serp-chip-clear:hover {
    background: color-mix(in srgb, var(--accent) 18%, transparent);
    text-decoration: none;
  }
}

.serp-dym {
  margin-bottom: var(--space-5);
  font-size: var(--text-lg);
  color: var(--text);
}

.serp-dym a {
  color: var(--link);
  font-style: italic;
  font-weight: 700;
}

.serp-noresults {
  max-width: var(--serp-measure);
  font-size: var(--text-md);
  line-height: 1.65;
}

.serp-noresults p { margin: 0 0 var(--space-4); }
.serp-noresults ul { margin: 0; padding-left: 1.5rem; color: var(--text-muted); }
.serp-noresults li { margin-bottom: var(--space-2); font-size: var(--text-sm); }

/* --- local pack ------------------------------------------------------ */

.serp-local-pack {
  max-width: var(--serp-measure);
  margin-bottom: var(--space-8);
  padding: var(--space-4) var(--space-4) var(--space-1);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface-sunken);
}

.serp-local-title {
  margin-bottom: var(--space-4);
  font-size: var(--text-lg);
  font-weight: 600;
}

.serp-local-pack .serp-result { margin-bottom: var(--space-5); }

.serp-local-map-link {
  padding-bottom: var(--space-3);
  font-size: var(--text-sm);
}

/* --- sponsored results ----------------------------------------------- */
/*
 * Paid results sit above the organic list and are built from the same parts, at
 * the same size and legibility. That is deliberate: a paid result disguised as an
 * organic one is deceptive, and one hidden in a box nobody reads is worthless to
 * the advertiser. What resolves the tension is the label — unmissable, unambiguous,
 * and linked to an explanation.
 *
 * The only structural difference is a rule under the block, so the boundary between
 * "what we were paid to show you" and "what the ranker chose" is a line on the page
 * and not a matter of noticing a word.
 */

.serp-ads {
  max-width: var(--serp-measure);
  margin-bottom: var(--space-6);
  padding-bottom: var(--space-4);
  border-bottom: 1px solid var(--border);
}

.serp-ad-label {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  font-size: var(--text-sm);
  font-weight: 700;
  line-height: 1.3;
  color: var(--text);
}

/*
 * The "?" that links to /privacy#advertising. A link and not a tooltip: a tooltip
 * cannot be reached from a keyboard or a phone, and this is the control that makes
 * the label meaningful rather than decorative.
 */
.serp-ad-why {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.05em;
  height: 1.05em;
  border: 1px solid var(--border-strong, var(--border));
  border-radius: 50%;
  font-size: .72em;
  font-weight: 600;
  line-height: 1;
  color: var(--text-muted);
  text-decoration: none;
}
.serp-ad-why:hover,
.serp-ad-why:focus-visible {
  color: var(--text);
  border-color: currentColor;
  text-decoration: none;
}

/* --- one result ------------------------------------------------------ */

.serp-result {
  max-width: var(--serp-measure);
  margin-bottom: var(--space-6);
}

.serp-result-head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.serp-source {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-width: 0;
  min-height: var(--tap);
  flex: 1;
}

@media (hover: hover) {
  .serp-source:hover { text-decoration: none; }
  .serp-source:hover .serp-cite { color: var(--text-muted); }
}

.serp-favicon {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 1.75rem;
  height: 1.75rem;
  border: 1px solid var(--border);
  border-radius: 50%;
  background: var(--surface-raised);
  overflow: hidden;
}

.serp-favicon img {
  display: block;
  width: 1.125rem;
  height: 1.125rem;
}

/* app.js adds .noicon when the site's /favicon.ico fails to load. */
.serp-favicon.noicon {
  background: var(--surface-hover) url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%2394a3b8"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39z"/></svg>') center / 1.125rem no-repeat;
}

.serp-favicon.noicon img { display: none; }

.serp-source-lines {
  display: flex;
  flex-direction: column;
  min-width: 0;
  line-height: 1.35;
}

.serp-site {
  font-size: var(--text-sm);
  color: var(--text-strong);
}

/* One line, ellipsed. A deep URL is otherwise the widest thing on the page
   and would set the width of every result around it. */
.serp-cite {
  font-size: var(--text-xs);
  font-style: normal;
  color: var(--text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.serp-more {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--tap);
  height: var(--tap);
  border-radius: 50%;
  color: var(--text-subtle);
}

.serp-more svg { display: block; }

@media (hover: hover) {
  .serp-more:hover { background: var(--surface-hover); text-decoration: none; }
}

.serp-title {
  margin: var(--space-1) 0 0.15rem;
  font-size: var(--text-xl);
  font-weight: 400;
  line-height: 1.3;
  text-wrap: pretty;
}

/* The title is the target a visitor actually aims for, and a single line of
   it is only ~26px tall. Vertical padding on an inline element grows the hit
   area without changing the line box, so the target reaches 44px and the
   layout does not move. The padding overlaps the source line above, which is
   harmless: both links point at the same result URL. */
.serp-title a {
  color: var(--link);
  padding-block: 0.5625rem;
}

.serp-title a:visited { color: var(--link-visited); }

.serp-title mark { font-weight: inherit; }

.serp-snippet {
  font-size: var(--text-sm);
  line-height: 1.6;
  color: var(--text-muted);
}

/* --- pagination ------------------------------------------------------ */
/* Numbered pages, wrapping and centred, with 44px targets. The strip used
   to be a row of 30px-tall text links that were genuinely hard to hit. */

.serp-pagination {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: var(--space-1);
  max-width: var(--serp-measure);
  margin: var(--space-8) 0 var(--space-4);
  font-size: var(--text-sm);
}

.serp-pagination a,
.serp-page-current,
.serp-page-ellipsis {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: var(--tap);
  min-height: var(--tap);
  padding-inline: var(--space-2);
  border-radius: var(--radius-md);
}

.serp-pagination a { color: var(--link); }

@media (hover: hover) {
  .serp-pagination a:hover { background: var(--surface-hover); text-decoration: none; }
}

.serp-page-current {
  background: var(--accent-soft);
  color: var(--accent);
  font-weight: 700;
}

.serp-page-ellipsis { color: var(--text-subtle); }

/* Prev/Next carry a label, so they need more than a square. */
.serp-page-prev,
.serp-page-next { padding-inline: var(--space-3); font-weight: 600; }

/* On the narrowest phones the numbered pages are dropped and Prev/Next
     carry pagination on their own — twelve number targets do not fit across
     320px without shrinking below the minimum size. */
@media (max-width: 379px) {
  .serp-page-num,
  .serp-page-ellipsis { display: none; }
}

/* --- images grid ----------------------------------------------------- */
/* No measure cap, unlike the text results: a line of prose stops being
   readable past ~40rem, while a grid of thumbnails only improves with more
   columns. auto-fill does the responsive work, from two columns on a phone
   to as many as a wide monitor has room for. */

.image-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
  gap: var(--space-3);
  margin-bottom: var(--space-2);
}

@media (min-width: 768px) {
  .image-grid {
    grid-template-columns: repeat(auto-fill, minmax(11.25rem, 1fr));
    gap: var(--space-4);
  }
}

/* The grid is the one thing on a results page that was never held to the text
   column: more width means more columns, which is strictly better for
   thumbnails. Under 1024px it takes the shell's width; on desktop it runs
   full-bleed like the web results, so the two verticals do not stop at
   different places on the same monitor. */
.images-page .serp-main {
  max-width: var(--shell-max);
  padding-bottom: 0;
}

@media (min-width: 1024px) {
  .images-page .serp-main { max-width: none; }
}

.image-tile {
  margin: 0;
  min-width: 0;   /* let a long tag string shrink instead of stretching the column */
}

.image-tile-link {
  display: block;
  /* A fixed pixel height cropped portrait images hard on a phone. An aspect
     ratio keeps the grid tidy while scaling with the column. */
  aspect-ratio: 4 / 3;
  border-radius: var(--radius-md);
  overflow: hidden;
  background: var(--surface-hover);
  line-height: 0;
  transition: box-shadow var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease);
}

.image-tile img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

@media (hover: hover) {
  .image-tile-link:hover {
    box-shadow: var(--shadow-md);
    transform: translateY(-2px);
  }
}

.image-tile-caption {
  display: flex;
  flex-direction: column;
  gap: 1px;
  padding: var(--space-2) 0.15rem 0;
  font-size: var(--text-xs);
  line-height: 1.4;
}

/* One line each: a tag list can run to a paragraph, and a ragged grid of
   different-height captions reads as broken rather than as data. */
.image-tile-tags,
.image-tile-user {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.image-tile-tags { color: var(--text); }
.image-tile-user { color: var(--text-subtle); }

/* --- infinite scroll ------------------------------------------------- */
/* Reserved height rather than a block that appears once loading starts: a
   footer that grows by 80px the moment the fetch fires shoves the grid
   under the visitor's finger mid-tap. */

.image-more {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-3);
  min-height: 6rem;
  padding: var(--space-6) 0 calc(var(--space-10) + var(--safe-bottom));
  font-size: var(--text-sm);
  color: var(--text-subtle);
}

.image-more-link {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap);
  padding-inline: var(--space-5);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-full);
  color: var(--link);
  font-weight: 600;
}

@media (hover: hover) {
  .image-more-link:hover {
    background: var(--surface-hover);
    box-shadow: var(--shadow-sm);
    text-decoration: none;
  }
}

.image-more-spinner {
  display: none;
  width: 1.625rem;
  height: 1.625rem;
  border: 3px solid var(--border);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: spin 0.7s linear infinite;
}

@keyframes spin { to { transform: rotate(360deg); } }

.image-more-status { min-height: 1em; }

/* app.js sets .is-auto once it has taken over loading, which is what hides
   the manual link. Until then — no JavaScript, or an IntersectionObserver
   the browser does not have — the link is the whole feature. */
.image-more.is-auto .image-more-link { display: none; }
.image-more.is-loading .image-more-spinner { display: block; }
/* An error puts the link back: its href is the plain server-rendered next
   page, the one route that does not depend on whatever just failed. */
.image-more.is-error .image-more-link { display: inline-flex; }
.image-more.is-done .image-more-link { display: none; }

/* --- image download dialog ------------------------------------------- */
/* Built by app.js on first open and reused, appended to <body>. */

.image-lightbox {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
}

.image-lightbox[hidden] { display: none; }

/* Scrolling the grid behind an open dialog moves content the visitor
   cannot see and cannot get back to without closing it. */
body.has-lightbox { overflow: hidden; }

.image-lightbox-backdrop {
  position: absolute;
  inset: 0;
  background: rgb(2 6 23 / 0.7);
}

/* A full-height sheet on a phone, a centred two-column card from 768px up.
   A side-by-side split at 390px leaves neither half usable. */
.image-lightbox-panel {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  width: 100%;
  height: 100%;
  padding: var(--space-4);
  padding-bottom: calc(var(--space-4) + var(--safe-bottom));
  overflow: auto;
  overscroll-behavior: contain;
  background: var(--surface);
  box-shadow: var(--shadow-pop);
}

@media (min-width: 768px) {
  .image-lightbox { padding: var(--space-6); }

  .image-lightbox-panel {
    flex-direction: row;
    gap: var(--space-6);
    width: min(56rem, 100%);
    height: auto;
    max-height: 100%;
    padding: var(--space-6);
    border-radius: var(--radius-lg);
  }
}

.image-lightbox-close {
  position: absolute;
  top: var(--space-2);
  right: var(--space-2);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--tap);
  height: var(--tap);
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: var(--surface);
  color: var(--text-muted);
  font-size: 1.5rem;
  line-height: 1;
  z-index: 2;
}

@media (hover: hover) {
  .image-lightbox-close:hover { background: var(--surface-hover); color: var(--text-strong); }
}

/* The stage holds its size while the larger rendition loads, so swapping
   the grid thumbnail for the full image does not reflow the dialog under
   the visitor's finger — the download button would move mid-tap. */
.image-lightbox-stage {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 0;
  min-height: 10rem;
  background: var(--surface-hover);
  border-radius: var(--radius-md);
  overflow: hidden;
}

.image-lightbox-stage img {
  max-width: 100%;
  max-height: 38vh;
  object-fit: contain;
}

@media (min-width: 768px) {
  .image-lightbox-stage { flex: 1 1 60%; min-height: 15rem; }
  .image-lightbox-stage img { max-height: 60vh; }
}

.image-lightbox-info {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  min-width: 0;
  padding-right: var(--space-8);   /* clear of the close button */
}

@media (min-width: 768px) {
  .image-lightbox-info { flex: 1 1 40%; }
}

.image-lightbox-title {
  font-size: var(--text-lg);
  font-weight: 500;
  line-height: 1.3;
  /* A provider tag list can run to a paragraph; two lines then ellipsis. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.image-lightbox-by { font-size: var(--text-sm); color: var(--text-subtle); }

.image-lightbox-sizes {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin: 0;
  padding: 0;
  border: 0;
}

.image-lightbox-sizes legend {
  padding: 0 0 var(--space-2);
  font-size: var(--text-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-subtle);
}

.image-lightbox-size {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-height: var(--tap);
  padding-inline: var(--space-3);
  border-radius: var(--radius-md);
  font-size: var(--text-sm);
  cursor: pointer;
}

@media (hover: hover) {
  .image-lightbox-size:hover { background: var(--surface-hover); }
}

.image-lightbox-size input { accent-color: var(--accent); }

/* Dimensions right-aligned in their own column: the point of the list is
   comparing them, and a ragged left edge makes that harder than it needs
   to be. */
.image-lightbox-dims {
  margin-left: auto;
  font-size: var(--text-xs);
  color: var(--text-subtle);
  font-variant-numeric: tabular-nums;
}

.image-lightbox-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  margin-top: auto;
  padding-top: var(--space-2);
}

.image-lightbox-download {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 1 1 auto;
  min-height: var(--tap);
  padding-inline: var(--space-6);
  border-radius: var(--radius-full);
  background: var(--accent);
  color: #fff;
  font-size: var(--text-sm);
  font-weight: 600;
}

@media (prefers-color-scheme: dark) {
  .image-lightbox-download { color: var(--gray-950); }
}

@media (hover: hover) {
  .image-lightbox-download:hover { filter: brightness(1.1); text-decoration: none; }
}

.image-lightbox-note { font-size: var(--text-xs); color: var(--text-subtle); }

/* --- video vertical -------------------------------------------------- */
/* Mirrors the image grid, lightbox and infinite-scroll styles above. A video
   tile is 16:9 with a duration badge and a play affordance over the thumbnail;
   the dialog swaps the <img> stage for a <video controls> stage. */

.video-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
  gap: var(--space-3);
  margin-bottom: var(--space-2);
}

@media (min-width: 768px) {
  .video-grid {
    grid-template-columns: repeat(auto-fill, minmax(11.25rem, 1fr));
    gap: var(--space-4);
  }
}

.videos-page .serp-main {
  max-width: var(--shell-max);
  padding-bottom: 0;
}

@media (min-width: 1024px) {
  .videos-page .serp-main { max-width: none; }
}

.video-tile {
  margin: 0;
  min-width: 0;
}

.video-tile-link {
  display: block;
  aspect-ratio: 16 / 9;
  border-radius: var(--radius-md);
  overflow: hidden;
  background: var(--surface-hover);
  line-height: 0;
  transition: box-shadow var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease);
}

.video-tile-thumb {
  position: relative;
  display: block;
  width: 100%;
  height: 100%;
}

.video-tile img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.video-duration {
  position: absolute;
  right: 0.4rem;
  bottom: 0.4rem;
  padding: 0.1rem 0.35rem;
  border-radius: var(--radius-sm);
  background: rgb(2 6 23 / 0.78);
  color: #fff;
  font-size: var(--text-xs);
  font-variant-numeric: tabular-nums;
  line-height: 1.4;
}

.video-play {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  opacity: 0;
  transition: opacity var(--dur-fast) var(--ease);
}

.video-play svg {
  width: 20%;
  height: 20%;
  min-width: 2rem;
  min-height: 2rem;
  filter: drop-shadow(0 1px 4px rgb(2 6 23 / 0.6));
}

@media (hover: hover) {
  .video-tile-link:hover {
    box-shadow: var(--shadow-md);
    transform: translateY(-2px);
  }
  .video-tile-link:hover .video-play { opacity: 0.9; }
}

.video-tile-caption {
  display: flex;
  flex-direction: column;
  gap: 1px;
  padding: var(--space-2) 0.15rem 0;
  font-size: var(--text-xs);
  line-height: 1.4;
}

.video-tile-tags,
.video-tile-user {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.video-tile-tags { color: var(--text); }
.video-tile-user { color: var(--text-subtle); }

/* --- video infinite scroll (mirrors .image-more) --------------------- */

.video-more {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-3);
  min-height: 6rem;
  padding: var(--space-6) 0 calc(var(--space-10) + var(--safe-bottom));
  font-size: var(--text-sm);
  color: var(--text-subtle);
}

.video-more-link {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap);
  padding-inline: var(--space-5);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-full);
  color: var(--link);
  font-weight: 600;
}

@media (hover: hover) {
  .video-more-link:hover {
    background: var(--surface-hover);
    box-shadow: var(--shadow-sm);
    text-decoration: none;
  }
}

.video-more-spinner {
  display: none;
  width: 1.625rem;
  height: 1.625rem;
  border: 3px solid var(--border);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: spin 0.7s linear infinite;
}

.video-more-status { min-height: 1em; }

.video-more.is-auto .video-more-link { display: none; }
.video-more.is-loading .video-more-spinner { display: block; }
.video-more.is-error .video-more-link { display: inline-flex; }
.video-more.is-done .video-more-link { display: none; }

/* --- video preview/download dialog (mirrors .image-lightbox) ---------- */

.video-lightbox {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
}

.video-lightbox[hidden] { display: none; }

.video-lightbox-backdrop {
  position: absolute;
  inset: 0;
  background: rgb(2 6 23 / 0.7);
}

.video-lightbox-panel {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  width: 100%;
  height: 100%;
  padding: var(--space-4);
  padding-bottom: calc(var(--space-4) + var(--safe-bottom));
  overflow: auto;
  overscroll-behavior: contain;
  background: var(--surface);
  box-shadow: var(--shadow-pop);
}

@media (min-width: 768px) {
  .video-lightbox { padding: var(--space-6); }

  .video-lightbox-panel {
    flex-direction: row;
    gap: var(--space-6);
    width: min(56rem, 100%);
    height: auto;
    max-height: 100%;
    padding: var(--space-6);
    border-radius: var(--radius-lg);
  }
}

.video-lightbox-close {
  position: absolute;
  top: var(--space-2);
  right: var(--space-2);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--tap);
  height: var(--tap);
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: var(--surface);
  color: var(--text-muted);
  font-size: 1.5rem;
  line-height: 1;
  z-index: 2;
}

@media (hover: hover) {
  .video-lightbox-close:hover { background: var(--surface-hover); color: var(--text-strong); }
}

.video-lightbox-stage {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 0;
  min-height: 10rem;
  background: var(--surface-hover);
  border-radius: var(--radius-md);
  overflow: hidden;
}

.video-lightbox-stage video {
  max-width: 100%;
  max-height: 38vh;
}

@media (min-width: 768px) {
  .video-lightbox-stage { flex: 1 1 60%; min-height: 15rem; }
  .video-lightbox-stage video { max-height: 60vh; }
}

.video-lightbox-info {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  min-width: 0;
  padding-right: var(--space-8);
}

@media (min-width: 768px) {
  .video-lightbox-info { flex: 1 1 40%; }
}

.video-lightbox-title {
  font-size: var(--text-lg);
  font-weight: 500;
  line-height: 1.3;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.video-lightbox-by { font-size: var(--text-sm); color: var(--text-subtle); }

.video-lightbox-sizes {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin: 0;
  padding: 0;
  border: 0;
}

.video-lightbox-sizes legend {
  padding: 0 0 var(--space-2);
  font-size: var(--text-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-subtle);
}

.video-lightbox-size {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-height: var(--tap);
  padding-inline: var(--space-3);
  border-radius: var(--radius-md);
  font-size: var(--text-sm);
  cursor: pointer;
}

@media (hover: hover) {
  .video-lightbox-size:hover { background: var(--surface-hover); }
}

.video-lightbox-size input { accent-color: var(--accent); }

.video-lightbox-dims {
  margin-left: auto;
  font-size: var(--text-xs);
  color: var(--text-subtle);
  font-variant-numeric: tabular-nums;
}

.video-lightbox-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  margin-top: auto;
  padding-top: var(--space-2);
}

.video-lightbox-download {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 1 1 auto;
  min-height: var(--tap);
  padding-inline: var(--space-6);
  border-radius: var(--radius-full);
  background: var(--accent);
  color: #fff;
  font-size: var(--text-sm);
  font-weight: 600;
}

@media (prefers-color-scheme: dark) {
  .video-lightbox-download { color: var(--gray-950); }
}

@media (hover: hover) {
  .video-lightbox-download:hover { filter: brightness(1.1); text-decoration: none; }
}

.video-lightbox-note { font-size: var(--text-xs); color: var(--text-subtle); }

/* =====================================================================
   8. Content pages — About, Bot, Privacy, Terms, Lucky
   ===================================================================== */

/* All four layouts were separate before, with their own paddings and their
   own idea of how wide a paragraph should be. They now share one page
   frame, one measure and one heading rhythm. */

.about-page,
.lucky-page {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  min-height: 100dvh;
}

.about-main,
.lucky-main {
  flex: 1;
  padding-bottom: var(--space-16);
}

.about-hero,
.lucky-hero {
  max-width: var(--measure-prose);
  padding: var(--space-10) 0 var(--space-8);
  margin-bottom: var(--space-8);
  border-bottom: 1px solid var(--border);
}

.about-kicker {
  margin-bottom: var(--space-3);
  font-size: var(--text-sm);
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--accent);
}

.about-hero h1,
.lucky-hero h1 {
  margin-bottom: var(--space-4);
  font-size: var(--text-3xl);
  font-weight: 600;
  letter-spacing: -0.02em;
}

.lucky-hero h1 strong { color: var(--accent); }

.about-lead,
.lucky-lead {
  max-width: var(--measure-prose);
  font-size: var(--text-lg);
  line-height: 1.6;
  color: var(--text);
}

.about-meta,
.lucky-meta,
.legal-updated,
.about-note,
.lucky-note {
  margin-top: var(--space-4);
  font-size: var(--text-sm);
  color: var(--text-subtle);
  line-height: 1.55;
}

.about-hero .btn,
.lucky-hero .btn,
.about-cta .btn { margin-top: var(--space-5); }

.about-section {
  max-width: var(--measure-prose);
  margin-bottom: var(--space-10);
}

.about-section h2,
.lucky-list h2,
.about-cta h2 {
  margin-bottom: var(--space-4);
  font-size: var(--text-2xl);
  font-weight: 600;
}

.about-section p { margin-bottom: var(--space-3); }
.about-section p:last-child { margin-bottom: 0; }
.about-section strong { color: var(--text-strong); }

/* Mission / vision / promise. One column on a phone — three 200px cards
   side by side on a 390px screen is what auto-fit gave before, and it
   produced three columns of two-word lines. */
.about-cards {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-4);
  margin-bottom: var(--space-10);
}

@media (min-width: 640px) {
  .about-cards { grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr)); }
}

.about-card {
  padding: var(--space-5);
  border: 1px solid var(--border);
  border-top: 3px solid var(--accent);
  border-radius: var(--radius-lg);
  background: var(--surface-raised);
  box-shadow: var(--shadow-sm);
}

.about-card h3 {
  margin-bottom: var(--space-2);
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-subtle);
}

.about-card p { font-size: var(--text-md); }

/* Values and product lists share a look. */
.about-values,
.about-products {
  list-style: none;
  margin: 0;
  padding: 0;
}

.about-values li,
.about-products li {
  padding: var(--space-4) 0;
  border-bottom: 1px solid var(--border);
}

.about-values li:last-child,
.about-products li:last-child { border-bottom: none; }

.about-values h3,
.about-products h3 {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
  margin-bottom: var(--space-2);
  font-size: var(--text-lg);
  font-weight: 600;
}

.about-values p,
.about-products p {
  font-size: var(--text-sm);
  color: var(--text-muted);
  line-height: 1.6;
}

.about-cta {
  max-width: var(--measure-prose);
  padding-top: var(--space-8);
  border-top: 1px solid var(--border);
}

.about-cta p { margin-bottom: var(--space-2); }

/* --- legal ----------------------------------------------------------- */

.legal-list {
  max-width: var(--measure-prose);
  margin: 0 0 var(--space-4);
  padding-left: 1.25rem;
}

.legal-list li {
  margin-bottom: var(--space-3);
  font-size: var(--text-md);
  line-height: 1.65;
}

.legal-list li strong { color: var(--text-strong); }

/* --- lucky ----------------------------------------------------------- */

.lucky-item {
  max-width: var(--measure-prose);
  padding: var(--space-5) 0;
  border-bottom: 1px solid var(--border);
}

.lucky-item h3 {
  margin-bottom: var(--space-2);
  font-size: var(--text-lg);
  font-weight: 600;
}

.lucky-item p {
  margin-bottom: var(--space-3);
  font-size: var(--text-md);
  color: var(--text-muted);
}

.lucky-sources {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-2);
}

/* Padded to a 44px target: these are the only links in the page body a
   visitor is expected to tap on a phone. */
.lucky-sources a {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap);
  padding-inline: var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-full);
  font-size: var(--text-sm);
  color: var(--link);
}

@media (hover: hover) {
  .lucky-sources a:hover {
    background: var(--surface-hover);
    border-color: var(--border-strong);
    text-decoration: none;
  }
}

/* =====================================================================
   9. Maps
   ===================================================================== */

/* The maps page is an app, not a document: the map fills the viewport and
   every control floats over it. dvh rather than vh so the floating chrome
   is positioned against the visible viewport on a phone. */
.maps-page {
  overflow: hidden;
  height: 100vh;
  height: 100dvh;
}

.maps-page .search-page { min-height: auto; }
.maps-page .site-footer { display: none; }

/* The shared results header, reshaped into a floating search pill. */
.maps-page .serp-header {
  position: fixed;
  top: var(--space-2);
  left: var(--space-3);
  right: var(--space-3);
  z-index: 100;
  max-width: 45rem;
  margin: 0 auto;
  padding-top: var(--space-1);
  border: none;
  border-radius: var(--radius-xl);
  background: var(--surface);
  box-shadow: var(--shadow-lg);
}

/* Everything about the header's *layout* — the three rows, the centred
   wordmark, the menu and SafeSearch slots either side of it, the centred tab
   strip, the gutters, the tap sizes — is inherited from the shared rules
   above, so All, Images and Maps are the same header. Only what a card
   floating over a map genuinely needs differs: the search box drops its own
   border and shadow, because a bordered field inside an already-elevated
   card reads as a box drawn inside a box, and the tab row drops its divider,
   because the card's bottom edge already is one. */
.maps-page .serp-box {
  border-color: transparent;
  box-shadow: var(--search-glow);
}

/* The pill is its own surface, so the header's bottom rule would draw a line
   across the middle of a floating card. */
.maps-page .serp-header { border-bottom: none; }

/* On a phone the pill becomes a full-width bar pinned to the top edge, the
   way the Google Maps app does it — a floating rounded card with side
   margins wastes width the map needs. */
@media (max-width: 1023px) {
  .maps-page .serp-header {
    top: 0;
    left: 0;
    right: 0;
    max-width: none;
    border-radius: 0 0;
    padding-top: var(--space-1);
  }
}

/* Inside the floating pill there is no results column below to line up with,
   so the desktop rail is dropped: a 172px reserved column and a 692px cap on
   the box would together leave a 720px card half empty. The wordmark takes
   its natural width and the box fills what is left. */
@media (min-width: 1024px) {
  .maps-page .serp-header-grid {
    grid-template-columns: auto auto 1fr;
    column-gap: var(--space-3);
  }

  .maps-page .serp-form { max-width: none; }
  .maps-page .serp-logo { font-size: 1.375rem; }

  /* SafeSearch is suppressed on this vertical, so the 1fr column to the right
     of the tabs is empty. Span the full header width and centre the strip,
     instead of leaving it flush with the box's left edge as it is on web/image
     results. justify-content: center is used here because the floating maps
     header is intentionally narrow and the auto-margin technique would have no
     free space to distribute; the scroll-x behaviour is still protected on
     small screens by the base auto-margin rules above. */
  .maps-page .serp-tabs {
    grid-area: 2 / 1 / 3 / 4;
    justify-content: center;
  }
  /* Remove the auto-margins so justify-content: center actually controls
     positioning. The auto-margin fallback is only needed on small screens
     where the tab strip can overflow; at 1024px+ the tabs fit. */
  .maps-page .serp-tabs > :first-child { margin-inline-start: 0; }
  .maps-page .serp-tabs > :last-child { margin-inline-end: 0; }
}

/* Map surface: fills the viewport, UI layers above it. */
.maps-main {
  position: fixed;
  inset: 0;
  z-index: 1;
  padding: 0;
}

.maps-app {
  position: relative;
  width: 100%;
  height: 100%;
}

.maps-map-wrap {
  position: absolute;
  inset: 0;
  z-index: 1;
  background: var(--surface-sunken);
}

.map-canvas { width: 100%; height: 100%; }

.maps-noscript {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-6);
  text-align: center;
  font-size: var(--text-sm);
  color: var(--text-muted);
  background: var(--surface);
}

/* --- floating controls ----------------------------------------------- */
/* maps.js measures the header into --maps-header-h, because that header is
   one row on a laptop and two on a phone and anything it covers (the
   drawer's close button, the mode buttons) is untappable. The fallback
   covers the interval before the script runs. */

.maps-menu-btn,
.maps-dir-fab {
  position: fixed;
  top: calc(var(--maps-header-h, 7rem) + var(--space-3));
  z-index: 110;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 3rem;
  height: 3rem;
  border: none;
  border-radius: 50%;
  background: var(--surface);
  color: var(--text-muted);
  box-shadow: var(--shadow-lg);
  transition: background var(--dur-fast) var(--ease), box-shadow var(--dur) var(--ease);
}

.maps-menu-btn { left: calc(var(--space-3) + var(--safe-left)); }
.maps-dir-fab { right: calc(var(--space-3) + var(--safe-right)); }

.maps-menu-btn[aria-expanded="false"] { color: var(--accent); }

@media (hover: hover) {
  .maps-menu-btn:hover,
  .maps-dir-fab:hover {
    background: var(--surface-hover);
    box-shadow: var(--shadow-pop);
  }
}

/* Satellite toggle, stacked under the directions button in the top right.
   The results drawer floats over the map's bottom-left corner, so a control
   parked there vanishes whenever the drawer is open. */
.maps-sat-btn {
  position: fixed;
  top: calc(var(--maps-header-h, 7rem) + var(--space-3) + 3rem + var(--space-2));
  right: calc(var(--space-3) + var(--safe-right));
  z-index: 110;
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--tap);
  padding-inline: var(--space-4);
  border: none;
  border-radius: var(--radius-full);
  background: var(--surface);
  color: var(--text-muted);
  font-size: var(--text-sm);
  font-weight: 600;
  white-space: nowrap;
  box-shadow: var(--shadow-lg);
  transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}

.maps-sat-btn svg { flex-shrink: 0; }

.maps-sat-btn.is-active {
  background: var(--accent);
  color: #fff;
}

@media (prefers-color-scheme: dark) {
  .maps-sat-btn.is-active { color: var(--gray-950); }
}

@media (hover: hover) {
  .maps-sat-btn:hover { background: var(--surface-hover); box-shadow: var(--shadow-pop); }
}

/* Desktop: the floating controls move up into the top corners. The header is
   a centred pill at this width so the corners are free, and the results
   drawer starts directly below the pill — which is exactly where the default
   "below the header" position would put these buttons, parked on top of the
   drawer's own mode buttons.
   This block sits after the three control rules on purpose: a media query
   adds no specificity, so an override placed before them simply loses. */
@media (min-width: 1024px) {
  .maps-menu-btn,
  .maps-dir-fab { top: var(--space-3); }

  /* One button height plus a gap below the directions button. */
  .maps-sat-btn { top: calc(var(--space-3) + 3rem + var(--space-2)); }

  /* The list button is the re-open affordance for a collapsed drawer, so it
     only shows up once the panel has slid away. While the panel is open its
     own X is the close affordance — two round buttons a thumb's width apart
     doing the same thing is the noise the floating list icon used to add. */
  .maps-menu-btn[aria-expanded="true"] { display: none; }
}

/* Satellite mode. maps.js repaints the basemap's own symbol layers; these
   rules cover the DOM overlays MapLibre draws on top, which would sit flat
   against busy imagery. The pins get a white rim plus a shadow so they read
   over both dark treeline and bright roofs. */
.map-canvas.is-satellite .maplibregl-marker svg {
  filter: drop-shadow(0 0 1px rgb(255 255 255 / 0.95))
          drop-shadow(0 1px 3px rgb(0 0 0 / 0.55));
}

.maps-error--floating {
  position: fixed;
  top: calc(var(--maps-header-h, 7rem) + var(--space-3));
  left: 50%;
  transform: translateX(-50%);
  z-index: 120;
  width: min(35rem, calc(100vw - 2 * var(--space-4)));
  box-shadow: var(--shadow-lg);
}

/* --- results / directions drawer ------------------------------------- */
/* A full-screen overlay on a phone, a full-height flush-left panel from
   1024px up. The 1024px figure is shared with maps.js (isMobileDrawer) —
   change both or the drawer animates the wrong way. */

.maps-side {
  position: fixed;
  inset: var(--maps-header-h, 7rem) 0 0 0;
  z-index: 130;
  display: flex;
  flex-direction: column;
  width: 100%;
  background: var(--surface);
  transform: translateX(-100%);
  transition: transform 0.25s var(--ease);
}

.maps-side.is-open { transform: translateX(0); }
.maps-side.is-collapsed { transform: translateX(-100%); }

@media (min-width: 1024px) {
  /* Desktop: the drawer is a full-height panel flush with the left edge of
     the viewport, not a floating card with a gap below the header pill. The
     centered header pill never reaches the left edge, so a top:0 panel does
     not collide with it. */
  .maps-side {
    inset: 0 auto 0 0;
    width: 25rem;
    max-width: calc(100vw - 2 * var(--space-4));
    z-index: 90;
    border-radius: 0;
    box-shadow: var(--shadow-lg);
    transform: none;
    transition: transform 0.25s var(--ease), opacity 0.25s var(--ease);
  }

  .maps-side.is-collapsed {
    transform: translateX(-100%);
    opacity: 0;
    pointer-events: none;
  }
}

/* --- tablet landscape: search bar docks into the left column --------- */
/* On a landscape tablet the centered floating pill wastes the top of the
   left results column and crowds a narrower viewport, so the header docks
   into the top of that column instead — flush left, drawer-width, with the
   drawer starting where it ends. Portrait tablets keep the mobile overlay
   drawer (no left column exists below 1024px), and wider landscape desktops
   keep the centered pill. 1366px caps the range so a 1440px+ monitor does
   not get a corner-docked search bar meant for a tablet. The block is placed
   after every desktop maps rule on purpose: a media query adds no
   specificity, so an override placed before them simply loses. */
@media (min-width: 1024px) and (max-width: 1366px) and (orientation: landscape) {
  .maps-page .serp-header {
    top: 0;
    left: 0;
    right: auto;
    width: 25rem;
    max-width: calc(100vw - 2 * var(--space-4));
    margin: 0;
    border-radius: 0;
  }

  /* The header is now the top of the left column, so the drawer starts where
     the header ends rather than at the top edge. top overrides only the top
     component of the desktop `inset: 0 auto 0 0`. */
  .maps-side {
    top: var(--maps-header-h, 7rem);
  }

  /* The floating list button re-opens a collapsed drawer. Docked header now
     owns the top-left corner, so the button drops to just below the header
     on the left edge. It is hidden while the drawer is open (its own X is the
     close affordance), so this only matters when the panel is collapsed. The
     directions and satellite buttons stay top-right — the right side is
     free, the docked header never reaches it. */
  .maps-menu-btn { top: calc(var(--maps-header-h, 7rem) + var(--space-3)); }
}

.maps-side-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  flex: none;
  padding: var(--space-2) var(--space-3);
  border-bottom: 1px solid var(--border);
}

/* Desktop keeps the header: its X is the panel's collapse affordance (the
   floating list button only reappears once the panel is collapsed). */

.maps-side-title { font-size: var(--text-lg); font-weight: 600; }

.maps-side-close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--tap);
  height: var(--tap);
  border: none;
  border-radius: 50%;
  background: transparent;
  color: var(--text-muted);
}

@media (hover: hover) {
  .maps-side-close:hover { background: var(--surface-hover); }
}

.maps-side-scroll {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
  padding: var(--space-3) var(--space-3) calc(var(--space-4) + var(--safe-bottom));
  overflow-y: auto;
  overscroll-behavior: contain;
}

.maps-empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: var(--space-10) var(--space-5);
  text-align: center;
  font-size: var(--text-sm);
  color: var(--text-muted);
}

.maps-empty-icon { color: var(--text-subtle); margin-bottom: var(--space-3); }

.maps-stats {
  margin-bottom: var(--space-3);
  font-size: var(--text-sm);
  color: var(--text-subtle);
}

.maps-hint,
.maps-noresults {
  margin-bottom: var(--space-4);
  font-size: var(--text-sm);
  color: var(--text-muted);
}

.maps-error {
  margin-bottom: var(--space-4);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--danger-border);
  border-radius: var(--radius-md);
  background: var(--danger-surface);
  color: var(--danger);
  font-size: var(--text-sm);
}

/* --- place rows ------------------------------------------------------ */

.map-results { list-style: none; margin: 0 0 var(--space-4); padding: 0; }

.map-result {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-3);
  border-radius: var(--radius-md);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease);
}

.map-result.is-selected { background: var(--accent-soft); }

@media (hover: hover) {
  .map-result:hover { background: var(--surface-hover); }
}

.map-result::before {
  content: "";
  flex: none;
  width: 2.25rem;
  height: 2.25rem;
  margin-top: 2px;
  border-radius: 50%;
  background: var(--surface-hover) url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%2364748b"><path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z"/></svg>') center / 1.25rem no-repeat;
}

.map-result-main {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  flex: 1;
}

.map-result-name { font-size: var(--text-md); font-weight: 600; color: var(--text-strong); }
.map-result-type { font-size: var(--text-sm); color: var(--green-text); }
.map-result-addr { font-size: var(--text-sm); color: var(--text-muted); }
.map-result-coords {
  font-size: var(--text-xs);
  color: var(--text-subtle);
  font-variant-numeric: tabular-nums;
}

.map-result-actions { display: flex; flex: none; }

.map-result-dir {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap);
  padding-inline: var(--space-3);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-full);
  background: var(--surface-raised);
  color: var(--link);
  font-size: var(--text-xs);
  font-weight: 600;
  white-space: nowrap;
}

@media (hover: hover) {
  .map-result-dir:hover { background: var(--surface-hover); text-decoration: none; }
}

/* --- directions form ------------------------------------------------- */
/* Pinned above the scroll area, so a page of results never pushes the mode
   buttons and the from/to fields out of reach. */

.maps-dir {
  position: relative;
  z-index: 2;
  flex: none;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-3);
  border-bottom: 1px solid var(--border);
  background: var(--surface);
}

@media (min-width: 1024px) {
  .maps-dir { border-radius: var(--radius-lg) var(--radius-lg) 0 0; }
}

.maps-dir-mode { display: flex; gap: var(--space-2); }

.maps-dir-mode-btn {
  flex: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: var(--tap);
  padding-inline: var(--space-2);
  border: 1px solid transparent;
  border-radius: var(--radius-full);
  background: var(--surface-hover);
  color: var(--text-muted);
  font-size: var(--text-sm);
  font-weight: 600;
  transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}

.maps-dir-mode-btn input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
}

.maps-dir-mode-btn.is-active {
  background: var(--accent-soft);
  color: var(--accent);
  border-color: var(--accent-border);
}

@media (hover: hover) {
  .maps-dir-mode-btn:hover { background: var(--accent-soft); color: var(--accent); }
}

.maps-dir-field { position: relative; display: block; }

.maps-dir-input {
  width: 100%;
  min-height: var(--tap);
  padding: var(--space-2) var(--space-3) var(--space-2) 2.25rem;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-md);
  background: var(--surface-raised);
  color: var(--text-strong);
  /* 16px: these are text inputs on a map, and an iOS zoom here leaves the
     visitor pinching back out over a full-screen canvas. */
  font-size: var(--input-size);
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}

.maps-dir-input:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 2px var(--accent-soft);
}

.maps-dir-field::before {
  content: "";
  position: absolute;
  left: var(--space-3);
  top: 50%;
  transform: translateY(-50%);
  width: 0.875rem;
  height: 0.875rem;
  border: 2px solid var(--border-strong);
  border-radius: 50%;
}

.maps-dir-field + .maps-dir-field::before {
  width: 0.625rem;
  height: 0.625rem;
  border: none;
  border-radius: 0;
  background: var(--border-strong);
  transform: translateY(-50%) rotate(45deg);
}

.maps-dir-actions { display: flex; gap: var(--space-2); }

.maps-dir-go,
.maps-dir-locate {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--tap);
  padding-inline: var(--space-4);
  border-radius: var(--radius-md);
  font-size: var(--text-sm);
  font-weight: 600;
}

.maps-dir-go {
  flex: 1;
  border: none;
  background: var(--accent);
  color: #fff;
}

@media (prefers-color-scheme: dark) {
  .maps-dir-go { color: var(--gray-950); }
}

.maps-dir-locate {
  flex: none;
  border: 1px solid var(--border-strong);
  background: var(--surface-raised);
  color: var(--text-muted);
}

.maps-dir-go:disabled,
.maps-dir-locate:disabled { opacity: 0.55; cursor: default; }

@media (hover: hover) {
  .maps-dir-go:not(:disabled):hover { filter: brightness(1.1); }
  .maps-dir-locate:not(:disabled):hover { background: var(--surface-hover); color: var(--accent); }
}

/* --- type-ahead ------------------------------------------------------ */

.maps-typeahead {
  position: absolute;
  top: calc(100% + var(--space-1));
  left: 0;
  right: 0;
  z-index: 20;
  list-style: none;
  margin: 0;
  padding: var(--space-1);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--surface-raised);
  box-shadow: var(--shadow-lg);
  display: none;
  max-height: 17.5rem;
  overflow-y: auto;
  overscroll-behavior: contain;
}

.maps-typeahead-item {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-height: var(--tap);
  justify-content: center;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  cursor: pointer;
}

@media (hover: hover) {
  .maps-typeahead-item:hover { background: var(--surface-hover); }
}

.maps-typeahead-sub { font-size: var(--text-xs); color: var(--text-subtle); }

/* Place suggestions under the maps search pill. Reuses .suggestions and
   only raises it above the map chrome. */
.maps-suggestions { z-index: 120; }

.maps-suggestions li {
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  gap: 1px;
}

/* --- route turn list ------------------------------------------------- */
/* max-height is what keeps the place list below it reachable: a long turn
   list scrolls within itself rather than running the whole panel. */

.maps-route {
  flex: none;
  display: flex;
  flex-direction: column;
  margin-bottom: var(--space-4);
  padding: var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface-raised);
  max-height: 60%;
  min-height: 7.5rem;
  overflow: hidden;
}

/* When the place list has been hidden (is-routing on the scroll area), the
   route panel is the only thing in the scroll area, so the 60% cap and the
   below-it margin are dead weight: let the panel grow to fill the scroll
   area's full height and let the step list inside it scroll on its own. */
.maps-side-scroll.is-routing > .maps-route {
  flex: 1 1 auto;
  max-height: none;
  margin-bottom: 0;
}

.maps-route-summary {
  flex: none;
  margin-bottom: var(--space-3);
  font-size: var(--text-md);
  font-weight: 700;
  color: var(--text-strong);
}

.maps-steps {
  list-style: none;
  margin: 0;
  padding: 0;
  counter-reset: step;
  overflow-y: auto;
  min-height: 0;
  overscroll-behavior: contain;
}

.maps-steps li {
  position: relative;
  padding: var(--space-3) var(--space-2) var(--space-3) 2.25rem;
  border-bottom: 1px solid var(--border);
  font-size: var(--text-sm);
  line-height: 1.5;
  counter-increment: step;
}

.maps-steps li:last-child { border-bottom: none; }

.maps-steps li::before {
  content: counter(step);
  position: absolute;
  left: 0;
  top: var(--space-3);
  width: 1.5rem;
  height: 1.5rem;
  border-radius: 50%;
  background: var(--accent);
  color: #fff;
  font-size: var(--text-xs);
  font-weight: 700;
  line-height: 1.5rem;
  text-align: center;
}

@media (prefers-color-scheme: dark) {
  .maps-steps li::before { color: var(--gray-950); }
}

.maps-step-instr { color: var(--text); }
.maps-step-dist {
  margin-left: var(--space-2);
  color: var(--text-subtle);
  font-variant-numeric: tabular-nums;
}

/* --- selected place card --------------------------------------------- */
/* A bottom sheet on a phone, a floating card beside the drawer on a
   laptop. */

.maps-place-card {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 95;
  width: 100%;
  max-height: 55dvh;
  border-radius: var(--radius-xl) var(--radius-xl) 0 0;
  background: var(--surface);
  box-shadow: 0 -4px 20px rgb(2 6 23 / 0.18);
  overflow-y: auto;
  overscroll-behavior: contain;
  transition: transform 0.25s var(--ease), opacity 0.25s var(--ease);
}

/* Kept in flow so it can transition out rather than disappearing. */
.maps-place-card[hidden] {
  display: block;
  transform: translateY(100%);
  opacity: 0;
  pointer-events: none;
}

@media (min-width: 1024px) {
  .maps-place-card {
    left: calc(26rem + var(--space-6) + var(--safe-left));
    right: auto;
    bottom: calc(var(--space-6) + var(--safe-bottom));
    width: 22.5rem;
    max-width: calc(100vw - 29rem);
    max-height: calc(100dvh - 12rem);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lg);
  }

  .maps-place-card[hidden] { transform: translateY(calc(100% + var(--space-10))); }
}

.maps-place-card-close {
  position: absolute;
  top: var(--space-2);
  right: var(--space-2);
  z-index: 2;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--tap);
  height: var(--tap);
  border: none;
  border-radius: 50%;
  background: var(--surface);
  color: var(--text-muted);
}

@media (hover: hover) {
  .maps-place-card-close:hover { background: var(--surface-hover); }
}

.maps-place-card-body {
  padding: var(--space-4);
  padding-bottom: calc(var(--space-4) + var(--safe-bottom));
  font-size: var(--text-sm);
  line-height: 1.55;
}

.maps-place-card-body h3 {
  margin-bottom: var(--space-1);
  padding-right: var(--tap);
  font-size: var(--text-lg);
  font-weight: 600;
}

.maps-place-meta { color: var(--green-text); font-size: var(--text-sm); margin-bottom: var(--space-2); }
.maps-place-addr { color: var(--text-muted); margin-bottom: var(--space-3); }

.maps-place-actions {
  display: flex;
  gap: var(--space-2);
  margin-top: var(--space-4);
}

.maps-place-actions a,
.maps-place-actions button {
  flex: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: var(--tap);
  padding-inline: var(--space-3);
  border: none;
  border-radius: var(--radius-md);
  background: var(--accent);
  color: #fff;
  font-size: var(--text-sm);
  font-weight: 600;
  text-decoration: none;
}

@media (prefers-color-scheme: dark) {
  .maps-place-actions a,
  .maps-place-actions button { color: var(--gray-950); }
}

/* --- map popups and credits ------------------------------------------ */

.maplibregl-popup-content {
  padding: var(--space-3);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
}

.map-pop-name { display: block; font-size: var(--text-sm); font-weight: 700; }
.map-pop-type { display: block; font-size: var(--text-xs); color: var(--green-text); }
.map-pop-addr { display: block; font-size: var(--text-sm); color: var(--text-muted); }
.map-pop-coords {
  display: block;
  font-size: var(--text-xs);
  color: var(--text-subtle);
  font-variant-numeric: tabular-nums;
}
.map-pop-dist { display: block; font-size: var(--text-xs); color: var(--text-muted); margin-top: 2px; }

/* Credits live behind MapLibre's own ⓘ toggle (collapsed in maps.js), so
   the resting state is one round button and the full list opens on tap. The
   corner is the only home available: the drawer can be closed and the site
   footer is hidden here, so anywhere else the credits could be dismissed —
   which the tile and map-data licences do not allow. */
.maplibregl-ctrl-bottom-right { z-index: 10; }

.maps-page .maplibregl-ctrl-attrib.maplibregl-compact {
  min-height: 1.625rem;
  margin: 0 var(--space-2) calc(var(--space-2) + var(--safe-bottom)) 0;
  border-radius: var(--radius-full);
  background: rgb(255 255 255 / 0.9);
  box-shadow: var(--shadow-sm);
}

.maps-page .maplibregl-ctrl-attrib-button { border-radius: 50%; }

.maps-page .maplibregl-ctrl-attrib.maplibregl-compact-show {
  padding: 3px 30px 3px 12px;
  max-width: min(35rem, calc(100vw - 2rem));
}

.maps-page .maplibregl-ctrl-attrib-inner {
  font-size: 11px;
  line-height: 1.5;
  color: #475569;
}

.maps-page .maplibregl-ctrl-attrib-inner a { color: #0065bd; }

/* --- maps disabled --------------------------------------------------- */
/* With the vertical switched off the page is just a notice, so the
   full-screen app behaviour is dropped and the message can scroll. */

.maps-page.maps-disabled {
  overflow: auto;
  height: auto;
}

.maps-page.maps-disabled .serp-header {
  position: sticky;
  top: 0;
  left: auto;
  right: auto;
  max-width: none;
  border-radius: 0;
  box-shadow: none;
  border-bottom: 1px solid var(--border);
}

.maps-page.maps-disabled .maps-main {
  position: relative;
  inset: auto;
  min-height: 60vh;
}

.maps-page.maps-disabled .serp-noresults {
  max-width: calc(var(--measure) + 2 * var(--gutter));
  margin-inline: auto;
  padding: var(--space-8) var(--gutter);
}

.maps-page.maps-disabled .site-footer { display: block; }

/* =====================================================================
   10. Motion and print
   ===================================================================== */

/* Honour the OS "reduce motion" setting everywhere rather than in the one
   place a spinner happened to be noticed. Animations are not removed —
   a spinner that stops spinning reads as a hang — they are reduced to a
   single frame's worth of movement, and the loading spinner keeps turning
   slowly because it carries meaning. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }

  .image-more-spinner,
  .video-more-spinner {
    animation-duration: 1.5s !important;
    animation-iteration-count: infinite !important;
  }

  .logo,
  .serp-logo { transform: none; }
}

@media print {
  .site-header,
  .site-footer,
  .home-header,
  .serp-header,
  .serp-pagination,
  .image-more,
  .video-more,
  .mic-btn,
  .suggestions { display: none !important; }

  body { background: #fff; color: #000; font-size: 11pt; }

  .serp-main,
  .about-main,
  .lucky-main { padding: 0; }

  .serp-result,
  .about-section,
  .lucky-item { break-inside: avoid; max-width: none; }

  /* A printed page loses every href, so the citation becomes the only way
     back to the source. */
  .serp-cite { white-space: normal; overflow: visible; }
}
