/* Card layout, following the gliderincidents pattern: rounded surface, accent
   bar on the left edge, list capped to a readable column. */

/* Where the fixed navigation is, since three different things have to clear it.
   nav.css puts the bar at the bottom on a phone and at the top from 800px up.
   `--nav-top-h` is how much it covers at the top of the window, and 0 while the
   bar is at the bottom. */
:root {
    --nav-h: calc(60px + env(safe-area-inset-bottom));
    --nav-top-h: 0px;
}

@media (min-width: 800px) {
    :root { --nav-top-h: 60px; }
}

/* The top offset has to be part of this shorthand, not a separate override in
   nav.css: that override was written as a `.view` rule in a media query, which
   carries no extra specificity, so this shorthand — later in the cascade — quietly
   beat it. Every view spent months starting 60px underneath the desktop bar. */
.view {
    padding: calc(var(--nav-top-h) + 1rem) 1rem 6rem;
    max-width: 800px;
    margin: 0 auto;
}

@media (min-width: 800px) {
    /* No bottom bar left to leave room for. */
    .view { padding-bottom: 1rem; }
}

.view[hidden] {
    display: none;
}

.view-header {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.75rem;
    margin-bottom: 1rem;
}

.view-title {
    font-size: 1.375rem;
    font-weight: 600;
}

.view-meta {
    font-size: 0.8125rem;
    color: var(--text-secondary);
}

.card {
    position: relative;
    background-color: var(--card-bg);
    border: 1px solid var(--border-color);
    border-radius: var(--radius);
    padding: 0.75rem 0.875rem 0.875rem 1rem;
    margin-bottom: var(--gap);
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
    overflow: hidden;
}

.card::before {
    content: '';
    position: absolute;
    left: 0;
    top: 0;
    bottom: 0;
    width: 4px;
    background: var(--secondary-accent);
}

.card.accent-good::before { background: var(--good); }
.card.accent-usable::before { background: var(--usable); }
.card.accent-marginal::before { background: var(--marginal); }
.card.accent-poor::before { background: var(--poor); }
.card.accent-muted::before { background: var(--tertiary-accent); }

.card-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.75rem;
    margin-bottom: 0.5rem;
}

.card-title {
    font-size: 1.0625rem;
    font-weight: 600;
}

.card-subtitle {
    font-size: 0.8125rem;
    color: var(--text-secondary);
    white-space: nowrap;
}

.card-note {
    font-size: 0.875rem;
    color: var(--text-secondary);
    margin-top: 0.5rem;
}

/* Key/value grid used by the weather and fleet cards. */
.facts {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(7.5rem, 1fr));
    gap: 0.5rem 0.75rem;
}

.fact-label {
    font-size: 0.6875rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-secondary);
}

.fact-value {
    font-size: 1rem;
    font-weight: 600;
}

.chips {
    display: flex;
    flex-wrap: wrap;
    gap: 0.375rem;
    margin-top: 0.75rem;
}

.chip {
    font-size: 0.75rem;
    padding: 0.1875rem 0.5rem;
    border-radius: 999px;
    background: var(--secondary-bg);
    border: 1px solid var(--border-color);
    color: var(--text-secondary);
}

.chip.warn {
    border-color: var(--marginal);
    color: var(--marginal);
}

.chip.good {
    border-color: var(--good);
    color: var(--good);
}

.report {
    white-space: pre-wrap;
    font-size: 0.9375rem;
    line-height: 1.55;
}

details.report-wrap summary {
    cursor: pointer;
    font-size: 0.875rem;
    color: var(--primary-accent);
    list-style: none;
    padding: 0.25rem 0;
}

details.report-wrap summary::-webkit-details-marker {
    display: none;
}

.button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    padding: 0.5rem 0.9rem;
    border: 1px solid var(--border-color);
    border-radius: 999px;
    background: var(--secondary-bg);
    color: var(--text-primary);
    cursor: pointer;
}

.button:active {
    opacity: 0.7;
}

.button[disabled] {
    opacity: 0.5;
    cursor: default;
}

.button.primary {
    background: var(--primary-accent);
    border-color: var(--primary-accent);
    color: #fff;
    font-weight: 600;
}

.state {
    padding: 2rem 1rem;
    text-align: center;
    color: var(--text-secondary);
}

.state.error {
    color: var(--poor);
}

/* Sign-in gate */
.signin {
    min-height: 100dvh;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 1rem;
    padding: 2rem;
    text-align: center;
}

