/* Flight tracker -- live aircraft on a map, with the selected one's track.
   Everything is scoped under .ft so it cannot leak into the rest of the site,
   and so it cannot be leaked into by Leaflet's own stylesheet.
   Colours follow the site's Bootstrap palette rather than inventing new ones. */

.ft {
    --ft-text: #212529;
    --ft-muted: #6c757d;
    --ft-faint: #adb5bd;
    --ft-line: #dee2e6;
    --ft-accent: #1b6ec2;
    --ft-panel-bg: #ffffff;
    /* The line from the visitor to the nearest matching aircraft answers the
       question in its own colour: green while that aircraft is still on its way
       towards its closest point, red once it is past it.

       Darker than the reds and greens in the altitude scale, for two reasons: the
       lozenge on the line uses the same colour behind white text and has to stay
       readable, and the difference in shade keeps a thick line about closing
       distance from being mistaken for a thin one about altitude. */
    --ft-closing: #15803d;
    --ft-opening: #b91c1c;
    --ft-map-h: 620px;
    color: var(--ft-text);
}

/* ---- controls ---------------------------------------------------------- */

.ft-form {
    display: flex;
    flex-direction: column;
    gap: 10px;
    margin-bottom: 16px;
}

/* Row one asks the only question the page really has: what am I looking at. It
   is deliberately short -- one box and one button -- because everything else
   that used to sit here was answering a different question. */
.ft-controls {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
}

.ft-label {
    font-size: 14px;
    color: var(--ft-muted);
    margin: 0;
}

.ft-input {
    font: 600 15px/1 ui-monospace, "Cascadia Mono", Consolas, monospace;
    letter-spacing: .06em;
    text-transform: uppercase;
    /* Wide enough for a couple of codes. A family preset writes nine of them in
       here and scrolls, which is fine -- the box is the honest source of truth
       for the filter, and the chips are the easy way to set it. */
    width: 14ch;
    padding: 7px 9px;
    border: 1px solid var(--ft-line);
    border-radius: 4px;
    background: #fff;
    color: var(--ft-text);
}

/* The type box and its x are drawn as one control: the shell carries the border
   and the focus ring, the field inside it carries neither. The same shape as the
   airport token, which holds its own x the same way. */
.ft-typebox {
    display: inline-flex;
    align-items: center;
    padding-right: 3px;
    border: 1px solid var(--ft-line);
    border-radius: 4px;
    background: #fff;
}

.ft-typebox .ft-input {
    border: none;
    background: none;
}

/* The ring goes round the whole control, including the x, rather than round the
   field and leaving the button sitting outside it. */
.ft-typebox:focus-within {
    outline: 2px solid var(--ft-accent);
    outline-offset: -1px;
    border-color: transparent;
}

.ft-typebox .ft-input:focus {
    outline: none;
}

/* One airport code, so it does not need the type box's width and should not look
   like a second list. */
.ft-input-airport {
    width: 6ch;
    text-align: center;
}

/* The box is upper-cased for the codes typed into it, which would render the
   placeholder as "ANY" -- indistinguishable from an airport code someone had
   actually entered. It has to look like the absence of one. */
.ft-input::placeholder {
    text-transform: lowercase;
    letter-spacing: normal;
    font-weight: 400;
    color: var(--ft-faint);
}

.ft-input:focus {
    outline: 2px solid var(--ft-accent);
    outline-offset: -1px;
    border-color: transparent;
}

.ft-go {
    padding: 7px 14px;
    border: 1px solid var(--ft-accent);
    border-radius: 4px;
    background: var(--ft-accent);
    color: #fff;
    font-size: 14px;
    cursor: pointer;
}

.ft-go:hover {
    filter: brightness(1.08);
}

/* Sits with the controls rather than on the map: the map's own corner is where
   Leaflet's zoom buttons live, and on a phone a thumb-sized target next to the
   type box is easier to find than an icon over the tiles. */
