/**
 * VOYAGER BRIDGE - legacy tokens, re-pointed, on migrated pages only (#29)
 * =============================================================================
 *
 * The problem this solves: `ayten.css` and `navbar_styles.css` are loaded by all
 * 20 pages and use their own `--color-*` tokens in ~180 places. A page cannot go
 * dark while the navbar and shared components stay hardcoded light - that is the
 * half-dark page, which is worse than no dark mode at all.
 *
 * Editing ayten.css directly would restyle every unmigrated page at once, which
 * is exactly the big-bang this migration is structured to avoid.
 *
 * So: this file redefines the LEGACY tokens in terms of Voyager tokens, scoped to
 * `.v-page`. A page that opts in gets its shared chrome themed for free and every
 * other page is untouched, because nothing here applies without that class.
 *
 * Load order matters - AFTER ayten.css (so these win) and AFTER voyager.css (so
 * the --v-* values exist):
 *
 *   voyager.css  ->  ayten.css  ->  voyager-bridge.css  ->  page CSS
 *
 * As pages migrate, this file is what shrinks: once nothing reads a `--color-*`
 * token any more, delete the mapping. When it is empty, ayten.css can go.
 */

body.v-page {
    /* ---- Surfaces ---- */
    --color-bg-primary:    var(--v-surface-high);
    --color-bg-secondary:  var(--v-surface-low);
    --color-bg-tertiary:   var(--v-surface);
    --color-white:         var(--v-surface-high);

    /* ---- Ink ---- */
    --color-text-primary:   var(--v-ink);
    --color-text-secondary: var(--v-ink-muted);
    --color-text-hint:      var(--v-ink-subtle);
    --color-text-inverse:   var(--v-accent-on);
    /* Disabled stays deliberately low-contrast; WCAG 1.4.3 exempts inactive
       controls, and lifting it makes disabled things read as enabled. */
    --color-text-disabled:  var(--v-ink-subtle);

    /* ---- Borders. Voyager forbids 1px rules, but shared components still draw
       them; pointing at the hairline makes them near-invisible rather than
       requiring every one to be hunted down. ---- */
    --color-border:        var(--v-hairline);
    --color-border-light:  var(--v-hairline);
    --color-border-dark:   var(--v-hairline);

    /* ---- Brand. The legacy primary is a blue; Voyager's accent is emerald.
       Mapping rather than keeping both means one accent on the page. ---- */
    --color-primary:       var(--v-accent);
    --color-primary-dark:  var(--v-accent);
    --color-primary-light: var(--v-accent);
    --color-primary-50:    var(--v-accent-soft);
    --color-primary-100:   var(--v-accent-soft);

    --color-secondary:       var(--v-warning);
    --color-secondary-dark:  var(--v-warning);
    --color-secondary-light: var(--v-warning);

    /* ---- Semantic ---- */
    --color-success:       var(--v-positive);
    --color-success-dark:  var(--v-positive);
    --color-success-light: var(--v-positive);
    --color-warning:       var(--v-warning);
    --color-warning-dark:  var(--v-warning);
    --color-warning-light: var(--v-warning);
    --color-error:         var(--v-danger);
    --color-error-dark:    var(--v-danger);
    --color-error-light:   var(--v-danger);
    --color-info:          var(--v-accent);
    --color-info-dark:     var(--v-accent);
    --color-info-light:    var(--v-accent);

    /* ---- Greys. Ordered light -> dark in the legacy scale, so they map onto
       the surface ladder and then the ink ladder. In dark mode this inverts
       correctly because both ladders invert together. ---- */
    --color-grey-50:  var(--v-surface);
    --color-grey-100: var(--v-surface-low);
    --color-grey-200: var(--v-surface-low);
    --color-grey-300: var(--v-surface-lowest);
    --color-grey-400: var(--v-ink-subtle);
    --color-grey-500: var(--v-ink-subtle);
    --color-grey-600: var(--v-ink-muted);
    --color-grey-700: var(--v-ink-muted);
    --color-grey-800: var(--v-ink);
    --color-grey-900: var(--v-ink);

    /* ---- Type. The pairing is the look: Space Grotesk for display and labels,
       Manrope for reading. ---- */
    --font-family-primary: var(--v-font-body);
}

