/*
 * EXT:airport_listing
 *
 * What this stylesheet is and is not
 * ----------------------------------
 * It is the components UIkit has none of – the map canvas, the pins, the flight
 * boards, the two-pane layout – and nothing else. Buttons, cards, labels,
 * alerts, tables, the grid and the form controls are UIkit's, used by their own
 * class names, so a project that re-themes UIkit re-themes this extension with
 * it and never has to fight a second set of opinions.
 *
 * Everything that could sensibly differ per project is a custom property on
 * .airportlisting. Overriding one of those is the supported way to change the
 * look; overriding a selector further down is not, and will be moved under you.
 *
 * The palette below is deliberately not UIkit's. The five size tiers need
 * colours that read as one ordered scale at eleven pixels across on a pale map,
 * and UIkit's semantic colours (primary, success, warning, danger) are five
 * unrelated hues that happen to exist. The scale here runs cool to warm with
 * rising chroma, so a mega hub is the loudest thing on the map at any zoom and
 * the tiers stay distinguishable for the common forms of colour blindness –
 * lightness carries the order, not hue alone.
 */

.airportlisting {
    /* ---- the size scale: local → mega hub ---- */
    --airport-size-1: #7c92a8;
    --airport-size-2: #3d8fb8;
    --airport-size-3: #1b9e8f;
    --airport-size-4: #e8a22b;
    --airport-size-5: #e4572e;

    /* ---- surfaces ---- */
    --airportlisting-surface: #ffffff;
    --airportlisting-surface-sunken: #f4f6f8;
    --airportlisting-surface-raised: #ffffff;
    --airportlisting-border: rgba(15, 32, 48, 0.12);
    --airportlisting-border-strong: rgba(15, 32, 48, 0.22);

    /* ---- ink ---- */
    --airportlisting-ink: #11212f;
    --airportlisting-ink-muted: #5b6b7a;
    --airportlisting-accent: #1b6ca8;

    /* ---- the map ---- */
    --airportlisting-map-water: #dfe8ef;
    --airportlisting-map-land: #f7f9fa;
    --airportlisting-map-border: #c6d3dd;
    --airportlisting-map-height: 100svh;

    /* ---- geometry ---- */
    --airportlisting-radius: 14px;
    --airportlisting-radius-small: 8px;
    --airportlisting-gap: 1rem;
    --airportlisting-shadow: 0 1px 2px rgba(15, 32, 48, 0.06), 0 8px 24px -12px rgba(15, 32, 48, 0.28);
    --airportlisting-shadow-raised: 0 2px 6px rgba(15, 32, 48, 0.1), 0 18px 40px -20px rgba(15, 32, 48, 0.4);

    /* A monospaced stack for the things that are codes, not words: IATA, gates,
       clock times. Tabular figures keep a column of times from dancing. */
    --airportlisting-font-mono: ui-monospace, "SF Mono", "Cascadia Mono", "Roboto Mono", Menlo, Consolas, monospace;

    color: var(--airportlisting-ink);
}

/*
 * Dark mode is opt-in through a class a project sets, and automatic only where
 * the page has not already decided. A site with its own dark theme sets
 * .airportlisting--dark (or re-declares the properties); a site with none gets
 * the media query. Both paths change nothing but the custom properties above.
 */
@media (prefers-color-scheme: dark) {
    .airportlisting:not(.airportlisting--light) {
        --airportlisting-surface: #121a21;
        --airportlisting-surface-sunken: #0d141a;
        --airportlisting-surface-raised: #1a242d;
        --airportlisting-border: rgba(235, 243, 250, 0.14);
        --airportlisting-border-strong: rgba(235, 243, 250, 0.26);
        --airportlisting-ink: #e8eef4;
        --airportlisting-ink-muted: #94a6b5;
        --airportlisting-accent: #5aa9e0;
        --airportlisting-map-water: #0f1920;
        --airportlisting-map-land: #1b262f;
        --airportlisting-map-border: #2c3b47;
        --airportlisting-shadow: 0 1px 2px rgba(0, 0, 0, 0.5), 0 8px 24px -12px rgba(0, 0, 0, 0.8);
    }
}