.ft-locate {
    padding: 7px 12px;
    border: 1px solid var(--ft-line);
    border-radius: 4px;
    background: #fff;
    color: var(--ft-accent);
    font-size: 14px;
    cursor: pointer;
}

.ft-locate:hover {
    border-color: var(--ft-accent);
}

/* The negative top margin here used to pull this up under the help paragraph.
   That paragraph is gone, so it would now pull it into the controls instead. */
.ft-locate-note {
    margin: 0 0 12px;
    font-size: 13px;
    color: var(--ft-muted);
}

/* A failure here is the answer to something the visitor asked for, so it is
   allowed to be louder than the surrounding help text. */
.ft-locate-problem {
    color: #b02a37;
}

.ft-presets {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-wrap: wrap;
}

/* Between the saved view and the type chips: they are different kinds of thing
   and the gap alone was not saying so. */
.ft-preset-split {
    width: 1px;
    align-self: stretch;
    margin: 2px 4px;
    background: var(--ft-line);
}

.ft-preset {
    padding: 6px 10px;
    border: 1px solid var(--ft-line);
    border-radius: 999px;
    background: #fff;
    color: var(--ft-muted);
    font-size: 13px;
    cursor: pointer;
}

.ft-preset:hover {
    border-color: var(--ft-accent);
    color: var(--ft-accent);
}

/* The view the page opens on. Violet, which is nothing else on this page -- not
   an altitude, not either answer the spotting line gives -- so it reads as an
   identity for the view rather than as a claim about any aircraft. */
.ft-preset-spot {
    border-color: #7e22ce;
    color: #7e22ce;
    font-weight: 600;
}

.ft-preset-spot:hover {
    background: #7e22ce;
    color: #fff;
}

/* ---- map --------------------------------------------------------------- */

.ft-map-wrap {
    position: relative;
}

.ft-map {
    height: var(--ft-map-h);
    border: 1px solid var(--ft-line);
    border-radius: 6px;
    background: #f8f9fa;
}

/* Leaflet puts its own transform on the marker element for positioning, so the
   aircraft's rotation has to live on this span inside it. */
.ft-marker {
    background: none;
    border: none;
}

.ft-plane {
    display: block;
    width: 24px;
    height: 24px;
    transform-origin: 50% 50%;
    transition: transform .4s linear;
    filter: drop-shadow(0 1px 1px rgba(0, 0, 0, .35));
}

.ft-plane svg {
    display: block;
}

.ft-emergency {
    animation: ft-flash .9s steps(1, end) infinite;
}

@keyframes ft-flash {
    50% { opacity: .25; }
}