/* Headings and anything the page treats as a label take the display face.
   Scoped to .v-page so no other page's typography moves. */
body.v-page h1,
body.v-page h2,
body.v-page h3,
body.v-page h4,
body.v-page .navbar-brand,
body.v-page .btn,
body.v-page button {
    font-family: var(--v-font-display);
    letter-spacing: -0.01em;
}

body.v-page button,
body.v-page .btn {
    letter-spacing: 0.02em;
}

/* -----------------------------------------------------------------------------
   NAVBAR
   navbar_styles.css hardcodes a light chrome and white text on it. Those two
   assumptions are the ones that break in dark mode, so they are re-pointed here
   rather than in the shared file.
   -------------------------------------------------------------------------- */

body.v-page .navbar {
    background: var(--v-surface-low);
    box-shadow: var(--v-lift);
}

body.v-page .navbar-brand,
body.v-page .navbar-brand span,
body.v-page .nav-btn {
    color: var(--v-ink);
}

/* navbar_styles.css washes the active/hover pill with rgba(255,255,255,.25),
   which was designed for a dark navbar. On a light ground it is invisible and on
   the new dark ground it is a pale block under pale text — 1.29:1, found by
   auditing computed contrast rather than by reading the CSS. */
body.v-page .nav-btn:hover,
body.v-page .nav-btn.active,
body.v-page .nav-btn[aria-current] {
    background: var(--v-surface-highest);
    color: var(--v-ink);
}

body.v-page .hamburger-line {
    background: var(--v-ink);
}

body.v-page .mobile-menu-dropdown {
    background: var(--v-surface-high);
}

/* The theme control sits in the navbar, so it has to read on that ground. */
body.v-page .v-theme-toggle {
    color: var(--v-ink-muted);
}

body.v-page .v-theme-toggle:hover {
    background: var(--v-surface-highest);
    color: var(--v-ink);
}

/* -----------------------------------------------------------------------------
   FOOTER
   -------------------------------------------------------------------------- */

body.v-page .footer {
    background: var(--v-surface-lowest);
    color: var(--v-ink-muted);
}

body.v-page .footer-link {
    color: var(--v-ink-muted);
}

body.v-page .footer-link:hover {
    color: var(--v-ink);
}

/* -----------------------------------------------------------------------------
   LEAFLET
   The map tiles are third-party and stay as they are; only the controls and
   popups, which are ours to style, follow the theme.
   -------------------------------------------------------------------------- */

body.v-page .leaflet-control-zoom a,
body.v-page .leaflet-bar a {
    background: var(--v-surface-high);
    color: var(--v-ink);
    border-color: var(--v-hairline);
}

body.v-page .leaflet-control-zoom a:hover,
body.v-page .leaflet-bar a:hover {
    background: var(--v-surface-highest);
}

/* The remaining light surfaces in dark mode were all Leaflet's own chrome, found
   by sweeping computed backgrounds on the real authenticated page. */
body.v-page .leaflet-control-layers,
body.v-page .leaflet-control-layers-expanded,
body.v-page .leaflet-control-attribution {
    background: var(--v-surface-high);
    color: var(--v-ink-muted);
}

body.v-page .leaflet-control-attribution a {
    color: var(--v-accent);
}

body.v-page .leaflet-container {
    background: var(--v-surface-lowest);
}

body.v-page .weather-toggle-btn,
body.v-page .weather-unit-btn {
    background: var(--v-surface-high);
    color: var(--v-ink-muted);
}

body.v-page .weather-unit-btn.active {
    background: var(--v-accent);
    color: var(--v-accent-on);
}

body.v-page .leaflet-popup-content-wrapper,
body.v-page .leaflet-popup-tip {
    background: var(--v-surface-high);
    color: var(--v-ink);
    box-shadow: var(--v-lift);
}

/* Dark mode makes a bright tile layer glare against the chrome. Damping it is
   the standard treatment and costs nothing when the layer is already dark. */
/* Dimming is a fallback for a basemap that is natively light — Satellite and
   Humanitarian. Skip it when CARTO Dark Matter is showing, or the map goes
   muddy and the route loses its contrast against it. */
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) body.v-page .leaflet-container:not([data-basemap-dark="true"]) .leaflet-tile-pane {
        filter: brightness(0.82) saturate(0.9);
    }
}