.signin h1 {
    font-size: 1.5rem;
}

.signin p {
    color: var(--text-secondary);
    max-width: 22rem;
}

.signin-logo {
    width: 96px;
    height: 96px;
    border-radius: 50%;
}

/* "Sign in with Google" button, per Google's branding guidelines (dark theme).
   The fill, stroke, text colour, padding and the four-colour G are prescribed —
   do not restyle them. */
.gsi-button {
    display: inline-flex;
    align-items: center;
    height: 40px;
    padding: 0 12px 0 12px;
    background-color: #131314;
    border: 1px solid #8E918F;
    border-radius: 4px;
    color: #E3E3E3;
    font-family: "Google Sans", Roboto, Arial, sans-serif;
    font-size: 14px;
    line-height: 20px;
    font-weight: 500;
    text-decoration: none;
    -webkit-tap-highlight-color: transparent;
}

.gsi-button:active {
    background-color: #1f1f20;
}

.gsi-icon {
    display: flex;
    align-items: center;
    height: 18px;
    margin-right: 10px;
}

.gsi-text {
    white-space: nowrap;
}

.settings-account {
    display: flex;
    align-items: center;
    gap: 0.75rem;
}

.settings-avatar {
    width: 40px;
    height: 40px;
    border-radius: 50%;
    flex-shrink: 0;
    background: var(--secondary-bg);
}

/* Fleet status dots */
.status-dot {
    display: inline-block;
    width: 0.5rem;
    height: 0.5rem;
    border-radius: 50%;
    margin-right: 0.375rem;
    vertical-align: middle;
    background: var(--tertiary-accent);
}

.status-flying { background: var(--good); }
.status-on_ground { background: var(--primary-accent); }
.status-recently_seen { background: var(--secondary-accent); }
.status-not_seen_today { background: var(--tertiary-accent); }

/* The ledger is a wide table, so it gets a wider column than the card views
   and tighter side padding on a phone — but never zero, or it collides with
   the screen edge. */
/* The ledger and the statistics get a wider column and less gutter than a card
   list wants. These are shorthands too, so they carry the nav offset as well —
   the same trap as `.view` itself. */
.view.view-flush {
    max-width: 1400px;
    padding: calc(var(--nav-top-h) + 0.5rem) 0.5rem 6rem;
}

@media (min-width: 700px) {
    .view.view-flush { padding: calc(var(--nav-top-h) + 1rem) 1rem 6rem; }
}

@media (min-width: 800px) {
    .view.view-flush { padding-bottom: 1rem; }
}

/* Segmented control, used for the theme picker. */
.segmented {
    display: flex;
    margin-top: 0.75rem;
    border: 1px solid var(--border-color);
    border-radius: 999px;
    overflow: hidden;
}

.segmented-option {
    flex: 1;
    padding: 0.4rem 0.5rem;
    background: transparent;
    border: none;
    border-right: 1px solid var(--border-color);
    color: var(--text-secondary);
    font-size: 0.875rem;
    cursor: pointer;
}

.segmented-option:last-child {
    border-right: none;
}

.segmented-option.active {
    background: var(--primary-accent);
    color: #fff;
    font-weight: 600;
}

/* Status group headings in the fleet view. */
.group-header {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin: 1.25rem 0 0.5rem;
}

.group-header:first-child {
    margin-top: 0;
}

.group-title {
    font-size: 0.75rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--text-secondary);
}



/* Tag chip, following the gliderincidents pattern: rounded rectangle with a
   tinted fill and a stronger border. Deliberately one neutral colour — the
   status is already named beside it, and tinting the count implied a second,
   unexplained meaning. */
.tag {
    display: inline-block;
    height: 1.75rem;
    line-height: 1.75rem;
    padding: 0 0.5rem;
    border-radius: 999px;
    min-width: 1.75rem;
    text-align: center;
    font-size: 0.75rem;
    font-weight: 600;
    white-space: nowrap;
    border: 1px solid;
    font-variant-numeric: tabular-nums;
    color: var(--text-primary);
    background-color: color-mix(in srgb, var(--tertiary-accent) 18%, transparent);
    border-color: var(--border-color);
}

/* ===== Fleet card ===== */

.card.fleet-card {
    padding: 0;
}

.fleet-main {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    padding: 0.7rem 0.875rem 0.7rem 1rem;
}