.airportlisting--dark {
    --airportlisting-surface: #121a21;
    --airportlisting-surface-sunken: #0d141a;
    --airportlisting-surface-raised: #1a242d;
    --airportlisting-border: rgba(235, 243, 250, 0.14);
    --airportlisting-border-strong: rgba(235, 243, 250, 0.26);
    --airportlisting-ink: #e8eef4;
    --airportlisting-ink-muted: #94a6b5;
    --airportlisting-accent: #5aa9e0;
    --airportlisting-map-water: #0f1920;
    --airportlisting-map-land: #1b262f;
    --airportlisting-map-border: #2c3b47;
}

/* =====================================================================
 * The map canvas
 * ================================================================== */

.airportlisting-map {
    position: relative;
    border-radius: var(--airportlisting-radius);
    overflow: hidden;
    background: var(--airportlisting-map-water);
}

.airportlisting-map__canvas {
    width: 100%;
    height: var(--airportlisting-map-height);
    background: var(--airportlisting-map-water);
}

/*
 * Undo the page's own image reset inside the map. This is not optional.
 *
 * Nearly every site carries some variant of
 *
 *     canvas, img, svg, video { max-width: 100%; height: auto }
 *
 * and it is correct everywhere except here. Leaflet's panes are absolutely
 * positioned and have no width of their own, so "100%" inside one resolves to
 * zero – and a canvas with a max-width of zero is a canvas nobody can see.
 *
 * The symptom is the hard part: the map container keeps its background, the
 * zoom control and any DOM markers still show, and only the things Leaflet
 * paints onto a canvas disappear. On this extension that is the entire vector
 * basemap and every single airport pin, which reads as "the map does not load"
 * rather than as a stylesheet conflict. Raster tiles vanish the same way,
 * because they are <img> elements in the same kind of pane.
 *
 * Specificity does the work here, so no "!important" is needed: two classes and
 * a type selector beat a bare type selector whatever order the files load in.
 */
.airportlisting-map .leaflet-container canvas,
.airportlisting-map .leaflet-container img,
.airportlisting-map .leaflet-container svg,
.airportlisting-map .leaflet-container video {
    max-width: none;
    min-width: 0;
    max-height: none;
}

/*
 * Leaflet sizes its tiles and panes in pixels and expects the box model it
 * assumed when it did. A global "box-sizing: border-box" is harmless on a
 * canvas with no border, but a tile that inherits padding or a border from a
 * site-wide image rule tears seams between the tiles.
 */
.airportlisting-map .leaflet-container img.leaflet-tile {
    padding: 0;
    border: 0;
}

/*
 * The four heights an editor can pick. "svh" and not "vh": on a phone, "vh" is
 * measured against the viewport with the browser chrome retracted, so a map
 * sized in vh is taller than the screen until the visitor scrolls – which on a
 * full-screen map they cannot, because the map eats the scroll.
 */
.airportlisting-map--screen .airportlisting-map__canvas { height: 100svh; }
.airportlisting-map--large .airportlisting-map__canvas { height: 70svh; }
.airportlisting-map--medium .airportlisting-map__canvas { height: 520px; }
.airportlisting-map--small .airportlisting-map__canvas { height: 340px; }

/* A full-bleed map breaks out of the page's content column. */
.airportlisting-element--map .airportlisting-map--screen {
    border-radius: 0;
    margin-inline: calc(50% - 50vw);
    width: 100vw;
}

.airportlisting-map__placeholder {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    height: 100%;
    color: var(--airportlisting-ink-muted);
}

/*
 * The vector basemap is not styled here, and cannot be.
 *
 * The map runs with preferCanvas, so the country outlines are painted onto a
 * canvas: there are no DOM nodes for a selector to match, and a rule like
 * ".airportlisting-country { fill: … }" would be dead text that looks like it
 * works. airport-map.js reads the two custom properties below off the
 * container and hands the resolved values to the renderer instead – so the
 * palette still lives here, where a project changes it, and the theme switch
 * further up still reaches the land and the sea.
 *
 *   --airportlisting-map-land     the landmass
 *   --airportlisting-map-border   the country outlines
 *   --airportlisting-map-water    the background behind both, which IS CSS,
 *                                 because it is the container's own background
 */