:root[data-theme="dark"] body.v-page .leaflet-container:not([data-basemap-dark="true"]) .leaflet-tile-pane {
    filter: brightness(0.82) saturate(0.9);
}

/* -----------------------------------------------------------------------------
   FIXES FROM THE FIRST REAL LOOK AT THE MIGRATED PAGE
   Reported against dev on 2026-09-01, measured rather than guessed.
   -------------------------------------------------------------------------- */

/* Weather controls were 38x13 and 45x19 CSS px with 10px labels — under the
   24x24 minimum target size, and unusable on a touch screen. */
body.v-page .weather-unit-btn,
body.v-page .weather-toggle-btn {
    min-height: 28px;
    min-width: 44px;
    padding: 5px 12px;
    font-size: var(--v-text-label);
    line-height: 1.2;
    border-radius: 999px;
}

/* Lazy-loaded photos had height:auto over an unloaded image, so the box was 0px
   tall — and `loading="lazy"` never fires for a zero-height element, so the photo
   could never load and the height could never resolve. Reserving the box breaks
   that circle and stops the layout jumping when the image arrives. */
body.v-page .popup-photo,
body.v-page .drawer-photo,
body.v-page .gallery-photo,
body.v-page [data-lazy-day-photo] {
    display: block;
    width: 100%;
    aspect-ratio: 4 / 3;
    min-height: 120px;
    object-fit: cover;
    background: var(--v-surface-low);
}

/* The menu control sat over the card title. Give it its own column and reserve
   the space, rather than nudging it with a margin that breaks at other widths. */
body.v-page .summary-card-header,
body.v-page .itinerary-header-row.editor-header {
    display: flex;
    align-items: center;
    gap: 10px;
}

body.v-page .summary-card-header > :first-child,
body.v-page .itinerary-header-row.editor-header .header-title {
    flex: 1 1 auto;
    min-width: 0;
}

body.v-page .summary-card-header .menu-btn,
body.v-page .summary-card .card-menu-btn,
body.v-page .itinerary-header-row.editor-header .header-menu-btn {
    flex: 0 0 auto;
    position: static;
    margin-left: auto;
}

/* -----------------------------------------------------------------------------
   FORM CONTROLS

   A native input or select with no explicit background falls back to the
   BROWSER's control colour, which under color-scheme:dark is rgb(59,59,59) in
   Arial — visibly not the page. The create page's travel-companion rows were
   exactly that: raw controls at 14px/4px radius sitting beside designed ones at
   --v-surface-high, Roboto, 16px/6px.

   Styling the elements rather than hunting each instance means a control added
   later is correct by default. Low specificity on purpose: any page rule with a
   class still wins.
   -------------------------------------------------------------------------- */

body.v-page input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="color"]),
body.v-page select,
body.v-page textarea {
    /* A RECESSED WELL, not a bordered box. The design system prohibits 1px
       rules — separation comes from a tonal step — and the first version of this
       rule broke that, contributing most of the 152 visible borders measured on
       create.html. One step DOWN the ladder reads as inset against a
       --v-surface-high card in dark AND against white in light, so it needs no
       per-theme handling. */
    background: var(--v-surface);
    color: var(--v-ink);
    border: none;
    border-radius: var(--v-radius-sm, 10px);
    padding: 10px 12px;
    font-family: inherit;
    /* 16px is also the floor below which iOS zooms the page on focus. */
    font-size: 1rem;
    line-height: 1.4;
}

/* Checkboxes and radios are excluded from the rule above — giving them a
   background would paint over the tick. They still need the accent, or they
   render as the browser's raw grey squares next to themed controls. */
body.v-page input[type="checkbox"],
body.v-page input[type="radio"],
body.v-page input[type="range"],
body.v-page progress {
    accent-color: var(--v-accent);
}

body.v-page input[type="checkbox"]:focus-visible,
body.v-page input[type="radio"]:focus-visible {
    outline: 2px solid var(--v-accent);
    outline-offset: 2px;
}

body.v-page input::placeholder,
body.v-page textarea::placeholder {
    color: var(--v-ink-subtle);
    opacity: 1;   /* Firefox dims placeholders by default, on top of the colour */
}