.fleet-photo {
    width: 92px;
    height: 69px;
    border-radius: 8px;
    object-fit: cover;
    flex-shrink: 0;
    background: var(--secondary-bg);
    border: 1px solid var(--border-color);
}

.fleet-photo-empty {
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0.25rem;
    text-align: center;
    font-size: 0.6875rem;
    line-height: 1.2;
    color: var(--text-secondary);
}

.fleet-info {
    flex: 1;
    min-width: 0;
}

.fleet-reg {
    font-size: 1.0625rem;
    font-weight: 600;
    letter-spacing: 0.01em;
}

.fleet-model {
    font-size: 0.8125rem;
    color: var(--text-secondary);
}

.fleet-status {
    text-align: right;
    flex-shrink: 0;
}

.fleet-status-line {
    font-size: 0.8125rem;
    color: var(--text-secondary);
    white-space: nowrap;
}

.fleet-status-detail {
    font-size: 0.75rem;
    color: var(--text-secondary);
    opacity: 0.75;
    white-space: nowrap;
    margin-top: 0.1rem;
}

.fleet-flights {
    font-size: 0.75rem;
    color: var(--primary-accent);
    white-space: nowrap;
    margin-top: 0.2rem;
    font-variant-numeric: tabular-nums;
}

.fleet-expandable .fleet-main {
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
}

.fleet-expandable .fleet-main:focus-visible {
    outline: 2px solid var(--primary-accent);
    outline-offset: -2px;
    border-radius: 12px;
}

.fleet-caret {
    display: inline-block;
    font-size: 0.7rem;
    line-height: 1;
    margin-top: 0.3rem;
    color: var(--text-secondary);
    transition: transform 0.15s ease;
}

.fleet-card.is-open .fleet-caret {
    transform: rotate(180deg);
}

/* The day's individual flights, revealed by tapping the card. */
.fleet-sorties {
    display: none;
    padding: 0.35rem 0.875rem 0.5rem 1rem;
    border-top: 1px solid var(--border-color);
    background: var(--section-bg);
}

.fleet-card.is-open .fleet-sorties {
    display: block;
}

.sortie {
    display: flex;
    align-items: baseline;
    gap: 0.6rem;
    padding: 0.22rem 0;
    font-size: 0.8125rem;
    font-variant-numeric: tabular-nums;
}

.sortie-nr {
    color: var(--text-secondary);
    min-width: 1.5rem;
}

.sortie-time {
    flex: 1;
}

.sortie-dur {
    color: var(--text-secondary);
}

.sortie-live .sortie-time,
.sortie-live .sortie-dur {
    color: var(--good);
}

/* Live figures, shown only while airborne. */
.fleet-live {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 0.25rem;
    padding: 0.55rem 0.875rem 0.65rem 1rem;
    border-top: 1px solid var(--border-color);
    background: color-mix(in srgb, var(--good) 7%, transparent);
}

.fleet-stat {
    text-align: center;
    min-width: 0;
}

.fleet-stat-value {
    font-size: 0.9375rem;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.fleet-stat-label {
    font-size: 0.625rem;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-secondary);
}

@media (max-width: 380px) {
    .fleet-photo { width: 72px; height: 54px; }
    .fleet-live { grid-template-columns: repeat(2, 1fr); gap: 0.4rem 0.25rem; }
}