/*
 * Raster tile filters.
 *
 * Three schemes in the TypoScript are OpenStreetMap tiles with one of these
 * over them – the cheap way to a house look: no second provider, no account, no
 * extra request. The filter is on the tile pane only, so the pins keep their
 * real colours; Leaflet draws them in a pane of their own.
 */
.airportlisting-map--filter-muted .leaflet-tile-pane { filter: saturate(0.35) contrast(0.94) brightness(1.04); }
.airportlisting-map--filter-grayscale .leaflet-tile-pane { filter: grayscale(1) contrast(0.92) brightness(1.06); }
.airportlisting-map--filter-dark .leaflet-tile-pane { filter: invert(1) hue-rotate(180deg) saturate(0.6) brightness(0.86) contrast(1.1); }

/* =====================================================================
 * Pins
 * ================================================================== */

/*
 * A pin is a Leaflet circleMarker on a canvas renderer, not a DOM node. Four
 * thousand DOM nodes with a CSS transition each is a page that stutters on
 * every pan; four thousand canvas circles is one paint.
 *
 * The consequence for this stylesheet: there is nothing here to style the pins
 * with. Their radius and colour are read off the configuration and the custom
 * properties above by airport-map.js. These rules are for the two things that
 * *are* DOM – the popup and the cluster bubbles.
 */

.airportlisting-cluster {
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    color: #fff;
    background: var(--airportlisting-accent);
    box-shadow: 0 0 0 4px color-mix(in srgb, var(--airportlisting-accent) 28%, transparent);
    transition: transform 120ms ease-out;
}

.airportlisting-cluster:hover {
    transform: scale(1.08);
}

.airportlisting-cluster--small { width: 34px; height: 34px; font-size: 0.75rem; }
.airportlisting-cluster--medium { width: 42px; height: 42px; font-size: 0.8rem; }
.airportlisting-cluster--large { width: 52px; height: 52px; font-size: 0.875rem; }

/*
 * The top tier's pins, which are DOM elements rather than canvas circles.
 *
 * Only the forty-nine largest airports are drawn this way, and only so that a
 * cluster bubble can never cover them – see createMarker() in airport-map.js.
 * The shape has to match what the canvas draws for every other tier exactly,
 * or the two kinds of pin would read as two kinds of thing: same diameter, same
 * fill, same white ring.
 *
 * The size and colour arrive as custom properties from the script, which took
 * them from the tier's own --airport-size-* property. One palette, two
 * renderers.
 */
.airportlisting-pin-dot {
    display: block;
    width: var(--pin-size);
    height: var(--pin-size);
    border-radius: 50%;
    background: var(--pin-color);
    border: 1px solid rgba(255, 255, 255, 0.9);
    box-sizing: border-box;
    transition: transform 120ms ease-out;
    /* Grows from the centre, so a hovered pin stays on its airport. */
    transform-origin: 50% 50%;
}

.airportlisting-pin-dot--disrupted {
    border-color: #e4572e;
    border-width: 2px;
}

/* The warning badge on a pin whose airport has an active disruption notice. */
.airportlisting-pin-badge {
    width: 14px;
    height: 14px;
    border-radius: 50%;
    background: #e4572e;
    border: 2px solid var(--airportlisting-surface);
    box-shadow: 0 0 0 2px rgba(228, 87, 46, 0.35);
}

/* =====================================================================
 * The hover card
 * ================================================================== */

.leaflet-popup-content-wrapper:has(.airportlisting-pin) {
    border-radius: var(--airportlisting-radius);
    padding: 0;
    background: var(--airportlisting-surface-raised);
    color: var(--airportlisting-ink);
    box-shadow: var(--airportlisting-shadow-raised);
}

.leaflet-popup-content:has(.airportlisting-pin) {
    margin: 0;
    width: 320px !important;
}

.leaflet-popup-tip { background: var(--airportlisting-surface-raised); }

.airportlisting-pin {
    padding: 0.9rem 1rem 1rem;
    font-size: 0.875rem;
    line-height: 1.45;
}

.airportlisting-pin__head {
    display: flex;
    align-items: flex-start;
    gap: 0.65rem;
    padding-bottom: 0.6rem;
    border-bottom: 1px solid var(--airportlisting-border);
}