/* Focus is an outline, which the system allows: it is an accessibility
   affordance rather than a divider, and it is only present while focused. */
body.v-page input:focus-visible,
body.v-page select:focus-visible,
body.v-page textarea:focus-visible {
    outline: 2px solid var(--v-accent);
    outline-offset: 1px;
}

body.v-page input:disabled,
body.v-page select:disabled,
body.v-page textarea:disabled {
    background: var(--v-surface-low);
    color: var(--v-ink-subtle);
    cursor: not-allowed;
}

/* -----------------------------------------------------------------------------
   CARD TYPOGRAPHY
   The cards had accumulated 12 different inline font sizes and four different
   line-heights (1, 1.3, 1.4, 1.5), applied per-template — which is why the
   continuous view read as sporadic: two adjacent cards could set the same text
   at 13px/1.4 and 13px/1.3 and neither looked deliberate.

   One scale, defined once. Everything below reads these, so a future change is
   a token edit rather than a sweep. The floor is 14px for prose: the previous
   13px was itself a raise from 12px and still read small in a 280px column.
   -------------------------------------------------------------------------- */
body.v-page {
    /* NAMED --v-ui-*, NOT --v-text-*. The design system owns --v-text-body at
       0.9375rem; an earlier version of this block redefined it as 0.875rem for
       a different purpose, and because the definition sits on body.v-page it
       beat the :root value on every migrated page — silently shrinking every
       system use of the token. Reusing a token name for a second meaning is the
       bug; the fix is a separate namespace. */
    /* ALIASES onto the five steps, not extra sizes. These started as their own
       values (15/14/13px) and were therefore a sixth and seventh step, which is
       the thing the consolidation exists to remove. Kept as names because they
       read better at the call sites than the raw step does. */
    --v-text-prose:   var(--v-text-body);    /* descriptions, summaries, day text */
    --v-ui-label:     var(--v-text-body);    /* labels, meta, secondary lines     */
    --v-ui-meta:      var(--v-text-label);   /* timestamps, counts, captions      */
    --v-leading-prose: 1.65;
    --v-leading-body:  1.5;

    /* The legacy token scale drives ~98 elements that are not worth rewriting
       individually; raising the two smallest steps lifts them all at once. */
    --font-size-xs: var(--v-text-label);
    --font-size-sm: var(--v-text-label);
    /* The navbar and footer carry a parallel scale of their own
       (--font-size-base/-lg in navbar_styles.css). Pointed at the steps here
       rather than edited there, so shared chrome joins the scale on every page
       at once. --font-size-4xl is deliberately NOT mapped: it sizes the brand
       mark inside a fixed-height bar, which is a layout constraint rather than
       a type step, and the headline token would overflow it. */
    --font-size-base: var(--v-text-body);
    --font-size-lg:   var(--v-text-body);
}

/* Formatted prose: paragraphs and lists, produced by AytenProse. */
body.v-page .v-prose {
    font-size: var(--v-text-prose);
    line-height: var(--v-leading-prose);
    color: var(--v-ink-muted);
}

body.v-page .v-prose p,
body.v-page .v-prose ul,
body.v-page .v-prose ol {
    margin: 0 0 0.7em;
}

body.v-page .v-prose > :last-child { margin-bottom: 0; }

body.v-page .v-prose ul,
body.v-page .v-prose ol { padding-left: 1.2em; }

body.v-page .v-prose li { margin: 0 0 0.35em; }
body.v-page .v-prose li:last-child { margin-bottom: 0; }

body.v-page .v-prose strong {
    color: var(--v-ink);
    font-weight: 600;
}

/* Clamp by LINE COUNT, not character count. A character slice cuts mid-word and
   is wrong at every width but one; this always ends on a line boundary. */
body.v-page .v-prose-clamp {
    max-height: calc(var(--v-prose-lines, 4) * var(--v-leading-prose) * var(--v-text-prose));
    overflow: hidden;
}

body.v-page .v-prose-block.is-open .v-prose-clamp,
body.v-page .day-summary-description-text.expanded {
    max-height: none;
    overflow: visible;
}