.fleet-notes {
    font-size: 0.6875rem;
    color: var(--text-secondary);
    opacity: 0.8;
    margin-top: 0.1rem;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Diagnostic readout, shown only with ?debug in the URL. */
.debug-overlay {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 9999;
    padding: 0.35rem 0.5rem;
    background: rgba(0, 0, 0, 0.85);
    color: #7CFF9B;
    font: 11px/1.35 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    word-break: break-word;
    pointer-events: none;
}

/* ===== Flight log statistics ===== */

.stats-card .facts {
    margin-top: 0.6rem;
}

.stats-bars {
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
    margin-top: 0.7rem;
}

.stats-bar-row {
    display: grid;
    grid-template-columns: 6.5rem 1fr auto;
    align-items: center;
    gap: 0.5rem;
    font-size: 0.8125rem;
}

.stats-bar-label {
    color: var(--text-secondary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.stats-bar-track {
    height: 0.5rem;
    border-radius: 999px;
    background: var(--secondary-bg);
    overflow: hidden;
}

.stats-bar-fill {
    display: block;
    height: 100%;
    border-radius: 999px;
    background: var(--primary-accent);
}

.stats-bar-value {
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    color: var(--text-primary);
}

@media (max-width: 380px) {
    .stats-bar-row { grid-template-columns: 5rem 1fr auto; font-size: 0.75rem; }
}

/* Average flight time by month. The one chart here that is not a bar list: a
   time series is read along an axis, and the record is three years wide, so it
   scrolls rather than squeezing forty months onto a phone. */
.stats-month-readout {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.2rem 0.6rem;
    margin-top: 0.7rem;
}

.stats-month-readout-label {
    font-size: 0.8125rem;
    color: var(--text-secondary);
}

.stats-month-readout-value {
    font-size: 1rem;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}

/* Its own line whatever the width, so selecting a month cannot reflow the card
   by wrapping one reading and not the next. */
.stats-month-readout-detail {
    flex-basis: 100%;
    font-size: 0.75rem;
    color: var(--text-secondary);
    font-variant-numeric: tabular-nums;
}

.stats-months {
    margin-top: 0.6rem;
    overflow-x: auto;
    overflow-y: hidden;
    -webkit-overflow-scrolling: touch;
}

.stats-months-plot {
    display: flex;
    align-items: flex-end;
    gap: 4px;
    width: max-content;
    min-width: 100%;
    padding-bottom: 0.15rem;
}

.stats-month {
    flex: none;
    width: 1.5rem;
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
    padding: 0;
    background: none;
    border: 0;
    font: inherit;
    color: inherit;
    cursor: pointer;
}

.stats-month.is-empty { cursor: default; }

.stats-month-track {
    position: relative;
    height: 6rem;
    border-radius: 2px;
    background: var(--secondary-bg);
}

.stats-month.is-selected .stats-month-track { background: var(--border-color); }

/* A month with no flying has no average, which is a different thing from an
   average of zero — so its track is dimmed rather than given a bar of no height
   that would read as a very short month. */
.stats-month.is-empty .stats-month-track { opacity: 0.4; }

.stats-month-fill {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    border-radius: 2px 2px 0 0;
    background: var(--primary-accent);
}

.stats-month-median {
    position: absolute;
    left: 0;
    right: 0;
    height: 2px;
    background: var(--secondary-accent);
}

/* The count strip carries no track of its own — two stacked grey boxes read as
   one chart with a fault in it rather than as two scales. */
.stats-month-count {
    position: relative;
    height: 1.1rem;
}

.stats-month-count-fill {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    border-radius: 1px;
    background: var(--text-secondary);
    opacity: 0.5;
}

.stats-month-mon,
.stats-month-year {
    font-size: 0.625rem;
    line-height: 1.1;
    text-align: center;
    color: var(--text-secondary);
    font-variant-numeric: tabular-nums;
}

/* Wider than its column and deliberately allowed to spill: it is written once a
   year, so the neighbours it overhangs are empty. */
.stats-month-year {
    height: 0.7rem;
    white-space: nowrap;
    overflow: visible;
    font-weight: 600;
}

/* The underline belongs to the month number, not to the column: hung off the
   button it floated below the year row, which is empty for eleven months in
   twelve. A shadow rather than a border so selecting one adds no height. */
.stats-month.is-selected .stats-month-mon {
    color: var(--text-primary);
    font-weight: 600;
    box-shadow: 0 2px 0 var(--primary-accent);
}

.stats-records {
    display: flex;
    flex-direction: column;
    gap: 0.55rem;
    margin-top: 0.7rem;
}

.stats-record {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.75rem;
}

.stats-record-label {
    font-size: 0.8125rem;
    color: var(--text-secondary);
}

.stats-record-values {
    text-align: right;
    white-space: nowrap;
}

.stats-record-value {
    display: block;
    font-size: 1rem;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}

.stats-record-detail {
    display: block;
    font-size: 0.75rem;
    color: var(--text-secondary);
    font-variant-numeric: tabular-nums;
}

/* Loading state. A static word gives no sign of life on a slow connection, and
   the flight log pulls the whole log — so the placeholder animates. Reduced
   motion gets the plain text instead. */
.state.loading {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.6rem;
}

.state.loading::before {
    content: '';
    width: 1rem;
    height: 1rem;
    border-radius: 50%;
    border: 2px solid var(--border-color);
    border-top-color: var(--primary-accent);
    animation: dassu-spin 0.7s linear infinite;
}

@keyframes dassu-spin {
    to { transform: rotate(360deg); }
}

@media (prefers-reduced-motion: reduce) {
    .state.loading::before { animation: none; border-top-color: var(--border-color); }
}

/* ===== Fleet map ===== */

/* The map fills everything the navigation does not. The view's padding, width
   cap and header are all dropped in map mode — a satellite view is the content,
   not a card on a page. */

#view-flotte.map-mode {
    /* Not `padding: 0` — the top nav still has to be cleared, or the imagery
       and its zoom controls go behind the bar. */
    padding: var(--nav-top-h) 0 0;
    max-width: none;
}

#flotte-map {
    position: relative;
    height: calc(100dvh - var(--nav-h));
    background: var(--secondary-bg);
}

@media (min-width: 800px) {
    /* The navigation moves to the top at this width. */
    #flotte-map { height: calc(100dvh - var(--nav-top-h)); }
}

/* Leaflet measures its container, so it must have a size before init. */
#flotte-map .leaflet-container {
    height: 100%;
    width: 100%;
    background: var(--secondary-bg);
    font-family: inherit;
}