@media (prefers-reduced-motion: reduce) {
    .ft-plane { transition: none; }
    .ft-emergency { animation: none; outline: 2px solid #dc2626; border-radius: 50%; }
}

/* ---- selected aircraft ------------------------------------------------- */

.ft-panel {
    position: absolute;
    top: 12px;
    right: 12px;
    z-index: 500; /* above Leaflet's panes, below its controls */
    width: 260px;
    padding: 14px 16px;
    background: var(--ft-panel-bg);
    border: 1px solid var(--ft-line);
    border-radius: 6px;
    box-shadow: 0 2px 10px rgba(0, 0, 0, .12);
}

.ft-panel-close {
    position: absolute;
    top: 6px;
    right: 8px;
    border: none;
    background: none;
    font-size: 20px;
    line-height: 1;
    color: var(--ft-faint);
    cursor: pointer;
}

.ft-panel-close:hover {
    color: var(--ft-text);
}

.ft-panel-title {
    display: flex;
    align-items: baseline;
    gap: 8px;
    flex-wrap: wrap;
    padding-right: 18px;
}

.ft-callsign {
    font: 600 18px/1 ui-monospace, "Cascadia Mono", Consolas, monospace;
    letter-spacing: .04em;
}

.ft-reg {
    font-size: 13px;
    color: var(--ft-muted);
}

.ft-panel-type {
    margin-top: 3px;
    font-size: 13px;
    color: var(--ft-muted);
}

/* ---- route ------------------------------------------------------------- */

.ft-route {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    gap: 8px;
    margin-top: 12px;
    padding: 10px 0;
    border-top: 1px solid var(--ft-line);
    border-bottom: 1px solid var(--ft-line);
}

.ft-route-end {
    min-width: 0;
}

.ft-route-end:last-child {
    text-align: right;
}

.ft-airport-code {
    display: block;
    font: 600 17px/1.1 ui-monospace, "Cascadia Mono", Consolas, monospace;
    letter-spacing: .04em;
}

.ft-airport-name {
    display: block;
    margin-top: 2px;
    font-size: 12px;
    color: var(--ft-muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.ft-route-arrow {
    color: var(--ft-faint);
    font-size: 16px;
}

.ft-route-note {
    margin-top: 10px;
    font-size: 12px;
    color: var(--ft-muted);
}

/* ---- progress through the flight --------------------------------------- */

.ft-progress {
    margin-top: 10px;
}

.ft-progress-track {
    height: 6px;
    border-radius: 3px;
    background: var(--ft-line);
    overflow: hidden;
}

.ft-progress-fill {
    height: 100%;
    width: 0;
    border-radius: 3px;
    background: var(--ft-accent);
    transition: width .6s ease;
}

.ft-progress-row {
    display: flex;
    justify-content: space-between;
    gap: 8px;
    margin-top: 5px;
    font-size: 12px;
    color: var(--ft-muted);
}

.ft-progress-pct {
    font-variant-numeric: tabular-nums;
    color: var(--ft-text);
    font-weight: 600;
}

.ft-progress-left {
    font-variant-numeric: tabular-nums;
}

@media (prefers-reduced-motion: reduce) {
    .ft-progress-fill { transition: none; }
}

.ft-times {
    margin-top: 10px;
    padding-top: 10px;
    border-top: 1px dashed var(--ft-line);
}

.ft-facts {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: 3px 12px;
    margin: 12px 0 0;
    font-size: 14px;
}

.ft-facts dt {
    color: var(--ft-muted);
    font-weight: 400;
}

.ft-facts dd {
    margin: 0;
    text-align: right;
    font-variant-numeric: tabular-nums;
}

.ft-panel-track {
    margin-top: 12px;
    padding-top: 10px;
    border-top: 1px solid var(--ft-line);
    font-size: 12px;
    color: var(--ft-muted);
}

/* ---- status and footer ------------------------------------------------- */

/* The answer. Above the map since it is what the page is usually open for, and
   the airport token sits in it because a filter that persists has to be
   somewhere it will be noticed. */
.ft-status {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
}

/* ---- the airport filter, as a token ------------------------------------ */

.ft-airport {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 3px 4px 3px 10px;
    border: 1px solid var(--ft-line);
    border-radius: 999px;
    background: #fff;
}

.ft-airport .ft-input-airport {
    border: none;
    padding: 2px 0;
    width: 5ch;
    background: none;
}

.ft-airport .ft-input-airport:focus {
    outline: none;
}

/* The x that empties a box: the airport token's, and the type box's. One
   control drawn one way, because they are the same gesture even though what
   they clear differs -- see the comment on the type box in the view. */
.ft-clear {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    padding: 0;
    border: none;
    border-radius: 50%;
    background: none;
    color: var(--ft-muted);
    font-size: 17px;
    line-height: 1;
    cursor: pointer;
}

.ft-clear:hover {
    background: var(--ft-line);
    color: #b02a37;
}

/* With no filter on, the token collapses to an offer of one. Nothing to read,
   nothing to clear, and no empty box to wonder about. */
.ft-airport-empty {
    border-color: transparent;
    padding: 0;
    background: none;
}

.ft-airport-empty .ft-label,
.ft-airport-empty .ft-input-airport {
    display: none;
}

.ft-airport-add {
    padding: 4px 10px;
    border: 1px dashed var(--ft-line);
    border-radius: 999px;
    background: none;
    color: var(--ft-muted);
    font-size: 13px;
    cursor: pointer;
}

.ft-airport-add:hover {
    border-style: solid;
    border-color: var(--ft-accent);
    color: var(--ft-accent);
}

/* Held, but inert: the current filter is answered from an endpoint that knows
   nothing about routes. Greyed rather than removed, because the visitor never
   cancelled it and picking a real type brings it back. */
.ft-airport-inert {
    opacity: .5;
    border-style: dashed;
}

.ft-airport-inert .ft-input-airport {
    color: var(--ft-muted);
}

/* No fix yet, so there is no nearest-one answer and this is the control that
   gets one. It sits where the answer will appear and looks like the thing to
   press, rather than like one more button in a row of them. */
.ft-locate-wanted {
    border-color: var(--ft-accent);
    background: var(--ft-accent);
    color: #fff;
    font-weight: 600;
}

.ft-locate-wanted:hover {
    filter: brightness(1.08);
}

.ft-pill {
    padding: 4px 12px;
    border-radius: 999px;
    background: var(--ft-accent);
    color: #fff;
    font-size: 13px;
}

.ft-idle {
    background: var(--ft-faint);
}

.ft-clock {
    font-size: 13px;
    color: var(--ft-muted);
    font-variant-numeric: tabular-nums;
}

/* The same reading as the label out on the map, in a fixed place so it survives
   panning the aircraft off the screen, and in the same colour as the line so the
   two are obviously one thing. */
.ft-spot {
    display: inline-flex;
    align-items: baseline;
    gap: 5px;
    padding: 4px 12px;
    border: 1px solid currentColor;
    border-radius: 999px;
    font-size: 13px;
    color: var(--ft-muted);
    font-variant-numeric: tabular-nums;
}

.ft-spot.ft-closing {
    color: var(--ft-closing);
}

.ft-spot.ft-opening {
    color: var(--ft-opening);
}

/* ---- the line to the nearest one --------------------------------------- */

/* Leaflet positions the icon's own element; the label hangs off it and centres
   itself on that point, which is the middle of the line. Sized by its content
   rather than by an iconSize, since the number changes width. */
.ft-spot-marker {
    width: 0;
    height: 0;
}

.ft-spot-label {
    position: absolute;
    transform: translate(-50%, -50%);
    display: inline-flex;
    align-items: baseline;
    gap: 4px;
    padding: 3px 9px;
    border-radius: 999px;
    background: var(--ft-muted);
    color: #fff;
    font-size: 13px;
    font-weight: 600;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
    /* A white ring, so the chip reads over the line running under it. */
    box-shadow: 0 0 0 2px #fff, 0 1px 4px rgba(0, 0, 0, .35);
}

/* The lozenge takes the line's own colour rather than annotating it with an
   arrow: green while the aircraft is getting closer, red while it is getting
   further away, violet while it is doing neither. All three are dark enough for
   the white number to stay readable on them, which is why they are these shades
   and not the brighter ones in the altitude scale. */
.ft-spot-label.ft-closing {
    background: var(--ft-closing);
}

.ft-spot-label.ft-opening {
    background: var(--ft-opening);
}

/* Where the visitor is. Round like the destination pin but haloed rather than
   plain, and in the site accent rather than the route pink, because it is a
   reference point rather than part of any flight. */
.ft-home {
    box-sizing: border-box;
    display: block;
    width: 16px;
    height: 16px;
    border-radius: 50%;
    background: var(--ft-accent);
    border: 3px solid #fff;
    box-shadow: 0 0 0 4px rgba(27, 110, 194, .25), 0 1px 3px rgba(0, 0, 0, .4);
}

/* The lock-on cue: while the device is pointing along the line to the nearest
   aircraft, the line breathes. It is read at arm's length, outdoors, in daylight,
   by someone who is looking at the sky rather than at the phone -- so it swells to
   two and a half times its width. It was 4 to 7, which was invisible in exactly
   those conditions.

   The casing swells with it and stays wider, so the line grows inside its own
   outline rather than out through it, and the whole thing reads as one thickening
   stroke rather than two. */
@keyframes ft-spot-pulse {
    0%, 100% { stroke-width: 4; stroke-opacity: .95; }
    50%      { stroke-width: 10; stroke-opacity: 1; }
}

@keyframes ft-spot-pulse-casing {
    0%, 100% { stroke-width: 8; stroke-opacity: .85; }
    50%      { stroke-width: 15; stroke-opacity: .95; }
}

.ft-spot-aligned {
    animation: ft-spot-pulse 1.3s ease-in-out infinite;
}

.ft-spot-aligned-casing {
    animation: ft-spot-pulse-casing 1.3s ease-in-out infinite;
}

/* Where motion is unwelcome the cue still has to be given, so the lines simply
   sit at the top of the pulse instead of doing nothing. */
@media (prefers-reduced-motion: reduce) {
    .ft-spot-aligned {
        animation: none;
        stroke-width: 10;
    }

    .ft-spot-aligned-casing {
        animation: none;
        stroke-width: 15;
    }
}

/* Which way the device is facing, drawn the way the phone map apps draw it: a
   translucent cone spreading out from under the dot, fading as it goes, in the
   same blue. Rotated from JS by a transform on this span -- Leaflet owns the one
   on the marker element itself, for positioning.

   Absolute against the marker's own 16px box, with the point of the cone at the
   middle of it, so turning it is a rotation about the dot and nothing else moves.
   Hidden until a reading arrives: a device with no magnetometer gets no cone
   rather than one pointing north and staying there. */
.ft-heading {
    position: absolute;
    left: 50%;
    top: 50%;
    width: 92px;
    height: 92px;
    margin: -46px 0 0 -46px;
    transform-origin: 50% 50%;
    /* The cone is the top half of the box, tip at the centre; the gradient fades
       it out before the edge so it ends in nothing rather than in a hard rim. */
    clip-path: polygon(50% 50%, 20% 0%, 80% 0%);
    background: radial-gradient(circle at 50% 50%,
                                rgba(27, 110, 194, .55) 0,
                                rgba(27, 110, 194, .32) 45%,
                                rgba(27, 110, 194, 0) 74%);
    /* Short enough to keep up with a turning phone, long enough that a jittering
       compass reads as a wobble rather than a flicker. */
    transition: transform .15s linear;
    pointer-events: none;
}

/* Destination airport pin on the map. */
.ft-dest {
    display: block;
    width: 14px;
    height: 14px;
    border-radius: 50%;
    background: #db2777;
    border: 2px solid #fff;
    box-shadow: 0 1px 3px rgba(0, 0, 0, .4);
}

/* Origin airport pin. Grey rather than the destination's pink, to match the
   dashed grey link running back to it: the aircraft flew that stretch before
   any receiver picked it up, which is the same "inferred" grey used for a
   coverage gap in the body of the track. */
.ft-origin {
    display: block;
    width: 12px;
    height: 12px;
    border-radius: 50%;
    background: #64748b;
    border: 2px solid #fff;
    box-shadow: 0 1px 3px rgba(0, 0, 0, .4);
}

.ft-footer {
    display: flex;
    justify-content: space-between;
    gap: 16px;
    flex-wrap: wrap;
    margin-top: 10px;
    padding-top: 10px;
    border-top: 1px solid var(--ft-line);
    font-size: 12px;
    color: var(--ft-muted);
}

@media (max-width: 640px) {
    .ft {
        --ft-map-h: 460px;
    }

    .ft-panel {
        position: static;
        width: auto;
        margin-top: 10px;
        box-shadow: none;
    }

    .ft-presets {
        margin-left: 0;
    }
}