/* The expand control is always a block BELOW the prose — never an overlay. */
body.v-page .v-prose-more,
body.v-page .day-summary-more-btn {
    display: block;
    margin-top: 6px;
    padding: 2px 0;
    background: none;
    border: 0;
    color: var(--v-accent);
    font-family: inherit;
    font-size: var(--v-ui-label);
    font-weight: 600;
    line-height: 1.4;
    cursor: pointer;
    text-align: left;
}

body.v-page .v-prose-more:hover,
body.v-page .day-summary-more-btn:hover { opacity: 0.82; }

body.v-page .v-prose-more[hidden] { display: none; }

/* Card text: one size and one leading, replacing the per-template inline values. */
body.v-page .card-description,
body.v-page .card-summary,
body.v-page .day-description,
body.v-page .lodging-description,
body.v-page .activity-description,
body.v-page .summary-card-description,
body.v-page .stacked-summary-description {
    font-size: var(--v-text-prose);
    line-height: var(--v-leading-prose);
    color: var(--v-ink-muted);
    margin: 8px 0 0;
}

/* Inline font-size/line-height written by the card templates would otherwise
   win over the scale above. These are the four that still carry them. */
body.v-page .day-description[style],
body.v-page .lodging-description[style],
body.v-page .activity-description[style],
body.v-page .card-description[style] {
    font-size: var(--v-text-prose) !important;
    line-height: var(--v-leading-prose) !important;
}

body.v-page .card-subtitle,
body.v-page .card-nights {
    font-size: var(--v-ui-label);
    line-height: var(--v-leading-body);
}

/* -----------------------------------------------------------------------------
   STACKED VIEW SUMMARY + WEATHER
   The mobile stacked view showed day blocks only; the trip summary and the
   weather charts lived exclusively in the desktop editor panel.
   -------------------------------------------------------------------------- */

body.v-page .stacked-summary {
    background: var(--v-surface-high);
    border-radius: var(--v-radius-lg);
    padding: 16px;
    margin: 0 0 12px;
    display: flex;
    flex-direction: column;
    gap: 12px;
}

body.v-page .stacked-summary-title {
    font-family: var(--v-font-display);
    font-weight: 700;
    font-size: 1.05rem;
    letter-spacing: -0.01em;
    color: var(--v-ink);
}

body.v-page .stacked-stats {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 8px;
}

body.v-page .stacked-stat {
    display: flex;
    flex-direction: column;
    gap: 1px;
    background: var(--v-surface-low);
    border-radius: var(--v-radius-sm);
    padding: 10px 8px;
}

body.v-page .stacked-stat b {
    font-family: var(--v-font-display);
    font-weight: 700;
    font-size: 1.25rem;
    line-height: 1.1;
    color: var(--v-ink);
    font-variant-numeric: tabular-nums;
}

body.v-page .stacked-stat span {
    font-family: var(--v-font-display);
    font-size: 0.66rem;
    font-weight: 500;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--v-ink-subtle);
}

body.v-page .stacked-summary-description {
    margin: 0;
    font-size: 0.9rem;
    line-height: 1.55;
    color: var(--v-ink-muted);
}

/* Only the truncated state is interactive, and it is the only state that
   carries [role=button] — so the affordance follows the behaviour. */
body.v-page .stacked-summary-description[role="button"] {
    cursor: pointer;
}

body.v-page .stacked-summary-description[aria-expanded="false"]::after {
    content: ' More';
    font-weight: 600;
    color: var(--v-accent);
}

body.v-page .stacked-summary-description[aria-expanded="true"]::after {
    content: ' Less';
    font-weight: 600;
    color: var(--v-accent);
}

body.v-page .stacked-summary-cities {
    font-size: 0.82rem;
    color: var(--v-ink-muted);
}

/* One reserved box per expected weather card. The cards resolve one at a time
   over the network; without a reserved slot each arrival shoved the day blocks
   below it down, and the view appeared to rebuild itself under the reader. */
body.v-page .stacked-weather-slot {
    min-height: 180px;
    display: flex;
    flex-direction: column;
}

body.v-page .stacked-weather-slot:empty {
    background: var(--v-surface-low);
    border-radius: var(--v-radius-lg);
    opacity: 0.5;
}

/* Layout for the marker badge; the two colours are set from JS because they come
   from the city palette and the ink is computed against it. */