/* MapLibre takes the host element over — it adds `maplibregl-map` to `#flotte-map`
   itself rather than creating a child. So this must not restate a height: an
   earlier `height: 100%` here beat the `calc(100dvh - …)` above on specificity and
   resolved against an auto-height parent, which is a map exactly zero pixels tall.
   Only the things the map cannot know about itself belong here. */
#flotte-map.maplibregl-map {
    background: var(--secondary-bg);
    font-family: inherit;
}

/* **An aircraft the terrain is standing in front of.** The markers are DOM above
   the canvas, so the depth buffer that correctly buries their trails has never
   touched them — a glider behind a ridge drew in front of it, which is most of why
   one over the valley read as floating above a mountain. `fleetmap.js` walks the
   ray to each one and sets this.

   Faded, not hidden: the question this map answers is where everything is, and one
   behind a ridge is still worth knowing about — it just must not look nearer than
   the ridge. */
.map-aircraft.is-behind-terrain {
    opacity: 0.3;
    filter: saturate(0.5);
}

/* The sky in the 3D view. **There is no sky layer to ask for** — that arrived in
   MapLibre 5 and this is 4.7.1, which rejects `type: "sky"` at style validation.
   Above the horizon the canvas draws nothing, so what shows through is the map
   container's own background: a gradient here is the whole horizon.

   Set on the container by `fleetmap.js`, so it follows the camera rather than the
   page — the flat views keep the plain background above. */