.airportlisting-pin__code,
.airportlisting-teaser__code,
.airportlisting-detail__code {
    font-family: var(--airportlisting-font-mono);
    font-weight: 700;
    letter-spacing: 0.04em;
    color: #fff;
    background: var(--airportlisting-accent);
    border-radius: var(--airportlisting-radius-small);
    padding: 0.25em 0.5em;
    line-height: 1.2;
}

.airportlisting-pin__title { display: flex; flex-direction: column; min-width: 0; }
.airportlisting-pin__name { font-weight: 600; }
.airportlisting-pin__place,
.airportlisting-pin__simulated { color: var(--airportlisting-ink-muted); font-size: 0.8125rem; }
.airportlisting-pin__action { margin: 0.75rem 0 0; }

/* =====================================================================
 * The facts block – shared by the hover card and the teasers
 * ================================================================== */

.airportlisting-facts { margin-top: 0.6rem; }

.airportlisting-facts__boards {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0.75rem;
}

/* One column once the teaser is narrower than two readable columns. The
   breakpoint is on the container, not the viewport: the same teaser sits in a
   full-width row and in a quarter-width card. */
@container (max-width: 22rem) {
    .airportlisting-facts__boards { grid-template-columns: minmax(0, 1fr); }
}

.airportlisting-facts__board-title {
    display: flex;
    align-items: center;
    gap: 0.3rem;
    font-size: 0.6875rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--airportlisting-ink-muted);
}

.airportlisting-facts__list { margin: 0.25rem 0 0; font-size: 0.8125rem; }

.airportlisting-facts__flight {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto;
    gap: 0.4rem;
    align-items: baseline;
}

.airportlisting-facts__time {
    font-family: var(--airportlisting-font-mono);
    font-variant-numeric: tabular-nums;
    font-weight: 600;
}