body.v-page .stacked-marker-badge {
    width: 20px;
    height: 20px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: var(--v-ui-meta);
    font-weight: 700;
    flex-shrink: 0;
    margin-right: 8px;
}

body.v-page .stacked-star { color: var(--v-warning); }

body.v-page .stacked-price {
    color: var(--v-positive);
    margin-left: 6px;
    font-size: var(--v-ui-meta);
}

body.v-page .stacked-activity-description {
    --v-prose-lines: 2;
    margin-top: 2px;
}

/* An activity row is a real control now: it takes focus, so it has to show it. */
body.v-page .stacked-activity-item:focus-visible {
    outline: 2px solid var(--v-accent);
    outline-offset: -2px;
}

body.v-page .stacked-weather {
    display: flex;
    flex-direction: column;
    gap: 12px;
    margin: 0 0 12px;
}

/* The weather card is built for the desktop row, where it sits in a fixed-width
   column. In the sheet it has to take the full width instead. */
body.v-page .stacked-weather .weather-chart-card {
    width: 100%;
    min-width: 0;
    max-width: none;
}

@media (max-width: 420px) {
    body.v-page .stacked-stats { grid-template-columns: repeat(2, 1fr); }
}

/* -----------------------------------------------------------------------------
   LAYERS CONTROL
   Leaflet's layers toggle is a black bitmap (images/layers.png, fetched from the
   CDN) sitting on what is now a dark control, so it read as inverted — the same
   baked-fill problem as the locate arrow below, and it takes the same cure.

   Drawn as a MASK so background-color supplies the ink and the glyph follows the
   theme. This also drops one cross-origin image request.
   -------------------------------------------------------------------------- */

body.v-page .leaflet-control-layers-toggle {
    background-image: none;
    background-color: var(--v-ink-muted);
    -webkit-mask-image: url("data:image/svg+xml;charset=UTF-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M11.99 18.54l-7.37-5.73L3 14.07l9 7 9-7-1.63-1.27-7.38 5.74zM12 16l7.36-5.73L21 9l-9-7-9 7 1.63 1.27L12 16z'/%3E%3C/svg%3E");
    mask-image: url("data:image/svg+xml;charset=UTF-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M11.99 18.54l-7.37-5.73L3 14.07l9 7 9-7-1.63-1.27-7.38 5.74zM12 16l7.36-5.73L21 9l-9-7-9 7 1.63 1.27L12 16z'/%3E%3C/svg%3E");
    -webkit-mask-size: 18px 18px;
    mask-size: 18px 18px;
    -webkit-mask-repeat: no-repeat;
    mask-repeat: no-repeat;
    -webkit-mask-position: center;
    mask-position: center;
    transition: background-color 200ms var(--v-ease);
}

body.v-page .leaflet-control-layers-toggle:hover {
    background-color: var(--v-ink);
}

/* The expanded panel is plain text and inputs on Leaflet's own white; the
   surface is themed above, so the controls inside have to be too. */
body.v-page .leaflet-control-layers-expanded label,
body.v-page .leaflet-control-layers-list {
    color: var(--v-ink);
}

body.v-page .leaflet-control-layers-separator {
    border-top-color: var(--v-surface-highest);
}

body.v-page .leaflet-control-layers input[type="radio"],
body.v-page .leaflet-control-layers input[type="checkbox"] {
    accent-color: var(--v-accent);
}

/* -----------------------------------------------------------------------------
   LOCATE CONTROL
   leaflet.locatecontrol ships its arrow as a background-image data URI with the
   fill baked in: black when idle, #2074b6 when active, #fc8428 when following.
   A baked fill cannot follow a theme, so the idle arrow was near-invisible on
   the dark control and the two states were off-palette.

   Swapped for the standard my-location crosshair, drawn as a MASK so the colour
   comes from background-color and therefore from the tokens — which is the only
   way a data-URI glyph can respond to light and dark.
   -------------------------------------------------------------------------- */