.maplibregl-map.map-3d {
    background: linear-gradient(to bottom, #1b3a5c 0%, #4a7fae 55%, #9fc2dd 100%);
}

/* ---------- The marker overlay ----------

   MapLibre draws the imagery, the trails and the terrain labels; the aircraft are
   DOM, because the label is HTML with two coloured fields and the placement engine
   measures real boxes. These two layers sit inside the map's own canvas container,
   so they pan and zoom with it in the compositor and are reprojected per frame by
   `fleetmapgl.js` rather than by a stylesheet.

   `.map-ground` is underneath: wind arrows and landmarks are the ground the flying
   happens over, not the subject. */
.map-markers,
.map-heads,
.map-ground {
    position: absolute;
    inset: 0;
    /* Nothing here is a hit target except the aircraft themselves, which take it
       back. A full-bleed layer that swallowed clicks would kill panning. */
    pointer-events: none;
    /* Above the canvas but below MapLibre's own controls. */
    z-index: 2;
}

.map-ground { z-index: 1; }

/* The live end of each trail. Between the two: over the ground furniture, under
   the aircraft it is attached to. Drawn here rather than as a GL layer because a
   source written every frame forces a full-screen redraw, which at 4K is the
   difference between 8 fps and 58 — see `flushTrailHeads`. */
.map-heads {
    z-index: 2;
    width: 100%;
    height: 100%;
    overflow: visible;
    /* Its own compositing layer, on purpose. These lines change geometry every
       frame directly above a GL canvas, and without a layer of their own the
       invalidation lands on whatever the canvas is composited into — which is the
       mechanism framed's live drop from 59 to 52 fps points at. `will-change`
       rather than a transform hack, and only on this element: the markers already
       animate `transform`, which is composited without repainting. */
    will-change: transform;
    /* Nothing here paints outside its own box, so an invalidation cannot spread. */
    contain: layout paint;
}

.map-markers { z-index: 3; }

/* Positioned from the top-left of the map pane; the transform is written by
   `fleetmapgl.js` every frame, and the negative margins put the box's *centre* on
   the coordinate — which is what everything inside is laid out from. */
.map-markers > *,
.map-ground > * {
    position: absolute;
    top: 0;
    left: 0;
    will-change: transform;
}

.map-markers > * { pointer-events: auto; cursor: pointer; }

/* The marker's transform belongs to Leaflet — it rewrites it on every zoom to
   reposition the marker. Transitioning it made the markers ease toward their new
   pixel positions instead of landing on them, so they lagged behind the imagery
   while zooming. Movement between fixes is interpolated in fleetmap.js against
   the coordinates instead, which Leaflet then projects correctly at any zoom.
   Only the rotation, which Leaflet never touches, is animated here. */
/* The icon box's centre is the aircraft: Leaflet places the box so that iconAnchor
   lands on the coordinate, and fleetmap.js makes that exactly half of iconSize. So
   everything inside is positioned from `50% 50%` rather than laid out in flow.

   This is not stylistic. `display: flex` used to be set here and never applied:
   Leaflet's stylesheet has `.leaflet-marker-icon { display: block }`, it is injected
   at runtime, and it ties on specificity — so it won. The square was left
   baseline-aligned against a two-line label and sat 39 px below the position it was
   marking, which showed up as markers not meeting the ends of their own trails.
   Positioning from the centre cannot be undone by a display rule. */
.map-aircraft {
    --plane-size: 11px;
    --plane-border: 2px;
    --plane-gap: 5px;
    white-space: nowrap;
}

/* Square outline; the border colour carries the aircraft category and is set
   per marker in fleetmap.js. Deliberately unrotated — a square has no
   orientation to read, so heading lives in the popup. */
.map-plane {
    position: absolute;
    left: 50%;
    top: 50%;
    /* Translating by half its own border box centres the square on the coordinate
       whatever the border adds. fleetmap.js rewrites this inline for a glider, to
       append the rotation — so any change here belongs in `planeTransform` too. */
    transform: translate(-50%, -50%);
    width: var(--plane-size);
    height: var(--plane-size);
    border: var(--plane-border) solid;
    border-radius: 2px;
    background: rgba(0, 0, 0, 0.35);
    box-shadow: 0 0 3px rgba(0, 0, 0, 0.9);
}

/* A glider gets its silhouette instead of the box: bigger, because a shape needs
   more pixels to read than a dot does, and with no border or fill of its own — the
   outline is a drop shadow, which follows the wings rather than a square around
   them. `color` carries the category colour into the SVG's `currentColor`. */
/* The powered aeroplane sits a little smaller than the glider: its wings are
   short and its body long, so at the same box it reads heavier on the map than a
   sailplane does. */
.map-plane.is-powered {
    width: calc(var(--plane-size) * var(--powered-scale, 2.9));
    height: calc(var(--plane-size) * var(--powered-scale, 2.9));
    border: none;
    border-radius: 0;
    background: none;
    box-shadow: none;
    filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.55));
}

.map-plane.is-glider {
    width: calc(var(--plane-size) * var(--glider-scale, 3.4));
    height: calc(var(--plane-size) * var(--glider-scale, 3.4));
    border: none;
    border-radius: 0;
    background: none;
    box-shadow: none;
    filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.55));
}

.map-glider {
    display: block;
    width: 100%;
    height: 100%;
}

.map-plane-label {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translateY(-50%);
    /* The one transition on this marker, and deliberately not the one that was
       removed. Placement runs on the poll — every three seconds — and picks the
       cheapest of sixteen discrete slots, so a label teleports to a new slot
       beside an aircraft that is gliding smoothly. Measured at exactly 3.00 s
       intervals; it reads as the *aircraft* jumping, because the label is the
       biggest thing attached to it.

       Safe where the marker's own was not: this transform belongs to us, not to
       Leaflet, so nothing rewrites it on a zoom and there is nothing to lag. The
       icon's transform is still written outright, every frame, unanimated. */
    transition: transform 220ms ease-out;
    /* Clear of the square, measured from the centre it is drawn around. */
    margin-left: calc(var(--plane-size) / 2 + var(--plane-border) + var(--plane-gap));
    font-size: 0.6875rem;
    font-weight: 700;
    line-height: 1.25;
    color: #fff;
    background: rgba(0, 0, 0, 0.62);
    border-radius: 3px;
    padding: 0 0.25rem;
}