.airportlisting-facts__place {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.airportlisting-facts__number {
    font-family: var(--airportlisting-font-mono);
    font-size: 0.75rem;
    color: var(--airportlisting-ink-muted);
}

.airportlisting-facts__passengers {
    display: flex;
    align-items: center;
    gap: 0.35rem;
    margin-top: 0.5rem;
}

/* =====================================================================
 * Teasers
 * ================================================================== */

.airportlisting-teaser {
    container-type: inline-size;
    position: relative;
    border-radius: var(--airportlisting-radius);
    background: var(--airportlisting-surface);
    transition: box-shadow 140ms ease-out, transform 140ms ease-out;
}

.airportlisting-teaser__link {
    display: block;
    color: inherit;
    text-decoration: none;
}

.airportlisting-teaser__link:hover,
.airportlisting-teaser__link:focus-visible { color: inherit; text-decoration: none; }

/* The row the map raises when its pin is hovered, and the other way round. */
.airportlisting-teaser--active,
.airportlisting-teaser:hover {
    box-shadow: var(--airportlisting-shadow);
}

.airportlisting-teaser--active {
    outline: 2px solid var(--airportlisting-accent);
    outline-offset: 2px;
}

/* ---- row ---- */

.airportlisting-teaser--row { border: 1px solid var(--airportlisting-border); }

.airportlisting-teaser--row .airportlisting-teaser__link {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    gap: 0.85rem;
    padding: 0.9rem 1rem;
}

.airportlisting-teaser--row .airportlisting-teaser__code { align-self: start; font-size: 0.9375rem; }

.airportlisting-teaser__heading { display: block; }
.airportlisting-teaser__name { font-weight: 600; display: block; }
.airportlisting-teaser__subtitle,
.airportlisting-teaser__place {
    display: block;
    color: var(--airportlisting-ink-muted);
    font-size: 0.8125rem;
}

.airportlisting-teaser__place { display: flex; align-items: center; gap: 0.3rem; }

/* ---- card ---- */

.airportlisting-teaser--card { overflow: hidden; }

.airportlisting-teaser--card .airportlisting-teaser__media {
    display: block;
    position: relative;
    aspect-ratio: 16 / 9;
    background: var(--airportlisting-surface-sunken);
}

.airportlisting-teaser__image { width: 100%; height: 100%; object-fit: cover; display: block; }

/*
 * No image: the IATA code on a tinted plate, coloured by the size tier. Better
 * than a grey rectangle, and better than a stock photo of an aeroplane that is
 * not this airport's.
 */
.airportlisting-teaser__placeholder {
    display: flex;
    align-items: center;
    justify-content: center;
    height: 100%;
    font-family: var(--airportlisting-font-mono);
    font-size: clamp(2rem, 14cqw, 3.5rem);
    font-weight: 700;
    letter-spacing: 0.08em;
    color: color-mix(in srgb, var(--teaser-color, var(--airport-size-1)) 70%, var(--airportlisting-ink));
    background: color-mix(in srgb, var(--teaser-color, var(--airport-size-1)) 14%, var(--airportlisting-surface-sunken));
}

.airportlisting-teaser--size-1 { --teaser-color: var(--airport-size-1); }
.airportlisting-teaser--size-2 { --teaser-color: var(--airport-size-2); }
.airportlisting-teaser--size-3 { --teaser-color: var(--airport-size-3); }
.airportlisting-teaser--size-4 { --teaser-color: var(--airport-size-4); }
.airportlisting-teaser--size-5 { --teaser-color: var(--airport-size-5); }

.airportlisting-teaser__badge {
    position: absolute;
    inset-block-start: 0.6rem;
    inset-inline-start: 0.6rem;
    font-family: var(--airportlisting-font-mono);
    font-weight: 700;
    font-size: 0.75rem;
    letter-spacing: 0.04em;
    color: #fff;
    background: color-mix(in srgb, var(--teaser-color) 85%, #000);
    border-radius: var(--airportlisting-radius-small);
    padding: 0.2em 0.45em;
}

.airportlisting-teaser--card .airportlisting-teaser__body { display: block; padding: 0.9rem 1rem 1rem; }

/* ---- table ---- */

.airportlisting-list__table-scroll { overflow-x: auto; }
.airportlisting-list__table { margin: 0; }

.airportlisting-teaser--table .airportlisting-teaser__code-link {
    font-family: var(--airportlisting-font-mono);
    font-weight: 700;
    /* Above the stretched overlay, so a middle-click still opens a new tab. */
    position: relative;
    z-index: 1;
}

/*
 * The whole table row is a click target, through one link stretched over it.
 *
 * A row cannot be wrapped in an <a>, so the airport's name carries the link and
 * this pseudo-element extends its hit area across the row. "position: relative"
 * on a <tr> is what makes the row the containing block; every engine this
 * extension targets honours it.
 *
 * The trade-off, stated because it is a real one: an overlay over a row means
 * text in that row cannot be selected with the mouse. For a listing whose
 * purpose is to be navigated that is the right way round, and the two other
 * teaser layouts – which are not tables – are unaffected.
 */
.airportlisting-teaser--table {
    position: relative;
    cursor: pointer;
}

.airportlisting-teaser__stretched::after {
    content: '';
    position: absolute;
    inset: 0;
}

/* The focus ring has to follow the link, not the invisible overlay. */
.airportlisting-teaser__stretched:focus-visible {
    outline: 2px solid var(--airportlisting-accent);
    outline-offset: 2px;
}

.airportlisting-teaser--table:has(.airportlisting-teaser__stretched:focus-visible) {
    background: color-mix(in srgb, var(--airportlisting-accent) 8%, transparent);
}

.airportlisting-teaser__place-inline { color: var(--airportlisting-ink-muted); margin-inline-start: 0.35rem; }

/* A 4 px rail in the tier colour, so the table keeps the scale the map uses. */
.airportlisting-teaser--table td:first-child { box-shadow: inset 4px 0 0 var(--teaser-color); }

/* =====================================================================
 * Map + list
 * ================================================================== */

/*
 * The one piece of layout UIkit has no component for: two panes, each the full
 * height of the screen, each scrolling on its own.
 *
 * The grid is declared at every width and the single-column case is the default,
 * so a phone gets the list with the map above it and no amount of resizing
 * leaves a half-width pane stranded.
 */
.airportlisting-maplist {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 0;
    margin-inline: calc(50% - 50vw);
    width: 100vw;
    background: var(--airportlisting-surface-sunken);
}

@media (min-width: 960px) {
    .airportlisting-maplist {
        grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
        height: 100svh;
    }

    /* Which half the map takes. Order, not placement: the panel stays first in
       the document, so a keyboard and a screen reader reach the filter before
       the map whichever side it is on. */
    .airportlisting-maplist--map-left .airportlisting-maplist__map { order: -1; }
}

.airportlisting-maplist__panel {
    display: flex;
    flex-direction: column;
    min-height: 0;
    background: var(--airportlisting-surface);
    border-inline-end: 1px solid var(--airportlisting-border);
}

.airportlisting-maplist--map-left .airportlisting-maplist__panel {
    border-inline: 1px solid var(--airportlisting-border) none;
}

.airportlisting-maplist__header,
.airportlisting-maplist__filter,
.airportlisting-maplist__viewport {
    flex: 0 0 auto;
    padding: 0.75rem 1.25rem;
    border-bottom: 1px solid var(--airportlisting-border);
}

.airportlisting-maplist__viewport {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    flex-wrap: wrap;
}

.airportlisting-maplist__viewport-count { flex: 1 1 auto; }

/* The scrolling half. "overscroll-behavior: contain" is what keeps a flick at
   the bottom of the list from scrolling the page behind it. */
.airportlisting-maplist__results {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
    padding: 1rem 1.25rem 2rem;
    scrollbar-gutter: stable;
}

.airportlisting-maplist__map { min-height: 0; }

.airportlisting-maplist__map .airportlisting-map,
.airportlisting-maplist__map .airportlisting-map__canvas {
    height: 100%;
    border-radius: 0;
}

/* Below the breakpoint the map is a band above the list rather than half a
   screen – half of a phone screen is not a map, it is a thumbnail. */
@media (max-width: 959px) {
    .airportlisting-maplist__map .airportlisting-map__canvas { height: 45svh; }
    .airportlisting-maplist { height: auto; }
}

.airportlisting-list__rows > * + * { margin-top: 0.6rem; }

.airportlisting-list__empty { margin: 0; }

/* =====================================================================
 * The legend
 * ================================================================== */

.airportlisting-legend {
    position: absolute;
    inset-block-end: 1.25rem;
    inset-inline-start: 1.25rem;
    z-index: 500; /* over Leaflet's panes, under its controls at 1000 */
    background: color-mix(in srgb, var(--airportlisting-surface) 92%, transparent);
    backdrop-filter: blur(8px);
    border: 1px solid var(--airportlisting-border);
    border-radius: var(--airportlisting-radius);
    box-shadow: var(--airportlisting-shadow);
    padding: 0.5rem 0.75rem;
    max-width: min(18rem, calc(100% - 2.5rem));
}

.airportlisting-legend__toggle { font-size: 0.8125rem; font-weight: 600; }
.airportlisting-legend__hint { margin: 0.35rem 0 0.5rem; }
.airportlisting-legend__list { font-size: 0.8125rem; }

.airportlisting-legend__item { display: flex; align-items: center; gap: 0.5rem; }

.airportlisting-legend__dot {
    flex: 0 0 auto;
    /* The same radius the map draws with, doubled into a diameter. */
    width: calc(var(--dot-size) * 2);
    height: calc(var(--dot-size) * 2);
    border-radius: 50%;
    background: var(--dot-color);
    box-shadow: 0 0 0 2px color-mix(in srgb, var(--dot-color) 30%, transparent);
}

.airportlisting-size-dot {
    display: inline-block;
    width: 0.65em;
    height: 0.65em;
    border-radius: 50%;
    background: var(--dot-color);
    margin-inline-end: 0.35em;
}

/* =====================================================================
 * Flight boards and the timetable
 * ================================================================== */

.airportlisting-board__title,
.airportlisting-disruptions__title {
    display: flex;
    align-items: center;
    gap: 0.4rem;
}

.airportlisting-board__scroll,
.airportlisting-timetable__scroll { overflow-x: auto; }

.airportlisting-board__table,
.airportlisting-timetable__table { margin: 0; }

.airportlisting-board__col-time,
.airportlisting-timetable__col-direction { width: 1%; white-space: nowrap; }

.airportlisting-flight__time,
.airportlisting-flight__code,
.airportlisting-flight__terminal,
.airportlisting-flight__gate,
.airportlisting-facts__time {
    font-family: var(--airportlisting-font-mono);
    font-variant-numeric: tabular-nums;
}

.airportlisting-flight__time { white-space: nowrap; }
.airportlisting-flight__scheduled { font-weight: 600; }

.airportlisting-flight__estimated {
    display: inline-block;
    margin-inline-start: 0.35rem;
    text-decoration: none;
    font-weight: 700;
    color: #b45309;
}

.airportlisting-flight__scheduled del,
.airportlisting-flight del { color: var(--airportlisting-ink-muted); }

/*
 * "+1" beside a time that is not today.
 *
 * Quiet on purpose: it qualifies the number next to it rather than competing
 * with it, and on most boards it appears on none of the rows. Raised and
 * smaller, the way a footnote mark reads, so the eye takes it as "there is
 * something about this time" and not as part of the time.
 */
.airportlisting-flight__nextday {
    margin-inline-start: 0.2rem;
    font-size: 0.7em;
    font-weight: 700;
    vertical-align: super;
    line-height: 1;
    color: var(--airportlisting-ink-muted);
    cursor: help;
}

/* On a teaser the time is already small; the marker must not shrink further. */
.airportlisting-teaser__time .airportlisting-flight__nextday,
.airportlisting-facts__time + .airportlisting-flight__nextday,
.airportlisting-teaser__time + .airportlisting-flight__nextday {
    font-size: 0.75em;
}

.airportlisting-flight__airline,
.airportlisting-flight__place-code,
.airportlisting-flight__aircraft,
.airportlisting-flight__route-label { display: block; }

.airportlisting-flight__gate {
    display: inline-block;
    border: 1px solid var(--airportlisting-border-strong);
    border-radius: var(--airportlisting-radius-small);
    padding: 0.05em 0.4em;
    margin-inline-start: 0.25rem;
}

/* A cancelled flight is struck through, not hidden: a visitor looking for a
   flight that is not there needs to be told it was cancelled. */
.airportlisting-flight--cancelled { opacity: 0.6; }
.airportlisting-flight--cancelled .airportlisting-flight__time,
.airportlisting-flight--cancelled .airportlisting-flight__route { text-decoration: line-through; }

.airportlisting-flight--departure .airportlisting-flight__direction-icon { color: var(--airport-size-5); }
.airportlisting-flight--arrival .airportlisting-flight__direction-icon { color: var(--airport-size-3); }

/*
 * "As of 14:32" — how current the flight data is.
 *
 * Quiet by default, because on a well-synced site it is a detail. It stops
 * being a detail the moment the data goes stale, which is what the modifier is
 * for: the same line in a warning colour, with a sentence saying so.
 */
.airportlisting-dataage {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 0.4rem;
    margin: 0 0 0.75rem;
    font-size: 0.8125rem;
    color: var(--airportlisting-ink-muted);
    font-variant-numeric: tabular-nums;
}

.airportlisting-dataage--stale {
    color: #b45309;
}

.airportlisting-dataage--stale .airportlisting-dataage__warning {
    font-weight: 600;
}

/*
 * "No live data available".
 *
 * A plain statement, not an alert: having no flight feed is the normal state
 * for all but a handful of airports, and a warning box on three and a half
 * thousand pages is noise rather than information.
 */
.airportlisting-nodata {
    display: flex;
    align-items: flex-start;
    gap: 0.6rem;
    padding: 0.9rem 1rem;
    border: 1px dashed var(--airportlisting-border-strong);
    border-radius: var(--airportlisting-radius);
    background: var(--airportlisting-surface-sunken);
    color: var(--airportlisting-ink-muted);
}

.airportlisting-nodata__icon { flex: 0 0 auto; margin-top: 0.15rem; }
.airportlisting-nodata__text { display: flex; flex-direction: column; gap: 0.15rem; }
.airportlisting-nodata__title { color: var(--airportlisting-ink); }
.airportlisting-nodata__hint { font-size: 0.8125rem; }

/* The one-line form, on a teaser. */
.airportlisting-facts__nodata {
    display: flex;
    align-items: center;
    gap: 0.35rem;
    margin-top: 0.5rem;
}

.airportlisting-timetable__filter { margin-bottom: 1rem; }
.airportlisting-timetable__airlines { min-height: 6rem; }

.airportlisting-timetable__loading {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 1rem 0;
    color: var(--airportlisting-ink-muted);
}

/* While a fragment is being fetched the old one stays, dimmed. A table that
   empties and refills is a page that jumps under the reader's finger. */
.airportlisting-timetable--busy .airportlisting-timetable__table,
.airportlisting-list--busy { opacity: 0.45; transition: opacity 120ms ease-out; }

/* =====================================================================
 * Detail view
 * ================================================================== */

.airportlisting-detail__header { margin-bottom: 1.5rem; }

.airportlisting-detail__hero {
    border-radius: var(--airportlisting-radius);
    overflow: hidden;
    margin-bottom: 1.25rem;
}

.airportlisting-detail__hero-image { width: 100%; height: auto; display: block; }

.airportlisting-detail__code {
    display: inline-block;
    font-size: 1rem;
    margin: 0 0 0.5rem;
    background: color-mix(in srgb, var(--airportlisting-accent) 14%, transparent);
    color: var(--airportlisting-accent);
}

.airportlisting-detail__name { margin: 0; }
.airportlisting-detail__subtitle { margin: 0.25rem 0 0; color: var(--airportlisting-ink-muted); }

.airportlisting-detail__place {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    margin: 0.5rem 0 0;
    color: var(--airportlisting-ink-muted);
}

.airportlisting-detail__boards { margin-block: 2rem; }
.airportlisting-detail__timetable { margin-block-start: 2.5rem; }

.airportlisting-detail__aside > * + * { margin-top: 2rem; }

.airportlisting-detail__map .airportlisting-map__canvas { height: 380px; }

.airportlisting-facts-list__row {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.4fr);
    gap: 0.5rem;
    padding-block: 0.4rem;
}

.airportlisting-facts-list__row dt { color: var(--airportlisting-ink-muted); font-weight: 400; }
.airportlisting-facts-list__row dd { margin: 0; }
.airportlisting-facts-list__code { font-family: var(--airportlisting-font-mono); }

.airportlisting-passengers__total {
    font-size: clamp(1.75rem, 5vw, 2.5rem);
    font-weight: 700;
    line-height: 1.1;
    margin: 0;
    font-variant-numeric: tabular-nums;
}

.airportlisting-passengers__split { margin-top: 0.5rem; font-variant-numeric: tabular-nums; }
.airportlisting-passengers__note { margin: 0; }

.airportlisting-localtime { font-family: var(--airportlisting-font-mono); font-variant-numeric: tabular-nums; }

.airportlisting-category + .airportlisting-category { margin-inline-start: 0.25rem; }

/* =====================================================================
 * Filter
 * ================================================================== */

/*
 * Live filtering.
 *
 * The submit button is hidden only once the script has taken over, and the
 * script signals that by putting "--live" on the form. Hiding it in the
 * template instead would leave a visitor without JavaScript – or one whose
 * filter request just failed – with a form they cannot send.
 */
.airportlisting-filter__busy {
    display: none;
    align-items: center;
    gap: 0.4rem;
    color: var(--airportlisting-ink-muted);
    font-size: 0.8125rem;
}

.airportlisting-filter--live .airportlisting-filter__submit {
    display: none;
}

.airportlisting-filter--live.airportlisting-filter--busy .airportlisting-filter__busy {
    display: inline-flex;
}

/*
 * While a filter request is in flight the old result stays, dimmed. A list that
 * empties and refills on every keystroke is a page that flickers.
 */
.airportlisting-filter--busy ~ * .airportlisting-list,
.airportlisting-maplist--busy .airportlisting-list {
    opacity: 0.55;
    transition: opacity 120ms ease-out;
}

.airportlisting-filter__categories-label {
    display: block;
    margin-bottom: 0.25rem;
}

.airportlisting-filter__category-list {
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem 0.9rem;
    margin: 0;
}

.airportlisting-filter__category-list--level-1,
.airportlisting-filter__category-list--level-2 {
    flex-basis: 100%;
    margin-inline-start: 1.25rem;
}

.airportlisting-filter__category-label {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    cursor: pointer;
}

/* =====================================================================
 * Motion
 * ================================================================== */

@media (prefers-reduced-motion: reduce) {
    .airportlisting *,
    .airportlisting *::before,
    .airportlisting *::after {
        transition-duration: 0.01ms !important;
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        scroll-behavior: auto !important;
    }
}