body.v-page .leaflet-control-locate a .leaflet-control-locate-location-arrow,
body.v-page .leaflet-control-locate.active a .leaflet-control-locate-location-arrow,
body.v-page .leaflet-control-locate.following a .leaflet-control-locate-location-arrow {
    background-image: none;
    background-color: var(--v-ink-muted);
    -webkit-mask-image: url("data:image/svg+xml;charset=UTF-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 8c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4zm8.94 3c-.46-4.17-3.77-7.48-7.94-7.94V1h-2v2.06C6.83 3.52 3.52 6.83 3.06 11H1v2h2.06c.46 4.17 3.77 7.48 7.94 7.94V23h2v-2.06c4.17-.46 7.48-3.77 7.94-7.94H23v-2h-2.06zM12 19c-3.87 0-7-3.13-7-7s3.13-7 7-7 7 3.13 7 7-3.13 7-7 7z'/%3E%3C/svg%3E");
    mask-image: url("data:image/svg+xml;charset=UTF-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 8c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4zm8.94 3c-.46-4.17-3.77-7.48-7.94-7.94V1h-2v2.06C6.83 3.52 3.52 6.83 3.06 11H1v2h2.06c.46 4.17 3.77 7.48 7.94 7.94V23h2v-2.06c4.17-.46 7.48-3.77 7.94-7.94H23v-2h-2.06zM12 19c-3.87 0-7-3.13-7-7s3.13-7 7-7 7 3.13 7 7-3.13 7-7 7z'/%3E%3C/svg%3E");
    -webkit-mask-size: contain;
    mask-size: contain;
    -webkit-mask-repeat: no-repeat;
    mask-repeat: no-repeat;
    -webkit-mask-position: center;
    mask-position: center;
    transition: background-color 200ms var(--v-ease);
}

body.v-page .leaflet-control-locate a:hover .leaflet-control-locate-location-arrow {
    background-color: var(--v-ink);
}

/* Located, and following the position, are distinct states worth distinguishing:
   the accent for "found you", the warning tone for "actively tracking". */
body.v-page .leaflet-control-locate.active a .leaflet-control-locate-location-arrow {
    background-color: var(--v-accent);
}

body.v-page .leaflet-control-locate.following a .leaflet-control-locate-location-arrow {
    background-color: var(--v-warning);
}

/* The spinner is the same baked-fill problem. */
body.v-page .leaflet-control-locate a .leaflet-control-locate-spinner {
    background-color: var(--v-ink-subtle);
}

/* The accuracy circle and marker are drawn by the plugin in its own blue. */
body.v-page .leaflet-control-locate-circle {
    fill: var(--v-accent);
    stroke: var(--v-accent);
}

/* -----------------------------------------------------------------------------
   FULL-BLEED ON MOBILE
   A page cannot hide the browser's chrome, but the browser hides it itself as
   you scroll. Two things are needed to benefit:

     - viewport-fit=cover on the meta tag, so the page may paint into the notch
       and home-indicator areas (added there, not here)
     - dvh rather than vh: 100vh is the LARGE viewport and stays the height the
       page would have with the bar already hidden, so content sits underneath it
       until you scroll. 100dvh tracks the chrome as it retracts.

   With those, the layout genuinely grows into the space the browser gives back,
   which is the effect people mean by "expanding full screen" on a phone.
   -------------------------------------------------------------------------- */

body.v-page {
    /* Painting under the chrome is only safe if fixed furniture is inset out of
       the notch and the home indicator. */
    padding-top: env(safe-area-inset-top);
    padding-bottom: env(safe-area-inset-bottom);
    padding-left: env(safe-area-inset-left);
    padding-right: env(safe-area-inset-right);
    box-sizing: border-box;
}

/* The navbar is the top edge, so it absorbs the top inset itself rather than
   being pushed down by the body's. */
body.v-page .navbar {
    padding-top: max(env(safe-area-inset-top), 0px);
}

/* The mobile sheet reaches the bottom edge and must clear the home indicator,
   or its last control is under the gesture bar. */
body.v-page .mobile-bottom-sheet,
body.v-page .peek-sheet,
body.v-page #mobileBottomSheet {
    padding-bottom: max(env(safe-area-inset-bottom), 0px);
}

/* Leaflet's own controls sit against the edges too. */
body.v-page .leaflet-bottom {
    margin-bottom: env(safe-area-inset-bottom);
}

@supports (height: 100dvh) {
    body.v-page .full-height,
    body.v-page .map-container {
        height: 100dvh;
    }
}