.map-reg { display: block; }

/* ?mawoni and ?dassu — landmarks, not aircraft. Muted and unrotated, because the
   point of them is to be something the moving things can be judged against. */
.map-landmark {
    white-space: nowrap;
    color: #cfd8e0;
}

.map-landmark-icon {
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    width: 18px;
    height: 18px;
    opacity: 0.9;
    filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.6));
}

.map-landmark-icon svg { display: block; width: 100%; height: 100%; }

.map-landmark-label {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translateY(-50%);
    margin-left: 0.75rem;
    font-size: 0.625rem;
    font-weight: 600;
    letter-spacing: 0.04em;
    line-height: 1.25;
    color: #e8eef3;
    background: rgba(0, 0, 0, 0.5);
    border-radius: 3px;
    padding: 0 0.25rem;
}

/* Second line, lighter than the registration so the identity still leads. */
.map-extra {
    display: block;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    color: #d6e4ee;
}

/* The climb rate carries its own colour, set per marker in fleetmap.js: white at
   zero, green climbing, red sinking, deepening to 5 m/s. */
.map-climb { color: #ffffff; }

.map-popup { min-width: 8rem; }
.map-popup-model { color: var(--text-secondary); font-size: 0.75rem; margin-bottom: 0.3rem; }

.map-popup-row {
    display: flex;
    justify-content: space-between;
    gap: 0.75rem;
    font-size: 0.8125rem;
    font-variant-numeric: tabular-nums;
}

.map-popup-row span:first-child { color: var(--text-secondary); }

.map-empty {
    position: absolute;
    left: 50%;
    bottom: 1rem;
    transform: translateX(-50%);
    z-index: 500;
    padding: 0.4rem 0.75rem;
    border-radius: 999px;
    background: var(--card-bg);
    border: 1px solid var(--border-color);
    color: var(--text-secondary);
    font-size: 0.8125rem;
}

/* Settings rows: label on the left, choice on the right, stacking on narrow
   screens so a three-option control never squeezes its labels. */
.setting {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    margin-top: 0.75rem;
}

.setting-label {
    font-size: 0.875rem;
    color: var(--text-secondary);
}

.setting .segmented {
    margin-top: 0;
    flex: 0 1 auto;
    min-width: 11rem;
}

@media (max-width: 420px) {
    .setting { flex-direction: column; align-items: stretch; gap: 0.4rem; }
    .setting .segmented { min-width: 0; }
}

/* ===== Fleet day list =====
   The third rotation state of the Flotte tab: one day's flights in the order
   they happened. Sticky header so the day being shown, and the way out of it,
   stay reachable on a long day. */

.day-head {
    position: sticky;
    /* Clear the top nav where there is one. At `top: 0` the header parked
       *behind* the fixed bar, which did not merely look wrong: the nav sits at
       z-index 1000 and swallowed the clicks, so ‹ and › could not be pressed at
       all on a wide window. */
    top: var(--nav-top-h);
    z-index: 2;
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.5rem 0;
    margin-bottom: 0.75rem;
    background: var(--bg-color);
    border-bottom: 1px solid var(--border-color);
}

.day-step {
    flex: 0 0 auto;
    width: 2.5rem;
    height: 2.5rem;
    font-size: 1.5rem;
    line-height: 1;
    color: var(--text-primary);
    background: var(--secondary-bg);
    border: 1px solid var(--border-color);
    border-radius: var(--radius);
    cursor: pointer;
}

.day-step:disabled { opacity: 0.3; cursor: default; }

.day-when { flex: 1 1 auto; text-align: center; }

.day-date {
    font-size: 1.0625rem;
    font-weight: 600;
}

.day-rel {
    margin-left: 0.5rem;
    font-size: 0.75rem;
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--primary-accent);
}

.day-meta {
    font-size: 0.8125rem;
    color: var(--text-secondary);
}

.day-list {
    border: 1px solid var(--border-color);
    border-radius: var(--radius);
    overflow: hidden;
}

/* Columns rather than flex space-between: takeoff times and durations only read
   as a column when they share an edge down the whole list. */
.day-row {
    display: grid;
    /* The last column is the launch letter. It holds its width whether or not
       there is one, so the durations stay in a line down the list. */
    grid-template-columns: 2rem 6.5rem 5.5rem 1fr auto 1.55rem;
    align-items: baseline;
    gap: 0.5rem;
    padding: 0.5rem 0.75rem;
    font-size: 0.9375rem;
    border-bottom: 1px solid var(--rule-faint);
}

.day-row:last-child { border-bottom: none; }
.day-row:nth-child(odd) { background: var(--row-stripe); }

.day-nr {
    font-size: 0.75rem;
    color: var(--text-secondary);
    font-variant-numeric: tabular-nums;
}

.day-time { font-variant-numeric: tabular-nums; }
.day-reg { font-weight: 600; }

.day-model {
    font-size: 0.8125rem;
    color: var(--text-secondary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.day-dur {
    font-variant-numeric: tabular-nums;
    color: var(--text-secondary);
}

.day-row.is-live .day-time,
.day-row.is-live .day-dur { color: var(--good); }

.day-suffix {
    padding: 0.625rem 0 0;
    text-align: right;
    font-size: 0.75rem;
    color: var(--text-secondary);
}

/* A day that is still loading dims rather than emptying, so stepping through
   days does not flash the list away and back. */
#fluege-day.is-busy { opacity: 0.55; }

@media (max-width: 380px) {
    /* `.day-model` is dropped below, so it is out of the grid flow entirely and
       this is four content columns plus the launch letter. */
    .day-row { grid-template-columns: 1.5rem 5.75rem 5rem 1fr 1.55rem; }
    .day-model { display: none; }
}

.fleet-omitted {
    padding: 0.5rem 0.25rem 0;
    font-size: 0.8125rem;
    color: var(--text-secondary);
}

/* ===== 30-day overview =====
   Second state of the Flüge tab. Empty days are rendered, not skipped: a month
   of flying is mostly about which days worked, and dropping the blanks would
   hide every weather write-off. */

.month-bars {
    display: flex;
    flex-direction: column;
    gap: 0.125rem;
    margin-top: 0.625rem;
}

.month-bar {
    display: grid;
    grid-template-columns: 4.5rem 1fr 2rem 3rem;
    align-items: center;
    gap: 0.5rem;
    font-size: 0.8125rem;
    font-variant-numeric: tabular-nums;
}

.month-bar.is-empty { opacity: 0.45; }

.month-day { color: var(--text-secondary); }
.month-day.is-weekend { color: var(--text-primary); font-weight: 600; }

.month-track {
    height: 0.75rem;
    border-radius: 999px;
    background: var(--secondary-bg);
    overflow: hidden;
}

.month-fill {
    display: block;
    height: 100%;
    border-radius: 999px;
    background: var(--primary-accent);
}

.month-count { text-align: right; font-weight: 600; }
.month-hours { text-align: right; color: var(--text-secondary); }

@media (max-width: 380px) {
    .month-bar { grid-template-columns: 4rem 1fr 1.75rem 2.75rem; gap: 0.375rem; font-size: 0.75rem; }
    .nav-label { font-size: 0.625rem; }
}


/* ---------- How a flight got airborne ----------

   One letter, in the club's own code — W Winde, F F-Schlepp, E Eigenstart — so
   an OGN-derived answer reads the same as the logbook's. Deliberately quiet: it
   is a fact about the flight, not a status, and the row's own numbers are what
   people are reading. `unknown` renders nothing at all rather than a placeholder
   column of dashes. */
.sortie-launch,
.day-row .sortie-launch {
    justify-self: end;
    min-width: 1.15em;
    padding: 0.05em 0.28em;
    border-radius: 0.25rem;
    border: 1px solid var(--rule-faint);
    font-size: 0.72rem;
    font-weight: 600;
    line-height: 1.5;
    text-align: center;
    color: var(--text-secondary);
    background: var(--secondary-bg);
}

/* An aerotow is the one worth picking out: it is the rarer launch here and the
   one that says something about the day. */
.launch-aerotow {
    color: var(--primary-accent);
    border-color: color-mix(in srgb, var(--primary-accent) 45%, transparent);
}

/* Under the day's totals, and quieter than them: how the flying started. */
.day-launches {
    font-size: 0.75rem;
    color: var(--text-secondary);
    margin-top: 0.1rem;
}

/* On the collapsed card the letter follows the "seit 12:17 · 2:46 h" line, so it
   reads as part of that sentence rather than as a column of its own. */
.fleet-status-detail .sortie-launch {
    margin-left: 0.4rem;
    vertical-align: baseline;
}
