/* ============================================================================
   Aeromexico Virtual — brand.css
   The single source of truth for the brand.

   Every token in :root below is mirrored 1:1 in /brand.json, which is what the
   Inflight Crew Center reads (via the VA's `crewTheme`) so that crew.inflight
   pages render in this exact design system. Change a value here, change it in
   brand.json, and the crew center follows. Nothing else should hard-code a
   colour.

   ---------------------------------------------------------------------------
   THE DECORATION IS REAL ARTWORK. NOTHING HERE IS DRAWN BY HAND.

   This file has been through two wrong answers on that point. First a blue →
   plum → pink gradient behind everything, which is the house style of software
   nobody art-directed. Then a set of hand-drawn Mexican motifs to replace it:
   a step-fret greca, a Puebla azulejo tiled across whole sections, papel
   picado bunting, a Quetzalcóatl watermark. Authentic references, but invented
   artwork — and invented artwork standing in for a real airline's design reads
   as exactly what it is.

   All of it is gone. What decorates the site now is material the airline
   actually owns:

     Aeromexico-Symbol.webp  the Caballero Águila, traced to mark.svg
     the fleet photographs   the VA's own sim shots of its own airframes, off
                             the community gallery (AMV_DATA.fleet[].photo)
     the tricolour           hard stops, real flag colours

   The hero is those photographs, full bleed, one at a time. The folk-art
   artwork that used to open the page (plane-logo.webp and the plane-hero.webp
   crop of it) is gone with the two heroes that used it: a drawing of a 787-9
   was sitting one click from photographs of the same airframe.

   Where the dark sections went next, still inside that rule: the ruled device
   now runs down BOTH edges and the eagle sits faded behind them. Both are the
   same two generated files — stripes measured off full-logo.webp, the mark
   traced off Aeromexico-Symbol.webp — used more, not joined by anything new.
   The one addition there is a two-stop red → rosa ramp under the stripe mask:
   two colours already in the palette, confined to a device generated from the
   logo, not three invented hues washed behind whole pages.

   ---------------------------------------------------------------------------
   AND THEN THE RULE WAS RELAXED, ON PURPOSE. READ THIS BEFORE RESTORING IT.

   The VA asked for Mexican patterns twice, having been told what this file
   says. That is their call to make about their own airline, so the greca is
   here: the stepped fret off Mixtec and Zapotec stonework, generated from a
   grid by tools/make-greca.py, tiled faintly across the pale sections and run
   as a band along the top of the footer.

   Be honest about what changed. The greca is NOT airline artwork the way the
   mark and the aircraft are — nobody at Aeroméxico drew it, and the paragraphs
   above would have refused it. What makes it defensible where the old azulejo
   and papel picado were not:

     - it is generated from parameters, not drawn by eye, so it is a real
       geometry that can be retuned rather than a picture someone illustrated;
     - it is built from right angles on a grid, which is what the ornament
       actually is — this is the one Mexican motif that is architectural rather
       than pictorial, so a faithful version needs no illustration;
     - it is used as texture and as a rule, never as a subject, and never
       standing in for the airline's own identity.

   So the standing rule is now: no PICTORIAL invention — no drawn eagles, no
   illustrated tiles, no papel picado bunting, nothing that imitates artwork a
   designer would have been paid to make. Geometry generated from parameters is
   allowed. If the next idea cannot be expressed as a script that emits it, it
   is the wrong idea.
   ========================================================================== */

:root {
    /* ---- Livery ------------------------------------------------------------ */
    --am-navy:      #0A1D3B;   /* tail navy — deep sections, footer           */
    --am-blue:      #0C2C64;   /* primary brand blue                          */
    --am-blue-600:  #123F8C;   /* lifted blue — hovers, links                 */
    --am-blue-300:  #7FA6E8;   /* on-dark blue text                           */
    --am-red:       #D8102F;   /* the fuselage swoosh                         */

    /* ---- Quetzalcóatl ------------------------------------------------------
       Pulled off the glyphs down the side of XA-ADL. Accent colours for tags
       and the per-type fleet rules — never body text, never large fields. */
    --am-jade:      #0E8C5A;
    --am-turquoise: #0E9BAA;
    --am-rosa:      #D6337E;   /* rosa mexicano                               */
    --am-marigold:  #F2A03D;   /* cempasúchil                                 */
    --am-clay:      #C05A2E;   /* barro                                       */
    --am-cobalt:    #1B5FC1;   /* talavera poblana                            */

    /* ---- Bandera ----------------------------------------------------------
       The official flag greens and reds, kept separate from the livery reds
       so nobody "tidies up" the palette by merging them. They are not ours. */
    --mx-green:     #006847;
    --mx-red:       #CE1126;

    /* ---- Paper -------------------------------------------------------------
       White. The greys are pulled very slightly toward the navy rather than
       being neutral, so they read as chosen next to the livery instead of as
       an unconsidered default mid-grey. */
    --am-paper:     #FFFFFF;
    --am-ink:       #0F1B2E;

    /* The hero/band depth wash. Deliberately ONE hue, dark to slightly less
       dark — it adds depth without becoming decoration. Ornament is the job
       of the motifs, not of this. */
    --am-wash: linear-gradient(168deg, #0A1D3B 0%, #0C2C64 58%, #103A7A 100%);

    /* ---- Semantic surfaces (light) ----------------------------------------- */
    --bg:         var(--am-paper);
    --bg-alt:     #F5F7FA;
    --surface:    #FFFFFF;
    --surface-2:  #F4F6F9;
    --line:       #E4E8EF;
    --line-soft:  #EDF0F5;
    --ink:        var(--am-ink);
    --muted:      #5A6478;
    --faint:      #97A0B0;
    --accent:     var(--am-blue);
    --accent-ink: #FFFFFF;     /* text that sits on --accent                  */

    /* ---- Type --------------------------------------------------------------
       Fraunces is a soft, slightly wonky old-style — it carries the warmth the
       brief asked for and it is nobody's default. Karla sets text underneath
       it. JetBrains Mono stays: flight levels, block times, ICAO codes and
       registrations all want tabular figures. */
    --font-display: 'Fraunces', 'Iowan Old Style', Georgia, serif;
    --font-body:    'Karla', system-ui, -apple-system, sans-serif;
    --font-mono:    'JetBrains Mono', ui-monospace, monospace;

    /* ---- Shape & motion ----------------------------------------------------
       Shadows are mixed from the navy, very sparingly. On white, a warm-brown
       shadow reads as a smudge and a neutral black one reads as heavy; a trace
       of the brand hue keeps edges soft and clean. */
    /* Bigger than they were (18 / 11 / 28), and this is the single change
       that does most of the work on how expensive the page looks. A radius
       reads as "rounded" from about 12px and as "considered" from about 20:
       below that the corner is a bevel on a rectangle, above it the shape is
       a rounded rectangle in its own right. The three keep a ratio rather
       than being picked separately, so a chip inside a card inside a panel
       steps down evenly instead of all three landing at once. */
    --radius:    24px;
    --radius-sm: 16px;
    --radius-lg: 34px;
    /* Slightly less overshoot than (.2,.8,.2,1). That curve arrives fast and
       decelerates hard, which is lively; this one is closer to even and reads
       as weight — the difference between a button that pops and one that
       moves because you pushed it. */
    --ease:      cubic-bezier(.32, .72, 0, 1);
    /* The reveal curve. `--ease` is a hover curve — it spends its time in the
       middle, which is right for a 250ms nudge and wrong for something
       arriving from off the page: at 600ms it reads as floating. This one
       spends almost all of its distance in the first third and then settles,
       so a card arrives and stops rather than drifting into place. */
    --ease-rise: cubic-bezier(.16, 1, .3, 1);

    /* How far a pale section takes to arrive.
       See SECTION SEAMS, below. Kept inside the section's own padding so the
       ramp never runs under the copy. */
    --seam:      clamp(1.75rem, 4.5vw, 4rem);
    /* The same idea for a NAVY section meeting a pale one. Shorter, because the
       two colours are forty percent apart rather than four: over the pale seam's
       distance a white-to-navy ramp reads as a smear, and over this one it
       reads as the section arriving. */
    --ink-seam:  clamp(1.25rem, 3.5vw, 2.75rem);
/* Three layers, not two, and all three very weak. A single soft shadow at
       the strength needed to be SEEN reads as a drop shadow — the object looks
       stuck to the page with a smudge under it. Stacked, each one does a
       different job: a hairline contact shadow that keeps the edge crisp, a
       short one that gives the object thickness, and a long diffuse one that
       puts it in a room. The whole stack is weaker than the old two layers,
       and reads as more solid.

       Still mixed from the navy rather than black. On white, black reads cold
       and heavy; a trace of the brand hue keeps the edge soft. */
    --shadow:    0 1px 1px rgba(15, 27, 46, .035),
                 0 4px 10px -4px rgba(15, 27, 46, .055),
                 0 18px 40px -20px rgba(15, 27, 46, .10);
    --shadow-lg: 0 1px 1px rgba(15, 27, 46, .04),
                 0 10px 24px -10px rgba(15, 27, 46, .09),
                 0 36px 80px -30px rgba(15, 27, 46, .17);

    /* Height of one stripes.svg tile, in px. The mask is sized 100% x this, so
       bar thickness and pitch stay constant however wide the column gets —
       sizing it `auto` ties the vertical rhythm to the width and collapses the
       bars to hairlines on a phone. Keep in step with tools/make-stripes.py,
       which prints the value it generated. */
    --stripe-tile: 520px;

    /* The greca, in px. Both keep the generator's 60:24 (band) and 60:42 (tile)
       proportions — stretch either axis on its own and the fret stops being
       square-cornered, which is the whole character of the ornament. Sized in
       px rather than `auto` for the same reason --stripe-tile is: `auto` ties
       the pattern to the element's width and it collapses on a phone. */
    --greca-band:   150px 60px;
    --greca-band-h: 22px;     /* how much of that band shows as the footer rule */
    --greca-tile:   90px 63px;

    /* The Quetzalcóatl frieze. Same 128:29 the generator emits. */
    --serpent-band: 128px 29px;
    --serpent-h:    29px;

    /* The serape, exactly as tools/make-serape.py emits it. Sized in px for the
       same reason every other generated tile here is: `auto` ties the weave to
       the element's width, and a 4px thread crossing collapses to nothing on a
       phone. Re-run the script and it prints the value to put here. */
    --serape-tile:  224px 60px;
    --serape-h:     14px;

    /* The pinned header, in two parts: the tricolour hairline and the bar
       under it. Named because crew.html sizes the framed crew centre against
       them — a viewport-height stage has to subtract the whole header, not
       just the bar, or the page gains five pixels of scroll it never uses. */
    /* The tricolour is INSIDE the bar now — see .flagline and NAV below — so
       it no longer adds to the header's height. What does is the gap around
       the island: the bar floats with air on all four sides, and the sticky
       block it occupies is the bar plus that gap twice.

       Five pixels again rather than eight. The review read a five-pixel strip
       pinned across the top of the page as a loading bar, which it was; drawn
       inside a rounded bar, clipped by its corners, it reads as the header's
       own flag and eight is heavy-handed. */
    --flagline-h: 5px;
    --nav-h:      4.25rem;
    --nav-gap:    clamp(.5rem, 1.3vw, .85rem);
    --header-h:   calc(var(--nav-h) + var(--nav-gap) * 2);

    --maxw: 1180px;
    color-scheme: light dark;
}

/* ---- Dark ----------------------------------------------------------------
   Night over the valley, not a grey dashboard: the paper goes to the same
   navy the tail is painted in, so the livery still reads. */
:root[data-theme="dark"] {
    --bg:        #071426;
    --bg-alt:    #0A1B31;
    --surface:   #0D2138;
    --surface-2: #10273F;
    --line:      #1E3556;
    --line-soft: #17293F;
    --ink:       #EEF2F8;
    --muted:     #9CACC4;
    --faint:     #64768F;
    --accent:    var(--am-blue-300);
    --accent-ink:#071426;
    --shadow:    0 1px 1px rgba(0, 0, 0, .22),
                 0 4px 10px -4px rgba(0, 0, 0, .34),
                 0 18px 40px -20px rgba(0, 0, 0, .5);
    --shadow-lg: 0 1px 1px rgba(0, 0, 0, .26),
                 0 10px 24px -10px rgba(0, 0, 0, .42),
                 0 36px 80px -30px rgba(0, 0, 0, .62);
}

@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
        --bg:        #071426;
        --bg-alt:    #0A1B31;
        --surface:   #0D2138;
        --surface-2: #10273F;
        --line:      #1E3556;
        --line-soft: #17293F;
        --ink:       #EEF2F8;
        --muted:     #9CACC4;
        --faint:     #64768F;
        --accent:    var(--am-blue-300);
        --accent-ink:#071426;
        --shadow:    0 1px 2px rgba(0, 0, 0, .3), 0 10px 30px -14px rgba(0, 0, 0, .6);
        --shadow-lg: 0 28px 70px -24px rgba(0, 0, 0, .7);
    }
}

/* ============================ RESET ====================================== */
*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; -webkit-tap-highlight-color: transparent; }
/* scroll-padding keeps an in-page #hash from landing underneath the pinned
   header — without it the heading you jumped to sits behind the bar. */
html { scroll-behavior: smooth; -webkit-text-size-adjust: 100%; scroll-padding-top: var(--header-h); }
html, body { max-width: 100%; overflow-x: clip; }

body {
    font-family: var(--font-body);
    background: var(--bg);
    color: var(--ink);
    font-size: 1.02rem;
    line-height: 1.65;
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
}

img, svg, video { display: block; max-width: 100%; height: auto; }
a { color: inherit; text-decoration: none; }
button, input, select, textarea { font: inherit; color: inherit; }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 4px; }

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

/* ---- The devices that dressed the dark sections: REMOVED ------------------
   Three things lived here and all three are gone, because the thing they were
   painted on is gone. The site no longer has a navy section for them to sit on
   — see SECTION SEAMS and .band below — and every one of them was designed
   against navy specifically:

     the ruled-feather rails   red → rosa mexicano, measured off the logo by
                               tools/make-stripes.py and run down both edges.
                               Red rails down a white page are not a quieter
                               version of this; they are a different, louder
                               thing.
     the eagle watermark       6% white through mark.svg. On white it is white
                               on white — it does not fade out gracefully, it
                               simply stops existing.
     the marigold serpent      a frieze along the top edge of every .band.

   The generators (tools/make-stripes.py, tools/make-serpent.py) and the SVGs
   they emit are all still in the repo. If a dark section ever comes back, so
   can these — pull them out of git history rather than redrawing them. What
   must NOT happen is a recolour: a red rail retinted pale grey to survive on
   white is exactly the invented ornament the top of this file spent two
   rewrites removing. */

/* ---- The greca ------------------------------------------------------------
   The stepped fret off Mixtec and Zapotec stonework — the mosaics at Mitla are
   the ones everybody has seen. Generated by tools/make-greca.py from a grid,
   not drawn: it is built entirely out of right angles, which is the reason it
   can be generated at all and the reason it sits beside the ruled-feather
   device without either looking out of place.

   Two placements, both automatic so no page has to remember them:

     .section--alt   a faint tiled field, the pale sections given a texture
     .footer         a band across the top edge, closing the page

   Painted through a mask like everything else here, so the colour is set by
   `background` and follows the theme. */
/* THE TILED GRECA FIELD IS OFF. The mask, the generator (tools/make-greca.py)
   and the token (--greca-tile) all stay; only the rule that painted it is
   gone, so restoring it is one block rather than a rebuild.

   Why: the brief moved to a smooth, premium, Apple-ish surface, and a repeating
   fret is the one thing on this page that argues with that. It had already been
   pulled from 5% to 2% to stop it competing with the cards, and at 2% behind a
   grid of white panels it was still legible as a PATTERN — which means it was
   still doing the thing that made it a problem, only more faintly. A texture
   that has to be almost invisible to be tolerable is not earning its place.

   This is the VA's motif and the VA asked for it twice, so it is switched off
   rather than deleted, and this note is here so the next reader knows it was a
   deliberate trade against the new brief and not an oversight. The tricolour
   .rule, the eagle .mark and the fleet photography still carry the identity. */

/* The footer's greca crown is removed with them. It was a course of red
   masonry straddling the edge of a navy footer; over a white footer it is a
   red stripe across the page. The tiled greca field on .section--alt stays —
   see above — because a tint at 2% is texture, not ornament.

   The gutter reservation that used to sit here went too: `.footer > .wrap`,
   `.section--ink > .wrap` and `.band > .wrap` padded their columns inward to
   clear the rails at each edge. With no rails there is nothing to clear, and
   the extra inches were making those sections measurably narrower than every
   other section on the page. */

/* ============================ ASSISTIVE ==================================
   The two rules that exist only for a reader who is not looking at the page.
   Both used to be filed under whichever component happened to need them
   first — .sr-only next to the route card's arrow, .skip under the header —
   and both were nearly lost when those components were replaced. Nine pages
   depend on .skip and four on .sr-only, so they live on their own now. */
.sr-only {
    position: absolute; width: 1px; height: 1px;
    padding: 0; margin: -1px; overflow: hidden;
    clip-path: inset(50%); white-space: nowrap; border: 0;
}

.skip {
    position: absolute; left: -9999px; top: 0;
    background: var(--accent); color: var(--accent-ink);
    padding: .7rem 1.15rem; border-radius: 0 0 var(--radius-sm) 0; z-index: 100;
}
.skip:focus { left: 0; }

/* ============================ BRAND MARKS ================================
   What is left after the hand-drawn ornament came out: the tricolour, and the
   airline's own symbol.

   The mark is a monochrome CSS MASK over a plain background colour, so one
   file serves every placement and both themes — set `color` on the element and
   it follows. Give it a `background-image` instead and it will be stuck black.
   ========================================================================= */

/* La bandera. Hard stops at the thirds — this is a flag, and a flag does not
   have a gradient in it.

   It used to run full-bleed across the top of every page, above the bar. It is
   drawn along the top edge of the bar itself now, inside the island's overflow
   so its own corner radius clips it. Positioned rather than in flow, so it
   sits over the bar's blur instead of being a row above it. */
.flagline {
    position: absolute; top: 0; left: 0; right: 0; z-index: 2;
    height: var(--flagline-h);
    background: linear-gradient(90deg,
        var(--mx-green) 0 33.333%, #FFF 0 66.666%, var(--mx-red) 0);
    /* A hairline under the whole strip. Without it the flag's middle third —
       which IS white — disappears into a light island and the tricolour reads
       as two disconnected bars with a gap between them. */
    box-shadow: inset 0 -1px 0 color-mix(in srgb, var(--am-navy) 16%, transparent);
}
/* ---- The lockup, and the Connect sub-brand --------------------------------
   Aeroméxico runs its regional operation as Aeroméxico Connect — a second
   lockup, the same eagle, the word CONNECT set under the wordmark. The VA asked
   for the virtual equivalent, and it is not an invention here: the E190 in
   data.js already flies `livery: 'Aeromexico Connect'` on regional and feeder
   sectors, released at Cadet, and there is already a Connect event for newer
   pilots. Connect is what a cadet starts on. This gives that a name and a mark.

   BUILT FROM TYPE, NOT FROM THEIR LOGO FILE. The VA sent the real Aeroméxico
   Connect lockup as a picture. Shipping that would be shipping the airline's
   registered wordmark as artwork, which is the provenance problem the serape
   note already walks through — and it would be the only raster brand asset on
   a site that draws its mark from a traced SVG and sets everything else in
   type. So this is the nav's own wordmark treatment with a third line added:
   Fraunces for the name, the letter-spaced sans beneath it for the sub-brand.
   Same eagle, our typography.

   Sub-brand centred UNDER the wordmark rather than beside it, which is the one
   structural thing carried over from the real lockup — it is what makes a
   sub-brand read as a division of the airline rather than as a second airline
   standing next to it. */
.lockup { display: inline-flex; align-items: center; gap: .8rem; }
.lockup .mark { width: 3rem; color: var(--am-blue); flex: none; }
.lockup__type { display: grid; justify-items: center; line-height: 1; min-width: 0; }
.lockup__type b {
    font-family: var(--font-display); font-weight: 800;
    font-variation-settings: 'SOFT' 50, 'WONK' 1;
    font-size: clamp(1.15rem, 2.4vw, 1.5rem); letter-spacing: -.02em;
    color: var(--ink);
}
.lockup__type span {
    margin-top: .34rem;
    font-size: clamp(.6rem, 1.1vw, .7rem); font-weight: 700;
    letter-spacing: .3em; text-transform: uppercase; color: var(--muted);
    /* The tracking pushes the last letter off its own centre; the padding puts
       the word back over the wordmark above it. */
    padding-left: .3em;
}
/* Inline-flex by default so it can sit in a line of type. Standing on its own
   it needs to be a block, or it shares a line with whatever came before it —
   which on the home two-up put the eyebrow and the lockup side by side. */
.lockup--block { display: flex; }
.lockup--lg .mark { width: 4rem; }
.lockup--lg .lockup__type b { font-size: clamp(1.4rem, 3vw, 1.95rem); }

/* On a dark ground the eagle goes to the on-dark blue, like every other place
   the mark is used against navy. */
:root[data-theme="dark"] .lockup .mark { color: var(--am-blue-300); }
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) .lockup .mark { color: var(--am-blue-300); }
}

/* ---- The crest ------------------------------------------------------------
   The tricolour with a crest in the centre band. THE CREST IS OURS, NOT THE
   NATION'S, and that is a deliberate substitution rather than an approximation.

   The VA asked for the flag "with the eagle and snake in the middle", which is
   the escudo nacional. This file already refused that once, in the note that
   used to sit above the serpent frieze: the eagle-and-serpent of the coat of
   arms is a state emblem whose reproduction is regulated under the Ley sobre
   el Escudo, la Bandera y el Himno Nacionales, and a virtual airline has no
   claim on it. That is not a style opinion and it does not change because the
   airline would like it to.

   What is here instead gives the same picture honestly: the Caballero Águila
   off Aeromexico-Symbol.webp, standing in the white band where the escudo
   stands. An eagle on the tricolour, and it is the airline's own eagle — which
   is a better answer than the borrowed one, because it says Aeromexico rather
   than saying Mexico with Aeromexico's name underneath it.

   Sized off the crest's WIDTH, not its height. The mark is 1.31:1 and the
   centre band of a 7:4 flag is 2.33 units wide; at 30% of the flag the mark is
   2.1 units and sits inside the band with air, which is roughly the proportion
   the real device keeps. Sized by height it spills into the green and the red
   at any value large enough to read. */
.flag-crest {
    display: inline-grid; place-items: center;
    width: var(--crest-w, 6.5rem); aspect-ratio: 7 / 4;
    border-radius: 5px;
    background: linear-gradient(90deg,
        var(--mx-green) 0 33.333%, #FFF 0 66.666%, var(--mx-red) 0);
    /* The hairline is load-bearing for the same reason it is on .rule: the
       middle band is white and would otherwise have no edge on white paper. */
    box-shadow: inset 0 0 0 1px rgba(15, 27, 46, .16);
}
.flag-crest .mark { width: 34%; color: var(--am-blue); }
.flag-crest--lg { --crest-w: 8.5rem; }

.flag {
    width: 1.6rem; height: 1.1rem; border-radius: 3px; flex: none;
    background: linear-gradient(90deg,
        var(--mx-green) 0 33.333%, #FFF 0 66.666%, var(--mx-red) 0);
    box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .14);
}

/* ============================ TYPE ======================================= */
h1, h2, h3, h4 {
    font-family: var(--font-display);
    font-weight: 700;
    font-variation-settings: 'SOFT' 40, 'WONK' 1;
    line-height: 1.12;
    letter-spacing: -.018em;
}
h1 { font-size: clamp(2.4rem, 6vw, 4.1rem); font-variation-settings: 'SOFT' 60, 'WONK' 1; }
h2 { font-size: clamp(1.8rem, 3.6vw, 2.7rem); }
h3 { font-size: clamp(1.18rem, 1.8vw, 1.42rem); }

/* The eyebrow carries the tricolour tick. It appears above almost every
   section on the site, so this one detail ties the whole thing to Mexico
   without any single page having to shout about it. */
.eyebrow {
    display: inline-flex; align-items: center; gap: .6rem;
    font-size: .72rem; font-weight: 700; text-transform: uppercase;
    letter-spacing: .16em; color: var(--muted);
}
/* The hairline is load-bearing, not a nicety: the middle band of the flag is
   white, and on warm paper a bare white band disappears into the page and
   leaves a green dash and a red dash with a hole between them. The outline
   gives the white something to be white against. Same reason .flag has one. */
.eyebrow::before {
    content: ""; width: 1.6rem; height: 5px; border-radius: 2px; flex: none;
    background: linear-gradient(90deg,
        var(--mx-green) 0 33.333%, #FFF 0 66.666%, var(--mx-red) 0);
    box-shadow: inset 0 0 0 1px rgba(15, 27, 46, .20);
}
:root[data-theme="dark"] .eyebrow::before { box-shadow: none; }

.lede  { font-size: clamp(1.04rem, 1.5vw, 1.19rem); color: var(--muted); max-width: 62ch; }
.muted { color: var(--muted); }
.faint { color: var(--faint); }
.mono  { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.num   { font-family: var(--font-mono); font-variant-numeric: tabular-nums; letter-spacing: -.02em; }

/* Was a gradient clipped into the text. Now it is simply the livery red —
   the class name is kept because the pages already use it. On the navy hero
   red goes muddy, so there it takes the marigold instead. */
.grad-text { color: var(--am-red); }
/* .band and .section--ink were navy; on white the livery red already reads. */

/* ============================ LAYOUT ===================================== */
.wrap    { width: 100%; max-width: var(--maxw); margin-inline: auto; padding-inline: clamp(1.15rem, 4vw, 2rem); }
.section { padding-block: clamp(3.5rem, 8vw, 7rem); }
.section--tight { padding-block: clamp(2.5rem, 5vw, 4rem); }

/* ---- SECTION SEAMS -------------------------------------------------------
   A page here alternates white and the pale tint, three or four times over.
   Each of those changeovers used to be a knife edge — one row of pixels where
   #FFFFFF became #F7F8FA and the greca field started mid-pattern — and the
   result read as a stack of boxes somebody had shuffled together rather than
   one page. The colours are four percent apart; the SEAM is what you saw, not
   the tint.

   So the tint now ramps in and out over `--seam`, and the texture ramps with
   it (the pseudo-element's own background is the gradient, painted through the
   greca mask, so one mask layer still does the masking). Inside the section's
   own padding, so no copy ever sits in the ramp.

   Where the neighbour is DARK, the hard edge is the right answer and is kept:
   navy against white is the airline's own device, and the band and the footer
   already meet it with a marigold greca crown. A soft ramp there would put a
   4rem sliver of white above that crown and read as a gap. Same for two pale
   sections in a row, which must not develop a white stripe between them.

   `:has()` is what identifies those neighbours. Without it (a browser older
   than 2023) every edge simply ramps, which is loose rather than broken. */
.section--alt,
.section--ink {
    position: relative; isolation: isolate;
    --seam-top: var(--seam);
    --seam-bot: var(--seam);
    background: linear-gradient(180deg,
        var(--bg) 0,
        var(--bg-alt) var(--seam-top),
        var(--bg-alt) calc(100% - var(--seam-bot)),
        var(--bg) 100%);
}
/* --alt and --ink are now the same tint, so the neighbour rules have to see
   them as one thing: two tinted sections in a row must not develop a white
   stripe between them, and neither must a tinted section sitting under the
   hero or over the footer. Written with :is() rather than by enumerating
   every pairing, which at two classes plus .band is nine selectors a side. */
:is(.section--alt, .section--ink, .band) + :is(.section--alt, .section--ink),
.hero + :is(.section--alt, .section--ink) { --seam-top: 0px; }

:is(.section--alt, .section--ink):has(+ :is(.section--alt, .section--ink, .band)),
:is(.section--alt, .section--ink):has(+ .footer),
:is(.section--alt, .section--ink):has(+ [data-site-footer]),
:is(.section--alt, .section--ink):has(+ .frame-host),
:is(.section--alt, .section--ink):last-child { --seam-bot: 0px; }

/* A tight section has less padding than the ramp wants, so it gets a shorter
   one rather than a ramp running under its first line of type. */
:is(.section--alt, .section--ink).section--tight { --seam: clamp(1.25rem, 3vw, 2.25rem); }
/* ---- The navy section: REMOVED -------------------------------------------
   `.section--ink` used to paint itself --am-navy and dissolve into its pale
   neighbours over --ink-seam, and about forty lines of overrides followed it
   to make cards, tables, tags, stats and eyebrows legible on that navy.

   All of it is gone. The brief is now a white site: no section paints itself
   dark, and the only dark ground left on the page is the hero's photography,
   which is a photograph rather than a colour. Dark is what the DARK THEME is
   for — flip the theme and every surface here goes navy together, which is
   the coherent version of what the ink sections were doing one at a time.

   The class name survives so no page has to be re-marked-up, and it now
   resolves to the same tint as `.section--alt` (see above). If you want a
   section to stand out from its neighbours, that tint plus the cards inside
   it is the mechanism — not a second colour.

   Do not reintroduce a per-section dark ground without also reintroducing the
   overrides that made it survivable. Half of that pairing is how you get white
   text on white paper. */

.head { max-width: 60ch; margin-bottom: clamp(2rem, 4vw, 3rem); }
.head .eyebrow { display: inline-flex; margin-bottom: .85rem; }
.head p { margin-top: .9rem; }

/* A hairline-opened column needs only enough gutter to separate two rules; a
   card needs enough for the shadow to fall into and for the white to read as
   a discrete object. Hence the wider gap since the cards came back. */
.grid { display: grid; gap: clamp(1.1rem, 2.2vw, 1.5rem); }
.grid-2 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 20rem), 1fr)); }
.grid-3 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 16.5rem), 1fr)); }
.grid-4 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 13rem), 1fr)); }

/* ============================ COMPONENTS ================================= */

/* --- Buttons ---
   Sentence case. The old buttons were UPPERCASE at .13em tracking, which is
   the single most generic control on the internet; at this size the words are
   also easier to read. */
.btn {
    display: inline-flex; align-items: center; justify-content: center; gap: .55rem;
    padding: .86rem 1.7rem; border-radius: 999px; border: 1px solid transparent;
    font-size: .95rem; font-weight: 700; letter-spacing: -.005em;
    cursor: pointer; white-space: nowrap;
    transition: transform .25s var(--ease), background .25s var(--ease),
                border-color .25s var(--ease), color .25s var(--ease), box-shadow .25s var(--ease);
}
.btn:hover  { transform: translateY(-2px); }
.btn:active { transform: translateY(0); }
.btn svg    { width: 1.05em; height: 1.05em; flex: none; }

.btn--primary { background: var(--am-blue); color: #fff; box-shadow: var(--shadow); }
.btn--primary:hover { background: var(--am-red); box-shadow: 0 14px 34px -14px rgba(216, 16, 47, .6); }

.btn--ghost { background: transparent; color: var(--ink); border-color: var(--line); }
.btn--ghost:hover { border-color: var(--ink); background: var(--surface); }

/* Both of these were painted for a dark ground: white-on-navy and a
   translucent white outline. They are still used on the hero, which IS dark
   (a photograph), and in the closing band, which no longer is. So they read
   the ground rather than assuming it: default to the light-ground version,
   and restore the on-dark version inside the hero. Seven pages use them and
   none has to be re-marked-up. */
.btn--light { background: var(--am-blue); color: #fff; box-shadow: var(--shadow); }
.btn--light:hover { background: var(--am-red); color: #fff; }

.btn--onink { background: transparent; color: var(--ink); border-color: var(--line); }
.btn--onink:hover { border-color: var(--ink); background: var(--surface); }

/* The .hero overrides that restored the on-dark versions of these are gone
   with the dark hero. index.html now asks for .btn--primary and .btn--ghost
   there directly, which is what it always meant. */

.btn--sm { padding: .6rem 1.2rem; font-size: .86rem; }

/* --- Cards ---
   THESE ARE CARDS AGAIN, ON THE VA'S INSTRUCTION. Read this before stripping
   them back out, because the paragraphs this replaces argued the other way.

   What was here: the first build put every piece of information in a bordered,
   rounded, shadowed box — six for "why fly with us", three for the hubs, four
   for the stats, one per aircraft. A page built entirely from boxes reads like
   a settings screen, so all of it was stripped to hairline-opened columns set
   in type, the way a print column is.

   The VA has now asked for the opposite, having seen a site they want this one
   to feel like. Be honest about what is different rather than pretending the
   old objection did not apply:

     - a card is a SURFACE now, not a rectangle. It is the page's own white,
       lifted off the ground by a shadow mixed from the navy, closed by a
       hairline that is barely there. The old version drew a hard border and
       let the box do the work;
     - there is exactly one radius (--radius) and one shadow (--shadow) across
       every card on the site, so a grid of them reads as one material rather
       than as six separate widgets;
     - the hover is a 3px lift, not a colour change — the card behaves like a
       physical thing.

   The rule that DOES survive from the old note: a card holds a discrete thing
   — an aircraft, a rank, a person, a counted figure, a partner. It does not
   hold a run of prose. Body copy still sets as a column against the page. If
   you are about to wrap a paragraph in one of these, don't. */
.card {
    background: var(--surface);
    border: 1px solid var(--line-soft);
    border-radius: var(--radius);
    padding: clamp(1.25rem, 2.4vw, 1.6rem);
    box-shadow: var(--shadow);
    display: flex; flex-direction: column;
    transition: transform .3s var(--ease), box-shadow .3s var(--ease),
                border-color .3s var(--ease);
}
/* Doubled up as `.card.card--hover` on purpose, and do not simplify it back.
   Every card on the site also carries [data-reveal], whose resting state is
   `[data-reveal].is-in { transform: none }` — the same (0,2,0) weight as a
   single-class hover, declared several hundred lines further down. At equal
   weight the later rule wins, so a one-class hover lifts nothing once the card
   has been revealed, which is every card the visitor can actually see. */
.card.card--hover:hover {
    transform: translateY(-3px);
    box-shadow: var(--shadow-lg);
    border-color: color-mix(in srgb, var(--accent) 30%, var(--line));
}
.card h3 { margin-bottom: .5rem; }
.card p  { color: var(--muted); font-size: .96rem; }

/* The glyph sits on a tint of the accent — a small surface inside a larger
   one. It is 10% of the accent rather than a saturated chip: at this size a
   solid blue square competes with the heading next to it. */
.card__icon {
    width: 2.6rem; height: 2.6rem; border-radius: var(--radius-sm);
    display: grid; place-items: center;
    margin-bottom: 1rem; flex: none;
    background: color-mix(in srgb, var(--accent) 10%, transparent);
    color: var(--accent);
}
.card__icon svg { width: 1.3rem; height: 1.3rem; }

/* The card's footer link, ruled off from the body above it. Borrowed shape:
   a quiet hairline, the label in the accent, and a chevron that steps out on
   hover so the affordance is motion rather than an underline. */
.card__foot {
    margin-top: auto; padding-top: 1rem;
    border-top: 1px solid var(--line-soft);
}
.card__foot a {
    display: inline-flex; align-items: center; gap: .45rem;
    font-size: .86rem; font-weight: 700; color: var(--accent);
}
.card__foot a svg,
.card__foot a [data-icon] { width: 1em; height: 1em; transition: transform .25s var(--ease); }
.card__foot a:hover [data-icon],
.card__foot a:hover svg { transform: translateX(3px); }

/* --- Chips / pills --- */
.chip {
    display: inline-flex; align-items: center; gap: .45rem;
    padding: .34rem .82rem; border-radius: 999px;
    background: color-mix(in srgb, var(--accent) 10%, transparent);
    color: var(--accent);
    font-size: .74rem; font-weight: 700; letter-spacing: .01em;
}
.chip--live { background: color-mix(in srgb, var(--am-jade) 15%, transparent); color: #0B7049; }
:root[data-theme="dark"] .chip--live { color: #45C48C; }
.chip--onink { background: rgba(255, 255, 255, .13); color: #fff; }

/* A row of them. Used where a list is a CHECKLIST — something you scan to see
   whether it applies to you — rather than something you read. */
.chips { display: flex; flex-wrap: wrap; gap: .5rem; }
.chips .chip { font-size: .82rem; padding: .46rem 1rem; }

.dot-live {
    width: .45rem; height: .45rem; border-radius: 50%; background: currentColor;
    animation: pulse 2s var(--ease) infinite;
}
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: .3; } }

/* --- Stat --- */
.stat__num {
    font-family: var(--font-display); font-weight: 800;
    font-variation-settings: 'SOFT' 60, 'WONK' 0;
    font-size: clamp(2.1rem, 4.5vw, 3rem); line-height: 1;
    letter-spacing: -.03em; font-variant-numeric: tabular-nums;
}
.stat__label {
    display: block; margin-top: .6rem;
    font-size: .72rem; font-weight: 700; text-transform: uppercase;
    letter-spacing: .15em; color: var(--muted);
}
/* What the figure actually counts. A number this size invites a guess, and the
   guess is usually wrong — "hours" could be this month's or all time. */
.stat__note { display: block; margin-top: .5rem; font-size: .85rem; color: var(--muted); line-height: 1.45; }

/* --- Rule ---
   Was a length of hand-drawn greca. It is the tricolour now: real colours,
   hard stops, no invented geometry. The hairline is load-bearing — the middle
   band is white and would otherwise vanish into the page. */
.rule {
    height: 5px; width: 108px; border-radius: 3px;
    background: linear-gradient(90deg,
        var(--mx-green) 0 33.333%, #FFF 0 66.666%, var(--mx-red) 0);
    box-shadow: inset 0 0 0 1px rgba(15, 27, 46, .18);
}
/* A DANGLING SELECTOR LIVED HERE and was a real bug, not untidiness: the line
   read `:root[data-theme="dark"] .rule,` with a trailing comma and no block,
   so the parser joined it to the next rule in the file. Every `.rule` in dark
   mode was getting `.table-scroll`'s surface, border, radius and shadow, and
   rendering as a faint grey box instead of the flag. It was invisible while no
   `.rule` sat anywhere you would notice; the page headers put one on a
   picture, and there it was.

   What it was left over from is the note that used to follow it: nothing on
   this site sat on a dark ground any more, so the dark override was dropped
   and only its selector survived. Something does again — every page header —
   and that case is handled in PAGE HEADERS rather than by theme, because a
   header is dark in BOTH themes. */

/* --- Table --- */
/* Now a framed panel, on the same instruction as the cards, and this is the
   one place the old note already allowed it: a table IS genuinely a surface.
   Same white, same radius, same shadow, so a table and a card sitting in the
   same column read as the same material. */
.table-scroll {
    overflow-x: auto;
    background: var(--surface);
    border: 1px solid var(--line-soft);
    border-radius: var(--radius);
    box-shadow: var(--shadow);
}
table { width: 100%; border-collapse: collapse; min-width: 40rem; }
thead th {
    text-align: left; padding: .95rem 1.15rem; white-space: nowrap;
    font-size: .7rem; font-weight: 700; text-transform: uppercase; letter-spacing: .14em;
    color: var(--muted); border-bottom: 1px solid var(--ink);
}
tbody td { padding: .95rem 1.15rem; border-bottom: 1px solid var(--line-soft); font-size: .94rem; }
tbody tr:last-child td { border-bottom: 0; }
tbody tr { transition: background .2s var(--ease); }
tbody tr:hover { background: color-mix(in srgb, var(--accent) 5%, transparent); }

/* ============================ EMPTY STATE ================================
   THE RULE: a section with no data says so. It does not invent a row to fill
   itself, and — the change here — it no longer silently deletes itself either.

   The site already refused the first half. data.js and crew.js both carry a
   null contract, and every feed on this site would rather render nothing than
   render a plausible-looking figure nobody flew. That part stands and is not
   negotiable: an invented pilot, a sample sector or a placeholder hour on a
   page whose whole argument is "every hour here was flown" would undo the
   argument.

   What was wrong was the other half. A section that calls .remove() when its
   feed is quiet leaves a page that is silently shorter than it should be — the
   visitor cannot tell the difference between "this airline does not do events"
   and "the events feed is down", and neither can the person who has to debug
   it. So the feed sections say the true thing instead: there is nothing here
   yet.

   Dashed rather than solid, because that is the one border on this site that
   means "not content". A solid card at this size reads as a thing that failed
   to load; a dashed one reads as a space waiting to be filled. */
.empty {
    display: grid; gap: .55rem; justify-items: center; text-align: center;
    padding: clamp(2.25rem, 6vw, 3.5rem) clamp(1.15rem, 4vw, 2rem);
    background: var(--surface);
    border: 1px dashed var(--line);
    border-radius: var(--radius);
    color: var(--muted);
}
.empty b {
    font-family: var(--font-display); font-weight: 700;
    font-size: 1.12rem; color: var(--ink);
}
.empty p { font-size: .94rem; max-width: 46ch; line-height: 1.6; }
.empty a { color: var(--accent); font-weight: 700; }

/* ============================ TWO-UP =====================================
   Lifted from aeromexico.com's own ContentTwoUp, which is the component that
   airline's site is mostly BUILT from: a photograph on one side, a short title,
   one sentence and a button on the other, alternating side down the page, some
   of them on white and some on the brand's blue.

   Two shapes there and both are kept, because they do different jobs:

     --square   the photograph runs to the page's outer edge. Reads as a
                spread. Use it where the picture is the point.
     --circle   the photograph is a disc inside its half, with air around it.
                Reads as a portrait. Use it where the WORDS are the point and
                the picture is there to give the section a face.

   What is NOT copied is their copy. The paragraphs on that page describe a
   rewards programme this airline does not run, and a virtual airline writing
   "your points never expire" underneath a disclaimer saying it is not that
   airline is the exact failure this whole file has been avoiding. What
   travelled is the SHAPE of the writing — a four-word title, one sentence that
   says the thing, one link — filled with what is already true here.

   The media is a real <img>, not their background-image. Their version cannot
   be given alt text and does not participate in lazy loading; ours is the same
   fleet photography the hero and the fleet page use, addressed by type so the
   markup never carries a URL that data.js could change underneath it. */
.twoup { position: relative; display: grid; align-items: center; }
.twoup--tint { background: var(--bg-alt); }

/* NOTHING IS CROPPED — the same rule the hero stage is built around, and the
   first version of this component broke it three separate ways.

   The fleet photographs are 1920x886, a ratio of 2.167. What was here:

     --square   a 4/3 box with object-fit: cover, which throws away about 39%
                of the width. Both wingtips.
     --square   above 62rem, `aspect-ratio: auto; min-height: 26rem` with
                `height: 100%` — so the box took whatever height the TEXT
                column happened to be and cropped the aeroplane to fit it. The
                crop changed with the length of the copy beside it.
     --circle   a square box, which throws away about 54%. That is most of an
                aeroplane.

   The fix is the hero panel's: give the box the photograph's OWN ratio, and
   `cover` becomes an exact fit — no crop and no letterbox, at any width, with
   no dependence on the text beside it. --photo-ratio is a variable so a source
   of a different shape can say so rather than being cropped to this one. */
.twoup__media { position: relative; min-width: 0; }
.twoup__media img {
    width: 100%; height: auto; display: block;
    aspect-ratio: var(--photo-ratio, 1920 / 886);
    object-fit: cover;
    /* The tinted ground is what shows while an off-site image is in flight, so
       the panel holds its shape instead of flashing white. */
    background: var(--surface-2);
}

/* --circle IS GONE, and this note is here so it is not casually re-added. A
   disc is a square box, and the only photography this site has is 2.167:1 —
   there is no version of a circle that holds one of these without cutting the
   aeroplane in half. It looked right in the reference because the pictures
   there are of PEOPLE, shot square.

   If squarish photography ever arrives, bring it back as a variant that sets
   --photo-ratio: 1 and states that requirement out loud. Until then a shape
   that can only be filled by breaking the site's oldest rule is not a shape
   this component should offer. */

.twoup__text { padding: clamp(2.25rem, 6vw, 4rem) clamp(1.15rem, 4vw, 2rem); }
.twoup__text > * + * { margin-top: 1rem; }
.twoup__title { font-size: clamp(1.6rem, 3vw, 2.3rem); }
.twoup__intro { color: var(--muted); max-width: 46ch; line-height: 1.6; }
.twoup__text .btn { margin-top: 1.6rem; }

@media (min-width: 62rem) {
    .twoup { grid-template-columns: 1fr 1fr; }
    /* The image half is the FIRST column by default and the second when the
       section is flipped. Written with `order` rather than by reordering the
       markup, so every two-up on a page has the same source order — the media
       first, then the words — and a screen reader gets one shape, not two. */
    .twoup--right .twoup__media { order: 2; }
    .twoup__text {
        padding-block: clamp(3rem, 6vw, 5rem);
        max-width: 40rem;
    }
    /* Hug the middle: the text column sits against the photograph rather than
       floating in the centre of its own half. */
    .twoup:not(.twoup--right) .twoup__text { padding-inline: clamp(2.5rem, 5vw, 4.5rem) clamp(1.5rem, 4vw, 3rem); }
    .twoup--right .twoup__text { margin-left: auto; padding-inline: clamp(1.5rem, 4vw, 3rem) clamp(2.5rem, 5vw, 4.5rem); }
    /* No aspect override here on purpose. The image keeps the photograph's
       ratio at every width; it is the TEXT column that grows, and
       `align-items: center` on the section centres the picture against it. */
}

/* ========================= THE APPLY TAB =================================
   A tab down the right edge that opens a drawer along the foot of the screen.
   The one piece of furniture on this site that follows you between pages, so
   it carries the single action the whole site is asking for and nothing else.

   A DISCLOSURE, NOT A DIALOGUE. No overlay, no focus trap, nothing underneath
   goes inert: you can ignore it and keep reading. That is the difference
   between this and the pop-up the same shape is usually used for.

   Sits under the header (z-index 60) and over the page. The drawer is pinned
   to the bottom so it never covers the thing you are reading, which is at the
   top of the viewport. */
.applytab { position: fixed; inset: 0; z-index: 55; pointer-events: none; }
.applytab > * { pointer-events: auto; }

/* ---- The tab --------------------------------------------------------------
   Vertical, on the right, in the livery red. `writing-mode` rather than a
   rotate: the text stays selectable and the box sizes itself to the words, so
   a longer label does not need the height recalculating by hand. */
.applytab__tab {
    position: absolute; right: 0; top: 50%; transform: translateY(-50%);
    display: flex; align-items: center; gap: .55rem;
    writing-mode: vertical-rl;
    padding: 1.1rem .5rem;
    border: 0; border-radius: .6rem 0 0 .6rem;
    background: var(--am-red); color: #FFF; cursor: pointer;
    font-family: var(--font-text); font-size: .72rem; font-weight: 800;
    letter-spacing: .14em; text-transform: uppercase;
    box-shadow: -4px 0 18px -8px color-mix(in srgb, var(--am-navy) 70%, transparent);
    transition: padding-right .25s var(--ease), background .25s var(--ease);
}
.applytab__tab:hover { padding-right: .8rem; background: var(--am-navy); }
.applytab__tab:focus-visible { outline: 2px solid var(--am-marigold); outline-offset: 3px; }
/* The arrow points the way the drawer will come from: up, out of the foot. */
.applytab__tab svg { width: .9rem; height: .9rem; transform: rotate(-90deg); }
.applytab.is-open .applytab__tab { display: none; }

/* ---- The drawer -----------------------------------------------------------
   Full width along the foot. The tricolour along its top edge is the same
   device the header wears, so the two read as the same airline's furniture. */
.applytab__drawer {
    position: absolute; left: 0; right: 0; bottom: 0;
    background: var(--surface);
    border-top: 1px solid var(--line);
    box-shadow: 0 -18px 50px -24px color-mix(in srgb, var(--am-navy) 70%, transparent);
    animation: applyUp .32s var(--ease-rise) both;
}
.applytab__drawer::before {
    content: ""; position: absolute; left: 0; right: 0; top: 0; height: 4px;
    background: linear-gradient(90deg,
        var(--mx-green) 0 33.333%, #FFF 0 66.666%, var(--mx-red) 0);
    box-shadow: inset 0 -1px 0 color-mix(in srgb, var(--am-navy) 16%, transparent);
}
@keyframes applyUp { from { transform: translateY(100%); } to { transform: none; } }
@media (prefers-reduced-motion: reduce) {
    .applytab__drawer { animation: none; }
    .applytab__tab { transition: none; }
}

.applytab__inner {
    display: flex; align-items: center; gap: clamp(1rem, 3vw, 2.5rem);
    flex-wrap: wrap;
    padding-block: clamp(1.1rem, 2.6vw, 1.6rem);
}
.applytab__say { flex: 1 1 22rem; min-width: 0; }
.applytab__head {
    margin: 0; font-family: var(--font-display); font-weight: 600;
    font-size: clamp(1.15rem, 2.6vw, 1.6rem); letter-spacing: -.02em; color: var(--ink);
}
.applytab__sub {
    margin: .35rem 0 0; font-size: .92rem; line-height: 1.55;
    color: var(--muted); max-width: 52ch;
}
.applytab__do { display: flex; flex-wrap: wrap; gap: .7rem; align-items: center; }
/* The close sits in the corner of the drawer rather than in the flow, so the
   sentence and the buttons keep the width they would have had without it. */
.applytab__close {
    position: absolute; top: .7rem; right: .7rem;
    width: 2.1rem; height: 2.1rem;
}
.applytab__close svg { width: .9rem; height: .9rem; }

@media (max-width: 47.99rem) {
    /* Low, and lower than it was. A phone's thumb lives at the bottom, and the
       middle of the right edge is where a scrollbar and a browser's own
       back-swipe gesture both are — but 22% put the tab across the home page's
       CTA row once the hero went full-screen and centred its content. At 10%
       it sits below the buttons and above the photo credit. */
    .applytab__tab { top: auto; bottom: 10%; transform: none; }
    .applytab__inner { padding-right: 2.6rem; }
    .applytab__do { width: 100%; }
    .applytab__do .btn { flex: 1 1 auto; justify-content: center; }
}

/* ============================ NAV ========================================
   THE HEADER IS AN ISLAND: a rounded bar floating with air on all four sides,
   rather than a full-width strip ruled off from the page.

   THE HOST IS WHAT IS PINNED, NOT `.nav`. Read this before moving
   `position: sticky` back down a level. A sticky element can only travel
   inside its own parent's border box, and the host is exactly as tall as the
   bar plus its gap — so sticky on `.nav` has nowhere to go and the header
   scrolls off the top of the page with everything else. Pinning the HOST puts
   the island in the viewport's frame, where it stays for the whole scroll.

   The host is also what supplies the gap: padding on the host, not margin on
   the bar, so the bar's own box is exactly the island and `overflow: hidden`
   on it clips the tricolour and the open menu to the corner radius.

   What went when the island came in:
     * the scrim under the bar — an island has nothing to dissolve into, and a
       fade hanging off a rounded thing reads as a shadow it does not cast;
     * the full-width navy-to-red edge under the bar — a ruled line across the
       page is the opposite of floating;
     * the flagline as a separate strip above the bar — it is drawn inside the
       island's top edge now, clipped by its own radius. */
[data-site-nav] {
    position: sticky; top: 0; z-index: 60;
    padding: var(--nav-gap) clamp(.55rem, 2.2vw, 1.1rem);
    /* The host spans the page and must not swallow clicks either side of the
       island. Restored on the bar itself. */
    pointer-events: none;
}
[data-site-nav] > * { pointer-events: auto; }

.nav {
    position: relative; z-index: 1;
    max-width: var(--maxw); margin-inline: auto;
    border-radius: clamp(14px, 1.5vw, 20px);
    overflow: hidden;
    background: color-mix(in srgb, var(--bg) 84%, transparent);
    backdrop-filter: blur(20px) saturate(140%);
    -webkit-backdrop-filter: blur(20px) saturate(140%);
    border: 1px solid color-mix(in srgb, var(--line) 80%, transparent);
    box-shadow: 0 6px 22px -14px color-mix(in srgb, var(--am-navy) 55%, transparent),
                0 1px 3px -1px color-mix(in srgb, var(--am-navy) 18%, transparent);
    transition: background .3s var(--ease), box-shadow .35s var(--ease),
                border-color .3s var(--ease);
}
/* THE HERO'S STRIPES, AT THE ENDS OF THE BAR. The same measured rules
   (stripes.svg, stripes-mirror.svg) flush to each end of the island and ragged
   inwards, drawn in once as the page opens. At the tile's own height, so the
   bar shows a slice of about five rules rather than forty squeezed into 64px,
   and faint: they are the island having the airline's grain in it, not a
   graphic competing with the links. `overflow: hidden` on the island trims
   them to its corners. */
.nav::before {
    content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none;
    --nav-rule-w: clamp(4rem, 12vw, 10rem);
    background: var(--nav-rule, var(--ink));
    opacity: .1;
    -webkit-mask:
        url('/assets/img/stripes-mirror.svg') no-repeat left  center / var(--nav-rule-w) var(--stripe-tile),
        url('/assets/img/stripes.svg')        no-repeat right center / var(--nav-rule-w) var(--stripe-tile);
            mask:
        url('/assets/img/stripes-mirror.svg') no-repeat left  center / var(--nav-rule-w) var(--stripe-tile),
        url('/assets/img/stripes.svg')        no-repeat right center / var(--nav-rule-w) var(--stripe-tile);
    animation: navRules 1.4s cubic-bezier(.62, 0, .18, 1) .2s both;
}
/* Growing the mask's width from nothing: the rules run out from each end. */
@keyframes navRules {
    from { -webkit-mask-size: 0 var(--stripe-tile), 0 var(--stripe-tile);
                   mask-size: 0 var(--stripe-tile), 0 var(--stripe-tile); }
}
@media (prefers-reduced-motion: reduce) { .nav::before { animation: none; } }

/* Once the page has moved the island takes on its own weight: near-opaque, so
   the content running under it stops showing through as texture, and lifted
   further off the page. */
.nav.is-stuck {
    background: color-mix(in srgb, var(--bg) 95%, transparent);
    border-color: var(--line);
    box-shadow: 0 16px 40px -20px color-mix(in srgb, var(--am-navy) 70%, transparent),
                0 3px 12px -6px color-mix(in srgb, var(--am-navy) 28%, transparent);
}
:root[data-theme="dark"] .nav {
    border-color: color-mix(in srgb, #FFF 12%, transparent);
    box-shadow: 0 8px 26px -16px rgba(0, 0, 0, .8);
}
:root[data-theme="dark"] .nav.is-stuck {
    border-color: color-mix(in srgb, #FFF 18%, transparent);
    box-shadow: 0 18px 44px -20px rgba(0, 0, 0, .9), 0 3px 12px -6px rgba(0, 0, 0, .5);
}
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) .nav {
        border-color: color-mix(in srgb, #FFF 12%, transparent);
        box-shadow: 0 8px 26px -16px rgba(0, 0, 0, .8);
    }
    :root:not([data-theme="light"]) .nav.is-stuck {
        border-color: color-mix(in srgb, #FFF 18%, transparent);
        box-shadow: 0 18px 44px -20px rgba(0, 0, 0, .9), 0 3px 12px -6px rgba(0, 0, 0, .5);
    }
}

/* THE ISLAND FLOATS OVER THE PICTURE. The opening picture is pulled up by the
   height of the sticky block and given it back as padding, so nothing inside it
   moves — only the picture grows upward, under the bar.

   EVERY PAGE HAS ONE NOW: the home page's hero, and the sky header on the other
   seven. Both are matched here, and both must be, because `.is-over-hero` puts
   white type in the bar the moment either exists — a page whose picture was not
   pulled up leaves that white type on the white page above it, which is a bar
   with no links in it. Anything that becomes a third opening picture goes in
   this selector on the day it is written. */
main > :is(.hero--live, .skyhead):first-child {
    margin-top: calc(var(--header-h) * -1);
}
main > .hero--live:first-child { padding-top: calc(var(--header-h) + clamp(4rem, 9vw, 6rem)); }
main > .skyhead:first-child { padding-top: calc(var(--header-h) + clamp(2.5rem, 6vw, 4rem)); }

/* ---- The bar, while it is ON the photograph -------------------------------
   Glass rather than paper: barely there, so the aeroplane runs behind it, and
   lit for a dark ground because that is what it is standing on — whatever the
   theme is doing to the rest of the page.

   EVERY COLOUR HERE IS STATED. The bar normally takes theme tokens, which is
   right everywhere it sits on the page's own paper; over a photograph in light
   mode those tokens are near-black text on a dark picture. Declared after the
   .is-stuck block so it wins for as long as the hero is under the bar, and
   dropped the moment you scroll past the hero — at which point the ordinary
   stuck treatment takes over and the bar becomes paper again.

   site.js adds .is-over-hero; the class only ever appears on the home page. */
[data-site-nav].is-over-hero .nav {
    background: color-mix(in srgb, #FFF 9%, transparent);
    border-color: color-mix(in srgb, #FFF 26%, transparent);
    box-shadow: 0 10px 34px -22px rgba(7, 20, 38, .75);
}
[data-site-nav].is-over-hero .nav__brand { color: #FFF; }
[data-site-nav].is-over-hero .nav { --nav-rule: #FFF; }
[data-site-nav].is-over-hero .nav::before { opacity: .2; }
[data-site-nav].is-over-hero .nav__links a { color: color-mix(in srgb, #FFF 76%, transparent); }
[data-site-nav].is-over-hero .nav__links a:hover,
[data-site-nav].is-over-hero .nav__links a[aria-current="page"] { color: #FFF; }
[data-site-nav].is-over-hero .icon-btn {
    color: color-mix(in srgb, #FFF 80%, transparent);
    border-color: color-mix(in srgb, #FFF 30%, transparent);
}
[data-site-nav].is-over-hero .icon-btn:hover { color: #FFF; border-color: #FFF; }
/* The Crew Center pill is navy, which is invisible against a night approach.
   On the photograph it goes white. */
[data-site-nav].is-over-hero .nav__actions .btn--primary {
    background: #FFF; color: var(--am-navy); border-color: #FFF;
}
[data-site-nav].is-over-hero .nav__actions .btn--primary:hover {
    background: var(--am-marigold); color: var(--am-navy); border-color: var(--am-marigold);
}
/* The open menu is a panel of its own and has to stay readable, so it keeps
   the page's paper rather than going glass with the bar. */
[data-site-nav].is-over-hero .nav__menu { background: var(--bg); }
[data-site-nav].is-over-hero .nav__menu a { color: var(--ink); }
.nav { transition: background .3s var(--ease), box-shadow .35s var(--ease),
                   border-color .3s var(--ease), color .3s var(--ease); }

/* The island supplies its own gutter, and it is tighter than the page's: the
   bar is already inset from the viewport by the host's padding, so the full
   page gutter on top of that leaves the brand marooned in the middle. */
.nav__inner {
    display: flex; align-items: center; gap: 1.5rem; height: var(--nav-h);
    padding-inline: clamp(.9rem, 2.4vw, 1.5rem);
}
/* `color` is stated because the lockup inside is painted with currentColor and
   this is an <a>, which would otherwise take a link colour. --ink is the
   theme's own text colour, so the mark is near-black on paper and near-white
   on navy without a second rule for the dark theme. */
.nav__brand {
    display: flex; align-items: center; gap: .7rem; margin-right: auto;
    min-width: 0; color: var(--ink);
}
/* The Caballero Águila, painted through assets/img/mark.svg as a mask so one
   file serves every placement and both themes. The ratio is the traced
   artwork's own — re-trace and this must move with it. */
/* ---- The lockup -----------------------------------------------------------
   THE AIRLINE'S OWN LOGO, crest and wordmark as one piece of artwork, supplied
   by the VA. It replaces the nav's old assembly of .mark plus two lines of
   type, which was an approximation of this.

   Painted through a MASK, exactly as .mark is: the file is white on
   transparent, so its alpha channel is the shape and `currentColor` is the
   ink. One file serves both themes and every placement, and there is no second
   copy of the artwork to keep in step.

   SIZED BY HEIGHT. The ratio is the cropped file's own, so setting a height is
   enough and the width falls out of it — which is what a lockup in a bar wants,
   since the bar is what fixes the height.

   The two files are the supplied PNGs cropped to their ink and flattened to
   white, because everything but the alpha is thrown away by the mask anyway.

   On whether this may ship at all, since the Connect note below argues the
   opposite: that note refuses to ship the real AEROMÉXICO CONNECT lockup
   because it is Aeroméxico's own registered sub-brand wordmark. This is not
   that. It is Aeromexico Virtual's lockup for Aeromexico Virtual's own name,
   handed over by the VA that owns it. */
.brandmark {
    display: block; flex: none;
    aspect-ratio: 938 / 225;
    background: currentColor;
    -webkit-mask: url('/assets/img/lockup.png') no-repeat left center / contain;
            mask: url('/assets/img/lockup.png') no-repeat left center / contain;
}
/* The VA supplied a STACKED lockup too — crest over the name, in a near
   square. It is not shipped, because nothing on the site wants that shape: the
   two places the lockup appears are both horizontal (a bar and a footer
   column already fighting to stay short on a phone), and the favicon has to
   read at 16px, where a wordmark is a smudge and the crest alone is the right
   answer. A 70 KB asset nothing references is weight for nothing. If a
   placement for it turns up, crop it the same way and add the variant here. */

.mark {
    display: block; flex: none;
    aspect-ratio: var(--mark-ratio, 2778 / 2118);
    background: currentColor;
    -webkit-mask: url('/assets/img/mark.svg') no-repeat center / contain;
            mask: url('/assets/img/mark.svg') no-repeat center / contain;
}
.nav__brand .brandmark { height: clamp(1.55rem, 3.6vw, 1.95rem); }
/* The .wordmark rules that were here are gone with the thing they styled: the
   nav used to assemble the airline's name out of the crest plus two lines of
   type, which was always an approximation of the lockup the VA actually owns.
   That lockup is a file now — see .brandmark, above the NAV block. */
/* The site map gained Home, About us and Staff in the round-2 review, so there
   are eight entries here where there were six. At a fixed 1.9rem gap that
   overran the bar between 992px (where the desktop nav appears) and about
   1150px, pushing the Crew Center button off the end. The gap now scales with
   the viewport and only reaches its old value on a wide screen. */
.nav__links { display: none; align-items: center; gap: clamp(1rem, 1.7vw, 1.9rem); }
.nav__links a {
    font-size: .95rem; font-weight: 600; color: var(--muted);
    transition: color .2s var(--ease); position: relative; padding-block: .35rem;
}
.nav__links a:hover, .nav__links a[aria-current="page"] { color: var(--ink); }
/* HOVER DRAWS A LINE, the hero's stripes at the size of a link: it runs in
   from the left and leaves to the right, so a pointer crossing the bar reads
   as one movement rather than a row of underlines blinking. Not on the current
   page, which already wears the flag. */
.nav__links a:not([aria-current="page"])::after {
    content: ""; position: absolute; left: 0; right: 0; bottom: -.2rem;
    height: 2px; border-radius: 2px; background: currentColor;
    transform: scaleX(0); transform-origin: right center;
    transition: transform .32s var(--ease);
}
.nav__links a:not([aria-current="page"]):hover::after {
    transform: scaleX(1); transform-origin: left center;
}
/* The active tab is marked with the flag, not with a gradient bar. */
.nav__links a[aria-current="page"]::after {
    content: ""; position: absolute; left: 0; right: 0; bottom: -.2rem; height: 3px; border-radius: 2px;
    background: linear-gradient(90deg,
        var(--mx-green) 0 33.333%, #FFF 0 66.666%, var(--mx-red) 0);
}
.nav__actions { display: flex; align-items: center; gap: .55rem; }
/* Below the desktop breakpoint the Crew Center button is dropped: the burger
   menu already carries it, and four controls plus the wordmark do not fit —
   the brand has `white-space: nowrap`, so instead of wrapping it overruns the
   actions and the theme toggle lands on top of the wordmark. */
@media (max-width: 61.99rem) {
    .nav__actions > .btn { display: none; }
}

.icon-btn {
    width: 2.4rem; height: 2.4rem; border-radius: 999px; border: 1px solid var(--line);
    background: transparent; display: grid; place-items: center; cursor: pointer;
    color: var(--muted); transition: color .2s var(--ease), border-color .2s var(--ease);
}
.icon-btn:hover { color: var(--ink); border-color: var(--ink); }
.icon-btn svg { width: 1.05rem; height: 1.05rem; }

.nav__toggle { display: grid; }
.nav__menu {
    display: none; border-top: 1px solid var(--line); background: var(--bg);
    padding: 1.15rem clamp(1.15rem, 4vw, 2rem) 1.6rem;
    /* The header is pinned now, so an open menu is part of the viewport's
       frame rather than part of the page: it has to scroll on its own or a
       short phone in landscape loses the last entries with no way to reach
       them. `overscroll-behavior` stops that scroll handing off to the page
       behind once it bottoms out. */
    max-height: calc(100dvh - var(--header-h)); overflow-y: auto;
    overscroll-behavior: contain; -webkit-overflow-scrolling: touch;
}
.nav__menu.is-open { display: block; }
.nav__menu a {
    display: block; padding: .85rem 0; border-bottom: 1px solid var(--line-soft);
    font-size: 1rem; font-weight: 600;
}
/* `.nav__menu a` is a class AND a type, so it outranks `.btn` and was winning:
   the Crew Center button in the open menu became display:block with zero side
   padding, which put its icon hard against the left edge of the pill and
   wrapped the label onto a second line. That is the "crew center and its icon
   at the extreme edge of the rectangle" the mobile review reported. Restated
   here at equal weight rather than by reordering the file, so a future edit to
   either rule cannot quietly bring it back. */
.nav__menu a.btn {
    display: inline-flex; align-items: center; justify-content: center;
    width: 100%; margin-top: 1.15rem;
    padding: .95rem 1.7rem; border-bottom: 0;
    font-size: .95rem; font-weight: 700;
}

@media (min-width: 62rem) {
    .nav__links  { display: flex; }
    .nav__toggle { display: none; }
    .nav__menu   { display: none !important; }
}

/* ============================ HERO =======================================
   The home page hero is the sky, the stripes and the lockup — see "RULED
   HERO" further down, where the whole of it lives. What is left here is the
   shell it shares with nothing else, kept because `.hero--live` is built on it
   and because `.hero__cta` is used by the closing band on seven other pages.

   Seven heroes have been thrown out of this file, and every one went with the
   rules that composed it rather than being left behind to rot. The list, and
   why each one went, is at the head of FLIGHT DECK HERO. If an eighth is ever
   needed, write it — do not resurrect one of those from git. */
.hero { position: relative; background: var(--bg); color: var(--ink); overflow: hidden; }
.hero h1 { margin-block: 1rem; text-wrap: balance; }
.hero .eyebrow { margin-bottom: .3rem; }
.hero__cta { display: flex; flex-wrap: wrap; gap: .8rem; margin-top: 2rem; }
@media (max-width: 48rem) {
    .hero__cta { margin-top: 1.6rem; gap: .6rem; }
    .hero__cta .btn { flex: 1 1 auto; justify-content: center; }
}

/* A single line of counted facts. The hero used to use this; it now runs its
   own four-figure block, but fleet.html still sets its type counts this way. */
.strip__facts {
    display: flex; flex-wrap: wrap; align-items: center; gap: .3rem 1.2rem;
    font-size: .88rem; color: var(--muted);
}
.strip__facts span { display: inline-flex; align-items: center; gap: 1.2rem; }
.strip__facts span + span::before {
    content: ""; width: 4px; height: 4px; border-radius: 50%;
    background: currentColor; opacity: .45;
}

/* ============================ OPERATING FIGURES ==========================
   Pilots, hours logged, flight reports filed — the airline's real numbers,
   read live from the crew center (see crew.js). Set on navy so they read as a
   statement of record rather than another card grid, and because these are the
   only numbers on the site that change on their own.

   Built on .stat, which already is "a big figure with a label" — it was left
   orphaned when the fabricated figures strip was removed, and this is the
   honest version of the thing it was written for. Only the layout, the rule
   above each figure and the marigold are new.

   NOTE the band is [hidden] in the markup and only revealed once real figures
   arrive. There is no zero state and no skeleton — an airline we could not get
   figures for shows nothing, rather than a row of dashes. See the null
   contract at the top of crew.js.
   ========================================================================= */
.ops__grid { display: grid; gap: clamp(1.6rem, 4vw, 2.4rem); }
@media (min-width: 40rem) { .ops__grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 64rem) { .ops__grid { grid-template-columns: repeat(4, 1fr); } }

.ops__grid .stat {
    background: var(--surface);
    border: 1px solid var(--line-soft);
    border-radius: var(--radius);
    box-shadow: var(--shadow);
    padding: clamp(1.5rem, 3vw, 2rem);
}
/* Bigger than .stat elsewhere: on this band the figures are the content. The
   marigold was chosen to carry on navy; against white it is a highlighter.
   The figures are ink now and the LABEL does the colouring. */
.ops__grid .stat__num { font-size: clamp(2.5rem, 5.5vw, 3.6rem); color: var(--ink); }
.ops__grid .stat__label { color: var(--accent); }

/* Provenance. Deliberately quiet and deliberately present: a live figure with
   no source named is just a number someone might have typed. */
.ops__asof {
    margin-top: clamp(2rem, 4vw, 3rem); font-size: .85rem; color: var(--muted);
    display: flex; flex-wrap: wrap; align-items: center; gap: .55rem;
}
.ops__asof b { color: var(--ink); }

/* ============================ FLEET / ROUTE BITS =========================
   An aircraft used to be a card: a saturated colour panel with the mark on it,
   over a bordered rounded box. Six of those in a grid is six blocks. Each type
   is now a column opened by a RULE in its own livery colour, with the mark
   small above the name — the colour survives, the block does not. Same move as
   the ornament: a band along an edge instead of a filled field. */
/* The per-type colour is held in a variable now rather than being set six
   times as a border-colour. It has three jobs on the card — the rule across
   the top, the mark, and the tail badge on the photograph — and a card that
   lifts on hover has to be able to put the colour BACK after .card--hover
   rewrites border-color on all four sides. `inherit` used to do that; against
   a card with its own border it inherits the grid's colour and the rule goes
   grey. */
.fleet-card {
    --type-color: var(--am-blue);
    display: flex; flex-direction: column; gap: 0;
    padding: 0; overflow: hidden;
    border-top: 3px solid var(--type-color);
}
.fleet-card:nth-child(6n+2) { --type-color: var(--am-jade); }
.fleet-card:nth-child(6n+3) { --type-color: var(--am-clay); }
.fleet-card:nth-child(6n+4) { --type-color: var(--am-turquoise); }
.fleet-card:nth-child(6n+5) { --type-color: var(--am-rosa); }
.fleet-card:nth-child(6n+6) { --type-color: var(--am-cobalt); }
.fleet-card--hover:hover,
.fleet-card.card--hover:hover { border-top-color: var(--type-color); }

/* Holds the mark once there is one. Collapses while there isn't, so the
   coloured rule and the type carry the entry on their own. */
.fleet-card__media {
    background: none; aspect-ratio: auto; display: block; overflow: visible;
}
.fleet-card__media:empty { display: none; }
/* The photograph runs to the card's own edge — the card is the frame, so a
   second inset frame around the image is a box inside a box. The mark-only
   fallback has no edge of its own to meet, so it keeps the body's inset. */
.fleet-card__media:not(:has(.fleet-photo)) {
    padding: clamp(1.15rem, 2.4vw, 1.5rem) clamp(1.15rem, 2.4vw, 1.5rem) 0;
}
.fleet-card__media .mark { width: 3.4rem; opacity: 1; color: var(--type-color); }
.fleet-card__media img { width: 100%; height: auto; }

/* The airframe itself, where there is a photograph of it. These are the VA's
   own sim shots off the community-aircraft gallery — real material, which is
   the only kind this site decorates with.

   Fixed aspect box with object-fit, not `height: auto`: the gallery's shots are
   not all the same ratio (some 1920x886, some 1920x864), and left to their own
   heights a row of three cards has three different photo heights and the type
   names below them stop lining up. The ratio here is the gallery's own, so the
   crop it applies is a hair off the top and bottom of the taller ones rather
   than anything that cuts an aircraft.

   The tinted surface behind is what shows while an off-site image is still in
   flight, so the card holds its shape instead of flashing white. */
.fleet-card__media { position: relative; }
.fleet-card__media .fleet-photo {
    aspect-ratio: 1920 / 886;
    object-fit: cover;
    background: var(--surface-2);
}

/* The mark as a tail badge on the photo, in the type's own livery colour —
   the same colour that opens the card as a rule above it. Small on purpose:
   the photograph is the subject now, and the badge is there to keep the
   per-type colour, not to compete with the aircraft.

   It sits on a light chip rather than straight on the image because these
   shots are mostly sky, and a coloured mark on pale blue at this size is
   unreadable. The chip is near-white in both themes for the same reason —
   it is a surface for the mark, not a themed surface.

   The chip has to be a WRAPPER, not the mark itself: .mark paints by masking
   `background: currentColor`, so a background on that element replaces the
   eagle instead of sitting behind it. */
.fleet-card__media .fleet-badge {
    position: absolute; left: .55rem; bottom: .55rem;
    display: block; padding: .32rem;
    background: rgba(255, 255, 255, .92);
    border-radius: var(--radius-sm);
    box-shadow: 0 1px 3px rgba(15, 27, 46, .22);
}
.fleet-card__media .fleet-badge .mark { width: 1.7rem; }

.fleet-card__body {
    padding: clamp(1.15rem, 2.4vw, 1.5rem);
    display: flex; flex-direction: column; gap: .35rem; flex: 1;
}
.fleet-card__body .type {
    font-family: var(--font-mono); font-size: .72rem; letter-spacing: .08em;
    color: var(--muted); font-weight: 700; text-transform: uppercase;
}
.fleet-card__specs {
    display: flex; flex-wrap: wrap; gap: .35rem 1.15rem; margin-top: auto; padding-top: 1rem;
    font-size: .8rem; color: var(--muted);
}
.fleet-card__specs b { font-family: var(--font-mono); color: var(--ink); font-weight: 600; }

.route-pair { display: flex; align-items: center; gap: .6rem; font-family: var(--font-mono); font-weight: 600; }
.route-pair .arrow { color: var(--faint); }

.tag {
    display: inline-block; padding: .22rem .6rem; border-radius: 6px;
    font-size: .7rem; font-weight: 700; letter-spacing: .04em;
    background: var(--surface-2); border: 1px solid var(--line); color: var(--muted);
}
.tag--hub { background: color-mix(in srgb, var(--am-red) 13%, transparent); border-color: transparent; color: var(--am-red); }
:root[data-theme="dark"] .tag--hub { color: #FF7C90; }

/* --- Rank ladder --- */
.rank {
    display: flex; align-items: center; gap: 1.1rem;
    padding: 1.05rem 0; border-bottom: 1px solid var(--line-soft);
}
.rank:last-child { border-bottom: 0; }
.rank__bars { display: flex; flex-direction: column; gap: 2px; width: 1.6rem; flex: none; }
.rank__bars i { height: 3px; border-radius: 2px; background: var(--accent); display: block; }
.rank__name { font-family: var(--font-display); font-weight: 700; font-variation-settings: 'SOFT' 40, 'WONK' 1; }
.rank__hours { margin-left: auto; font-family: var(--font-mono); font-size: .86rem; color: var(--muted); white-space: nowrap; }

/* --- Steps ---
   Applying really is a sequence — apply, review, callsign, fly — so numbering
   it carries information rather than decorating it, and the numerals stay.
   The filled disc behind each one does not. */
.step { display: flex; gap: 1.1rem; }
.step__n {
    flex: none; min-width: 1.6rem;
    font-family: var(--font-mono); font-size: 1rem; font-weight: 700;
    color: var(--accent); font-variant-numeric: tabular-nums;
}

/* --- Live roster (fed by live.js) --- */
.live-row {
    display: grid; gap: .3rem 1rem; align-items: center;
    grid-template-columns: 1fr auto;
    padding: .85rem 0; border-bottom: 1px solid var(--line-soft);
}
.live-row:last-child { border-bottom: 0; }
.live-row__cs { font-family: var(--font-mono); font-weight: 700; font-size: .9rem; }
.live-row__ac { font-size: .8rem; color: var(--muted); grid-column: 1; }
.live-row__leg { font-family: var(--font-mono); font-size: .82rem; color: var(--muted); text-align: right; grid-row: 1 / span 2; }

/* ============================ CTA BAND ===================================
   The band used to be the gradient at 55% over navy. It is now navy with
   Quetzalcóatl surfacing out of the left edge, and nothing else. */
/* The closing call to action. It was the page's second navy field — wash,
   eagle, serpent crown and all. It is now the same tint as any other quiet
   section, and what makes it read as a CLOSE is centred type and a primary
   button, not a change of colour.

   .band__wash is kept as a rule that paints nothing rather than deleted:
   seven pages carry the div in their markup, and one dead rule costs less
   than seven edits that could each be got wrong. */
.band { position: relative; overflow: hidden; background: var(--bg-alt); color: var(--ink); isolation: isolate; }
.band__wash { display: none; }
.band__tile { display: none; }   /* retired with .hero__grid */
.band__inner { padding-block: clamp(3.5rem, 8vw, 6rem); text-align: center; position: relative; }
.band__inner > .flag-crest { margin: 0 auto clamp(1.1rem, 2.5vw, 1.6rem); }
.band h2 { max-width: 20ch; margin-inline: auto; }
.band p { color: var(--muted); max-width: 52ch; margin: 1.1rem auto 0; }
.band .hero__cta { justify-content: center; }

/* ============================ THE CLOSE ==================================
   Two pieces of the airline's own geometry, at the bottom of every page, doing
   opposite jobs on purpose.

   THE RAIL, FAINT AND BEHIND. The ruled-feather device — tools/make-stripes.py,
   the ragged profile measured off full-logo.webp — down both edges of the
   closing band and the footer at a few percent. This is the treatment the flag
   gets behind the hero and it is the treatment that suits it: at full strength
   on white it is a graphic you look at, and it spent a commit on the hero
   proving that. At 4% it is the paper having something in it.

   Both edges here, not the hero's one. An edge treatment picks a side; a
   texture behind a closing band wants to be symmetrical, or the page finishes
   lopsided.

   THE SERAPE, ON THE SEAM. A 14px woven ribbon across the join between the
   band and the footer — generated in our palette by tools/make-serape.py, see
   that file for why a script rather than the photograph the VA sent.

   It is the one thing down here at full colour, and that is the point: it is
   14 pixels tall and it is a JOIN. Textile trim is what closes a garment, and
   a hard seam between two same-tinted surfaces was the one edge on this page
   that had nothing to say. Everything else at the bottom is a whisper; this is
   a line of colour, and it works because it is the only one. */
.band, .footer { position: relative; isolation: isolate; }
.band::after,
.footer::after {
    content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none;
    opacity: .04;
    background: linear-gradient(180deg,
        var(--am-red) 0, var(--am-rosa) 50%, var(--am-red) 100%);
    --rail-w: clamp(1.7rem, 4.6vw, 3.4rem);
    -webkit-mask:
        url('/assets/img/stripes.svg')        repeat-y right top / var(--rail-w) var(--stripe-tile),
        url('/assets/img/stripes-mirror.svg') repeat-y left  top / var(--rail-w) var(--stripe-tile);
            mask:
        url('/assets/img/stripes.svg')        repeat-y right top / var(--rail-w) var(--stripe-tile),
        url('/assets/img/stripes-mirror.svg') repeat-y left  top / var(--rail-w) var(--stripe-tile);
}
/* On navy the same rail needs more to be there at all. */
:root[data-theme="dark"] :is(.band, .footer)::after { opacity: .09; }
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) :is(.band, .footer)::after { opacity: .09; }
}

/* The ribbon. On the FOOTER's top edge rather than the band's bottom one, so a
   page that has no closing band still gets it — every page has a footer. */
.footer::before {
    content: ""; position: absolute; left: 0; right: 0; top: 0;
    height: var(--serape-h);
    background: url('/assets/img/serape.svg') repeat-x left center / var(--serape-tile);
    z-index: 1; pointer-events: none;
}

/* ============================ FOOTER ===================================== */
/* Light, like the rest of the page. A dark footer is the one black field most
   sites keep, the reference site included — but the brief here is that nothing
   paints itself dark unless the THEME is dark, and a single navy slab under a
   white page reads as a leftover rather than a decision. It separates on a
   hairline and a tint instead. */
.footer {
    position: relative; background: var(--bg-alt); color: var(--muted);
    /* No hairline: the serape ribbon IS the top edge now, and a grey rule
       under a woven band reads as the band having come loose. */
    padding-block: calc(var(--serape-h) + clamp(3rem, 6vw, 4.5rem)) 2.25rem;
}
.footer a { color: var(--muted); transition: color .2s var(--ease); }
.footer a:hover { color: var(--ink); }
.footer__grid { display: grid; gap: 2.5rem; grid-template-columns: 1fr; }
@media (min-width: 48rem) { .footer__grid { grid-template-columns: 1.4fr repeat(3, 1fr); } }
.footer h4 {
    font-size: .72rem; text-transform: uppercase; letter-spacing: .16em;
    font-family: var(--font-body); font-weight: 700;
    color: var(--ink); margin-bottom: 1.05rem;
}
.footer ul { list-style: none; padding: 0; display: grid; gap: .6rem; font-size: .92rem; }
.footer__brand .mark { width: 4rem; margin-bottom: 1.05rem; color: var(--am-blue); }
.footer__brand .brandmark { height: 2.5rem; margin-bottom: 1.05rem; color: var(--ink); }
.footer__wordmark {
    font-family: var(--font-display); font-weight: 800;
    font-variation-settings: 'SOFT' 50, 'WONK' 1;
    font-size: 1.5rem; letter-spacing: -.02em; color: var(--ink); margin-bottom: 1.05rem;
}
.footer__brand p { font-size: .92rem; max-width: 34ch; }
/* Hecho en México — the flag, said plainly, at the bottom of every page. */
.footer__origin {
    display: inline-flex; align-items: center; gap: .6rem; margin-top: 1.3rem;
    font-size: .78rem; font-weight: 700; letter-spacing: .04em; color: var(--ink);
}
.footer__base {
    margin-top: 3rem; padding-top: 1.6rem; border-top: 1px solid var(--line);
    display: flex; flex-wrap: wrap; gap: 1rem; justify-content: space-between;
    font-size: .8rem; color: var(--faint);
}

/* The disclaimer is not decoration — keep it legible. */
.disclaimer { font-size: .78rem; line-height: 1.6; color: var(--muted); max-width: 68ch; }

/* ============================ PAGE TRANSITIONS ===========================
   Every link on this site is a real navigation to a real document — there is
   no router here and there should not be one. What was missing was the frame
   between the two: the old page vanished and the new one appeared, which reads
   as a snap even when the load itself is instant.

   Cross-document view transitions fix exactly that and need no JavaScript. The
   browser snapshots the outgoing page, snapshots the incoming one, and
   animates between them; a browser that does not support it navigates the way
   it always did. There is no fallback to write and nothing to go wrong.

   The chrome is NAMED so it does not take part. Without this the whole
   viewport cross-fades, header and footer included — and a navigation bar
   dissolving into an identical navigation bar is the one thing that would make
   this feel worse rather than better. Named, it is lifted out of the root
   snapshot and simply persists while the content changes underneath it, which
   is the whole effect: the frame stays, the page moves.

   Out goes faster than in comes: 180ms against 340ms. Symmetrical timing reads
   as a dissolve — for a moment neither page is really there. Clearing the old
   one quickly and letting the new one arrive on its own curve reads as the new
   page ARRIVING, which is what actually happened. */
@view-transition { navigation: auto; }

[data-site-nav]    { view-transition-name: site-chrome-top; }
[data-site-footer] { view-transition-name: site-chrome-bottom; }

@keyframes vt-out { to   { opacity: 0; } }
@keyframes vt-in  { from { opacity: 0; transform: translateY(10px); } }

::view-transition-old(root) { animation: vt-out .18s var(--ease) both; }
::view-transition-new(root) { animation: vt-in .34s var(--ease-rise) both; }

/* Asked for less motion: navigate the way the browser always did. `none` here
   is not a degraded transition, it is no transition — which is the honest
   reading of the request. */
@media (prefers-reduced-motion: reduce) {
    @view-transition { navigation: none; }
}

/* ============================ SEARCH & PAGER =============================
   Five sectors a page, and a box to search them. Both exist because the route
   list had grown past the point where scrolling it was reading it.

   The pager states the range in words as well as offering the buttons —
   "Showing 6-10 of 23" — because Previous/Next alone tells you which way you
   can go and nothing about where you are or how much is left. */
.route-controls { margin-bottom: clamp(1.1rem, 2.5vw, 1.5rem); }

.field { display: block; position: relative; }
.field input {
    width: 100%;
    padding: .82rem 1.15rem;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: 999px;
    color: var(--ink);
    font-size: .95rem;
    transition: border-color .2s var(--ease), box-shadow .2s var(--ease);
}
.field input::placeholder { color: var(--faint); }
.field input:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 16%, transparent);
}
/* Safari draws its own clear affordance on type=search and it does not follow
   the theme; the field is short enough that clearing it by hand is no hardship. */
.field input::-webkit-search-decoration,
.field input::-webkit-search-cancel-button { -webkit-appearance: none; }


/* ============================ SKELETON ===================================
   For the second half of the same complaint: a section that is in the layout
   but still waiting on the crew centre used to pop its content in. This is the
   shape of what is coming, held until it arrives.

   WHERE THIS SITS AGAINST THE NO-PLACEHOLDER RULE, because the rule is real
   and this is not a hole in it. What crew.js and hero.js refuse is INVENTED
   DATA — a row of dashes, a sample sector, a plausible figure nobody flew, on
   a page whose whole argument is that every hour on it was flown. A skeleton
   claims nothing. It is a grey rectangle that says "something is loading
   here", and it resolves to one of exactly two things: the real content, or
   the .empty note saying there is none. It never resolves to a number.

   The rule it must obey: only ever shown where the section is ALREADY in the
   layout and a request is genuinely in flight. A skeleton for something that
   turns out not to exist is worse than showing nothing, because it promised. */
.skeleton { display: grid; gap: .8rem; }
.skeleton__row { display: grid; gap: .55rem; }
.skeleton__bar,
.skeleton__block {
    border-radius: var(--radius-sm);
    background: var(--surface-2);
    position: relative; overflow: hidden;
}
.skeleton__bar   { height: .78rem; }
.skeleton__bar--sm { height: .62rem; width: 42%; }
.skeleton__bar--md { width: 76%; }
.skeleton__block { aspect-ratio: 1; border-radius: var(--radius); }

/* One sweep across the whole group rather than a separate animation per bar,
   so a column of them reads as one surface catching the light instead of a
   dozen things blinking out of step. */
.skeleton__bar::after,
.skeleton__block::after {
    content: ""; position: absolute; inset: 0;
    background: linear-gradient(90deg,
        transparent 0, color-mix(in srgb, var(--ink) 6%, transparent) 50%, transparent 100%);
    animation: skeleton-sweep 1.5s linear infinite;
}
@keyframes skeleton-sweep {
    from { transform: translateX(-100%); }
    to   { transform: translateX(100%); }
}
@media (prefers-reduced-motion: reduce) {
    .skeleton__bar::after, .skeleton__block::after { animation: none; }
}

/* ============================ REVEAL =====================================
   What was here: everything on every page rose 14px over 700ms on `--ease`,
   and the stagger between siblings was a setTimeout. Three things were wrong
   with that, and together they are why the page read as a slideshow.

   700ms on a hover curve is a long time to spend covering 14px. The eye
   registers the motion, not the arrival — it floats. The distance is now a
   little longer, the fade is shorter than the move, and the curve puts most
   of the distance in the first third, so a card arrives and stops.

   The delay is a CSS variable rather than a setTimeout. A timer runs on the
   main thread and the transition runs on the compositor, so under load the
   two came apart: a stagger meant to be 80ms behind arrived 300ms behind, or
   out of order. As `transition-delay` the browser schedules the whole run at
   once and it cannot drift.

   And a whole grid was ONE reveal, so six cards appeared as one slab. Grids
   are staggered per child now (see [data-reveal-group] in site.js), which is
   the difference between a section appearing and a section arriving. */
[data-reveal] {
    opacity: 0;
    transform: translateY(var(--reveal-rise, 1.25rem));
    transition: opacity .42s var(--ease-rise) var(--reveal-delay, 0ms),
                transform .62s var(--ease-rise) var(--reveal-delay, 0ms);
}
[data-reveal].is-in { opacity: 1; transform: none; }
/* No will-change here on purpose. A long page carries forty of these, and
   promoting all forty to their own layer before any of them has moved costs
   more than the first frame it would buy. Opacity and transform are promoted
   by the browser when the transition actually starts, which is when it is
   worth paying for. */

/* Variants. A wide figure sliding up reads as a slide deck, so the map and the
   imagery fade on the spot; a section heading is worth a longer arrival than
   the cards under it. */
[data-reveal="fade"] { --reveal-rise: 0px; }
[data-reveal="lift"] { --reveal-rise: 2.25rem; }

@media (prefers-reduced-motion: reduce) {
    [data-reveal] { opacity: 1; transform: none; transition: none; }
}

/* ============================ EMBED FRAMES =============================== */
.frame-host { border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); }
.frame-host iframe { width: 100%; border: 0; display: block; }

/* ============================ THE NETWORK VIEWS ==========================
   /network is one section shown three ways — the globe, the route table and
   the bases — so these are tabs, not three stacked sections. Everything the
   flat Robinson map used to style is gone with it: `.map`, `.map-scroll`,
   `.map__land/arc/pt/label`, the legend and the pan hint. So are the route
   CARDS and the pager under them, and the flagship strip: the cards became
   twenty-three table rows and the flagship figures became the panel that
   opens when you pick a destination on the globe. If you are restoring any of
   it, restore the view it belonged to rather than the rule.
   ========================================================================= */

.viewswitch {
    display: flex; gap: .3rem;
    padding: .3rem;
    margin-bottom: clamp(1.1rem, 2.5vw, 1.6rem);
    background: var(--surface-2);
    border: 1px solid var(--line);
    border-radius: 999px;
    width: max-content; max-width: 100%;
    overflow-x: auto; scrollbar-width: none;
}
.viewswitch::-webkit-scrollbar { display: none; }
.viewswitch__tab {
    display: inline-flex; align-items: center; gap: .5rem;
    padding: .55rem 1.1rem;
    border: 0; border-radius: 999px;
    background: transparent; color: var(--muted);
    font: inherit; font-size: .88rem; font-weight: 700;
    white-space: nowrap; cursor: pointer;
    transition: background .22s var(--ease), color .22s var(--ease);
}
.viewswitch__tab svg { width: 1.05rem; height: 1.05rem; }
.viewswitch__tab:hover { color: var(--ink); }
/* THE SELECTED PILL IS ONE ELEMENT THAT MOVES, not a background that appears
   on one tab and disappears from another. Those two look identical in a still
   and completely different in use: a colour crossfading between two pills
   reads as two things blinking, and a pill that travels reads as one control
   being operated. It is the ::before of the strip, sized and placed from the
   selected tab's own box by `placePill()` in network.html, so nothing here
   assumes how many tabs there are or how wide their labels set.

   It lives INSIDE the scrolling box on purpose: the strip scrolls sideways on
   a phone, `offsetLeft` is measured against it, and a pill positioned against
   anything else would drift away from its tab as you scrolled. */
.viewswitch { position: relative; }
.viewswitch::before {
    content: ""; position: absolute; z-index: 0;
    top: .3rem; bottom: .3rem; left: 0;
    width: var(--pill-w, 0); translate: var(--pill-x, 0) 0;
    background: var(--am-blue); border-radius: 999px;
    box-shadow: 0 1px 3px rgba(10, 29, 59, .22);
    transition: translate .38s var(--ease), width .38s var(--ease);
}
/* Until the first measurement there is no pill — better nothing for one frame
   than a full-width bar sliding in from the left on load. */
.viewswitch:not([style*="--pill-w"])::before { opacity: 0; }
.viewswitch__tab { position: relative; z-index: 1; }
.viewswitch__tab[aria-selected="true"] { color: #fff; }
/* The count rides inside the tab rather than sitting under a heading, which
   is the whole of what "23 sectors" needs to be. */
.viewswitch__n {
    font-family: var(--font-mono); font-size: .76rem; font-weight: 700;
    opacity: .72;
}
:root[data-theme="dark"] .viewswitch::before { background: var(--am-blue-300); }
:root[data-theme="dark"] .viewswitch__tab[aria-selected="true"] { color: var(--am-navy); }
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) .viewswitch::before { background: var(--am-blue-300); }
    :root:not([data-theme="light"]) .viewswitch__tab[aria-selected="true"] { color: var(--am-navy); }
}

/* ---- The panel underneath -------------------------------------------------
   `show()` swaps `hidden` on three panels, which is the right thing for a
   tablist and, on its own, reads as a snap: the globe is simply gone and a
   table is simply there. One animation on the panel that just appeared turns
   the swap into a hand-over, and it is keyed off a class rather than off
   `:not([hidden])` so it re-runs on every switch instead of once. */
.view.is-entering { animation: viewIn .34s var(--ease-rise) both; }
@keyframes viewIn {
    from { opacity: 0; transform: translateY(10px); }
    to   { opacity: 1; transform: none; }
}

/* Asked for less motion: the pill is simply where it belongs and the panel is
   simply there. Not a shorter slide — none. */
@media (prefers-reduced-motion: reduce) {
    .viewswitch::before { transition: none; }
    .view.is-entering { animation: none; }
}

/* ---- The globe (globe.js) -------------------------------------------------
   The STAGE is what the canvas fills, not the sphere. Two things need that:
   the globe slides sideways inside it to clear room for the card, and the
   leader line from the picked dot to the card is drawn in the same canvas, so
   the card has to be over the canvas for the line to reach it.

   Sized by height with the width left to the layout — globe.js takes its
   radius from whichever of the two is smaller, so a wide stage gives a
   centred globe with room either side rather than a taller one. */
.globe-stage {
    position: relative;
    margin-inline: calc(clamp(1.15rem, 4vw, 2rem) * -1);   /* full-bleed of .wrap */
}
.globe-host { width: 100%; height: clamp(23rem, 62vh, 38rem); }
.globe__canvas {
    display: block;
    width: 100%; height: 100%;
    cursor: grab;
    /* A horizontal drag turns the globe; a vertical one scrolls the page past
       it. On a phone this is most of the viewport, and trapping a downward
       swipe inside it would strand the reader. The tilt is not worth that,
       and the arrow keys still give both axes. */
    touch-action: pan-y;
    outline-offset: 4px;
}
.globe-host.is-turning .globe__canvas { cursor: grabbing; }
/* Once something is picked, the globe is a thing you click, not just spin. */
.globe-host.has-pick .globe__canvas { cursor: pointer; }
.globe__canvas:focus-visible { outline: 2px solid var(--accent); }

.globe-hint {
    display: flex; align-items: center; justify-content: center; gap: .45rem;
    margin: 0; min-height: 1.4em;
    font-size: .76rem; color: var(--faint);
    text-transform: uppercase; letter-spacing: .1em; font-weight: 700;
    text-align: center;
}
.globe-hint svg { width: 1rem; height: 1rem; flex: none; }

/* ---- The card that opens on a pick ---------------------------------------
   It comes OUT of the globe: scaled down and pushed towards the sphere to
   start with, so it grows outward from the direction the leader line runs.
   The overshoot in the curve is the same idea as the turn in globe.js — the
   panel is something the globe threw out, and it should land like one.

   Wide screens get a side panel and narrow ones a sheet across the foot of
   the stage; both sit OVER the canvas, because the leader line cannot reach
   a card that is outside it. */
.globe-card {
    position: absolute;
    z-index: 2;
    padding: 1.1rem 1.25rem 1.25rem;
    background: color-mix(in srgb, var(--surface) 88%, transparent);
    -webkit-backdrop-filter: blur(14px);
    backdrop-filter: blur(14px);
    border: 1px solid var(--line);
    border-radius: 14px;
    box-shadow: 0 18px 44px rgba(10, 29, 59, .17);
    opacity: 0;
    transition: opacity .2s var(--ease), transform .34s cubic-bezier(.18, 1.3, .4, 1);
}
.globe-card.is-in { opacity: 1; transform: none; }
.globe-card__close {
    position: absolute; top: .55rem; right: .55rem;
    display: grid; place-items: center;
    width: 1.9rem; height: 1.9rem;
    border: 0; border-radius: 50%;
    background: transparent; color: var(--muted);
    font-size: 1.1rem; line-height: 1; cursor: pointer;
}
.globe-card__close:hover { background: var(--surface-2); color: var(--ink); }
.globe-card__close svg { width: 1rem; height: 1rem; }
.globe-card__eyebrow {
    font-size: .68rem; font-weight: 700; letter-spacing: .12em;
    text-transform: uppercase; color: var(--accent);
}
.globe-card__city { margin: .3rem 0 .1rem; font-size: clamp(1.25rem, 2.6vw, 1.6rem); }
.globe-card__code {
    margin: 0 0 .7rem;
    font-size: .88rem; font-weight: 700; color: var(--muted); letter-spacing: .04em;
}
.globe-card__code i { font-style: normal; color: var(--am-red); padding-inline: .2rem; }
.globe-card__sector + .globe-card__sector {
    margin-top: .9rem; padding-top: .9rem; border-top: 1px solid var(--line);
}
.globe-card__facts { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .55rem .9rem; margin: 0; }
.globe-card__facts div { display: grid; gap: .1rem; }
.globe-card__facts dt {
    font-size: .68rem; font-weight: 700; letter-spacing: .08em;
    text-transform: uppercase; color: var(--faint);
}
.globe-card__facts dd {
    margin: 0;
    font-family: var(--font-mono); font-size: .9rem; font-weight: 600; color: var(--ink);
}
.globe-card .rank-chip { margin-top: .8rem; }

@media (min-width: 900px) {
    .globe-card {
        top: 50%; right: clamp(1rem, 4vw, 3rem);
        width: min(21rem, 34%);
        transform: translate(-1.5rem, -50%) scale(.94);
    }
    .globe-card.is-in { transform: translate(0, -50%); }
}
@media (max-width: 899.98px) {
    .globe-card {
        left: clamp(.75rem, 3vw, 1.5rem); right: clamp(.75rem, 3vw, 1.5rem);
        bottom: clamp(.5rem, 2vw, 1rem);
        transform: translateY(1.25rem) scale(.96);
    }
    .globe-card.is-in { transform: none; }
    .globe-card__facts { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
/* Reduced motion: the card still appears and still carries every figure, it
   just does not fly. globe.js stops the turn and the pulse to match. */
@media (prefers-reduced-motion: reduce) {
    .globe-card { transition: opacity .01s; transform: none !important; }
}

/* ---- The route table ------------------------------------------------------
   Twenty-three sectors is a table. It was a six-column table once, became
   cards because the cards put the airport pair — the thing a pilot scans for
   — at a readable size, and is a table again now that the PAIR is the first
   column and a button: picking it opens that sector on the globe. The card
   layout is not gone, it moved to where one sector is actually being read.

   Columns that are not the pair, the city, the distance or the rank drop out
   below 46rem rather than squeezing: a seven-column table on a phone is a
   column of fragments. Everything dropped is on the globe card. */
#view-routes > .field { max-width: 28rem; margin-bottom: 1.1rem; }
.rtable-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.rtable { width: 100%; border-collapse: collapse; font-size: .9rem; }
.rtable th, .rtable td { padding: .62rem .7rem; text-align: left; vertical-align: middle; }
.rtable th {
    font-size: .68rem; font-weight: 700; letter-spacing: .1em; text-transform: uppercase;
    color: var(--faint); border-bottom: 2px solid var(--ink); white-space: nowrap;
}
.rtable td { border-bottom: 1px solid var(--line); }
.rtable tbody tr { transition: background .16s var(--ease); }
.rtable tbody tr:hover { background: var(--surface-2); }
.rtable__num { text-align: right; }
.rtable th.rtable__num { text-align: right; }
.rtable__pick {
    display: inline-flex; align-items: center; gap: .1rem;
    padding: .2rem .45rem; margin-inline-start: -.45rem;
    border: 0; border-radius: 7px;
    background: transparent; color: var(--ink);
    font: inherit; font-family: var(--font-mono); font-size: .88rem; font-weight: 700;
    letter-spacing: .02em; white-space: nowrap; cursor: pointer;
    transition: background .16s var(--ease), color .16s var(--ease);
}
.rtable__pick i { font-style: normal; color: var(--am-red); padding-inline: .3rem; }
.rtable__pick:hover { background: var(--am-blue); color: #fff; }
.rtable__pick:hover i { color: #fff; }
.rtable__unit { font-weight: 600; opacity: .7; text-transform: none; letter-spacing: 0; }
.rtable__note { margin: .9rem 0 0; font-size: .8rem; color: var(--faint); max-width: 62ch; }
@media (max-width: 46rem) {
    .rtable__opt { display: none; }
    .rtable { font-size: .86rem; }
    .rtable th, .rtable td { padding: .55rem .45rem; }
}

/* A base card offers the same pick the table rows do, so the three views all
   lead to the one place a destination is explained. */
.hub-card__go { margin-top: 1rem; justify-self: start; }

/* A codeshare is someone else's metal, and the table says so inline rather
   than spending a column on a field that is empty for all but two rows. */
.tag--sm { font-size: .62rem; padding: .1rem .4rem; letter-spacing: .08em; }

/* A rank chip: the smallest way to say "you need to be this to file this". */
.rank-chip {
    display: inline-flex; align-items: center; gap: .4rem;
    font-size: .72rem; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
    color: var(--accent); white-space: nowrap;
}
.rank-chip::before {
    content: ""; width: .42rem; height: .42rem; border-radius: 50%;
    background: currentColor; flex: none;
}
/* In a table cell the chip is one of seven columns, not a card's footer. */
.rank-chip--sm { font-size: .66rem; letter-spacing: .05em; }
.rank-chip--sm::before { width: .34rem; height: .34rem; }

/* ---- The bases -----------------------------------------------------------
   Two reviewers said these cards looked plain. They now open on a plate — a
   photograph when the VA has one of its own in data.js, otherwise the crest on
   the livery with the IATA code set into it — and carry the aerodrome's own
   runway count, elevation and terminals under the sector count. */
.hub-card { display: grid; align-content: start; }
.hub-card__plate {
    position: relative; aspect-ratio: 16 / 9; margin: -.4rem 0 1rem;
    border-radius: calc(var(--radius) - .35rem); overflow: hidden;
    background: linear-gradient(145deg, var(--am-blue-600) 0%, var(--am-navy) 68%);
    display: grid; place-items: center;
}
/* The serape weave, at the foot of the plate: the same three stops as the
   flagline, so the base card is tied to the airline's own flag rather than to
   a stock gradient. */
.hub-card__plate::after {
    content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 4px;
    background: linear-gradient(90deg,
        var(--mx-green) 0 33.333%, #FFF 0 66.666%, var(--mx-red) 0);
}
.hub-card__crest {
    position: absolute; inset: 0; display: grid; place-items: center;
    color: #FFF; opacity: .26;
}
.hub-card__crest .mark { width: 46%; }
.hub-card__iata {
    position: relative; z-index: 1; color: #FFF;
    font-size: clamp(1.8rem, 4vw, 2.4rem); font-weight: 700; letter-spacing: .12em;
    text-shadow: 0 2px 12px rgba(7, 20, 38, .55);
}
.hub-card__plate.has-photo { background: var(--surface-2); }
.hub-card__plate img { width: 100%; height: 100%; object-fit: cover; display: block; }
.hub-card__note {
    margin: .6rem 0 0; font-size: .88rem; line-height: 1.55;
    color: color-mix(in srgb, currentColor 62%, transparent);
}

/* ============================ RANK LADDER ================================
   The ranks page. The old ladder was seven rows of name-and-hours, which is
   what the review called "numerical rather than developmental" — it said what
   you needed and never what you got. Each rank is now an entry: what it
   releases, how far it lets you fly, and what is expected of you there. */
.ladder { display: grid; gap: 0; }
.ladder__row {
    display: grid; gap: .4rem 1.6rem;
    grid-template-columns: 1fr;
    padding: clamp(1.3rem, 3vw, 1.9rem) 0;
    border-top: 1px solid var(--line);
}
.ladder__row:last-child { border-bottom: 1px solid var(--line); }
@media (min-width: 48rem) {
    .ladder__row { grid-template-columns: minmax(0, 15rem) minmax(0, 1fr); }
}
.ladder__lead { display: grid; gap: .35rem; align-content: start; }
.ladder__insignia-note {
    font-size: .74rem; letter-spacing: .1em; text-transform: uppercase;
    color: var(--faint); font-weight: 700;
}
.ladder__name {
    font-family: var(--font-display); font-weight: 800; letter-spacing: -.015em;
    font-size: clamp(1.15rem, 2.2vw, 1.4rem); line-height: 1.15;
}
.ladder__hours {
    font-family: var(--font-mono); font-size: .95rem; font-weight: 600; color: var(--accent);
}
.ladder__body { display: grid; gap: .9rem; }
.ladder__body p { margin: 0; color: var(--muted); }
/* "The font size is a bit small, making it a bit hard to read." It was .92rem
   for the duty line and body size for the note, on a page whose whole job is
   to be read carefully. The lead line is now set at reading size in the ink,
   and the duty line stays quiet but no longer shrinks. */
.ladder__note { font-size: 1.06rem; line-height: 1.6; color: var(--ink) !important; }
.ladder__duty { font-size: .98rem; line-height: 1.6; }

/* ---- What a rank gets you ------------------------------------------------
   The review asked for privileges against each rank, and a list of them under
   the rank's own description is where a pilot looks for them. Ruled rather
   than boxed, like everything else here that is information. */
.ladder__perks { display: grid; gap: .5rem; }
.ladder__perks h3 {
    margin: 0; font-family: var(--font-text);
    font-size: .72rem; font-weight: 800; letter-spacing: .14em;
    text-transform: uppercase; color: var(--accent);
}
.ladder__perks ul { list-style: none; margin: 0; padding: 0; display: grid; gap: .1rem; }
.ladder__perks li {
    position: relative; padding: .5rem 0 .5rem 1.5rem;
    font-size: .98rem; line-height: 1.55; color: var(--muted);
    border-top: 1px solid var(--line-soft);
}
.ladder__perks li:first-child { border-top: 0; }
/* The tick is the airline's, not a bullet: this is a list of things you HAVE
   at this rank, which is what a check mark says and a disc does not. */
.ladder__perks li::before {
    content: ""; position: absolute; left: 0; top: .82em;
    width: .7rem; height: .38rem; border: 2px solid var(--accent);
    border-top: 0; border-right: 0; transform: rotate(-45deg);
}
.ladder__perks li b { color: var(--ink); font-weight: 700; }
.ladder__facts {
    display: flex; flex-wrap: wrap; gap: .5rem 1.6rem;
    padding-top: .3rem;
}
.ladder__fact { display: grid; gap: .1rem; font-size: .88rem; }
.ladder__fact i {
    font-style: normal; color: var(--muted);
    font-size: .66rem; text-transform: uppercase; letter-spacing: .13em; font-weight: 700;
}
.ladder__fact b { font-family: var(--font-mono); font-weight: 600; }
.ladder__row.is-appointed { background: var(--surface-2); padding-inline: clamp(1rem, 3vw, 1.6rem); }
:root[data-theme="dark"] .ladder__row.is-appointed { background: var(--surface); }

/* The bars are a rank insignia, not a chart: one more bar per rung. */
.ladder__bars { display: flex; gap: 3px; align-items: flex-end; height: 1.1rem; }
.ladder__bars i { width: 4px; border-radius: 2px; background: var(--accent); display: block; }

/* ---- Rank insignia --------------------------------------------------------
   The epaulette, drawn rather than fetched. A navy board with one to four gold
   bars across it — the flight deck's own grammar, which a pilot reads without
   being told what it means, and which the ascending bar chart that used to sit
   here did not. The two senior grades add the wing; the appointed rank wears
   the crest in place of bars, because it is not a step on the same ladder.

   Kept at a size that works beside the rank name at every width: the board is
   sized in ems off the row, so it scales with the type rather than against it. */
.insignia {
    display: flex; flex-direction: column; justify-content: center; gap: .26em;
    width: 3.4rem; height: 2.2rem; padding: .3em .42em; border-radius: .28rem;
    background: linear-gradient(160deg, var(--am-navy), var(--am-blue-600));
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, .22),
                0 1px 3px rgba(15, 27, 46, .3);
    position: relative; overflow: hidden;
}
.insignia i {
    display: block; height: .2rem; border-radius: 1px;
    background: linear-gradient(90deg,
        var(--am-marigold), color-mix(in srgb, var(--am-marigold) 62%, #FFF), var(--am-marigold));
}
/* The wing: a single gold chevron across the top corner of the board, which is
   how the senior grades are marked without adding a fifth bar that would read
   as a rank that does not exist. */
.insignia--wing::after {
    content: ""; position: absolute; top: -.42rem; right: -.62rem;
    width: 1.5rem; height: 1.5rem; transform: rotate(45deg);
    background: color-mix(in srgb, var(--am-marigold) 88%, transparent);
}
.insignia--crest {
    display: grid; place-items: center; padding: .24rem;
    background: linear-gradient(160deg, var(--am-red), var(--am-navy));
}
/* `.mark` is a masked box that takes its size from a width and its colour from
   currentColor, so a height here would fight its own aspect-ratio. */
.insignia--crest { color: #FFF; }
.insignia--crest .mark { width: 1.6rem; }
@media (prefers-reduced-motion: no-preference) {
    .insignia { transition: transform .3s var(--ease); }
    .ladder__row:hover .insignia { transform: translateY(-2px) rotate(-2deg); }
}

/* ============================ STAFF ======================================
   Four reviewers asked for this page. A role with nobody in it says so — the
   Operations Plan gives every director a remit, and a remit with a vacancy
   against it is more useful to a reader (and more honest) than a filled grid. */
.staff-grid { display: grid; gap: clamp(1.4rem, 3vw, 2rem); }
@media (min-width: 40rem) { .staff-grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 64rem) { .staff-grid { grid-template-columns: repeat(3, 1fr); } }
.staff-grid--exec { grid-template-columns: 1fr; }
@media (min-width: 48rem) { .staff-grid--exec { grid-template-columns: repeat(2, 1fr); } }

/* A person is exactly the kind of discrete item the card note allows, so the
   staff entries take the same surface as everything else rather than staying
   the one hairline grid left on the site. */
.staff {
    display: grid; gap: .55rem; align-content: start;
    background: var(--surface);
    border: 1px solid var(--line-soft);
    border-radius: var(--radius);
    padding: clamp(1.25rem, 2.4vw, 1.6rem);
    box-shadow: var(--shadow);
    transition: transform .3s var(--ease), box-shadow .3s var(--ease);
}
.staff:hover { transform: translateY(-3px); box-shadow: var(--shadow-lg); }

.staff__role {
    font-size: .7rem; font-weight: 800; letter-spacing: .14em; text-transform: uppercase;
    color: var(--accent);
}
.staff__id { display: flex; align-items: center; gap: .9rem; }

/* THE PORTRAIT PLATE. The mobile review reported the staff images as blank,
   and the grey disc with two letters in it deserved that: it reads as a
   photograph that failed to load. It is now the airline's own crest on the
   livery, with the monogram set over it — a designed plate rather than an
   empty slot, so a card with no photograph still looks finished. When the VA
   has real portraits, an <img> dropped inside this same box replaces the
   crest with no other change.

   The IFC avatar is deliberately NOT fetched: Discourse serves those from a
   per-upload URL this repo cannot know, so guessing one would give every card
   a 404 — the reported bug again, with a network request attached. */
.staff__av {
    width: 3rem; height: 3rem; border-radius: 50%; flex: none;
    display: grid; place-items: center; position: relative; overflow: hidden;
    background: linear-gradient(150deg, var(--am-blue-600), var(--am-navy));
    color: #FFF;
    font-family: var(--font-display); font-weight: 800; font-size: 1.1rem;
    border: 1px solid color-mix(in srgb, var(--am-navy) 40%, transparent);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, .2);
}
.staff__av > b { position: relative; z-index: 1; letter-spacing: .02em; }
.staff__av img {
    position: absolute; inset: 0; width: 100%; height: 100%;
    object-fit: cover; z-index: 2;
}
/* The crest sits behind the monogram at low contrast — present enough to be
   the airline's plate, quiet enough that two letters still read over it. */
.staff__av-crest {
    position: absolute; inset: 0; display: grid; place-items: center;
    opacity: .3; color: #FFF;
}
.staff__av-crest .mark { width: 78%; }
/* A vacancy is a DIFFERENT thing from a missing picture, and has to look it:
   no monogram, the crest alone, and the livery drained out of the plate. */
.staff__av--vacant {
    background: var(--surface-2); border-color: var(--line); color: var(--faint);
    box-shadow: none;
}
.staff__av--vacant .staff__av-crest { opacity: .38; color: var(--faint); }

/* The foot of the card: who the role answers to on the left, the way through
   to their IFC profile on the right. The review asked for a profile button on
   every staff member, and a name with an underline was not reading as one. */
.staff__foot {
    display: flex; align-items: center; justify-content: space-between;
    gap: .8rem; flex-wrap: wrap; margin-top: .3rem; padding-top: .9rem;
    border-top: 1px solid var(--line-soft);
}
.staff__ifc { flex: none; }
.staff__ifc svg { width: .85rem; height: .85rem; }
/* The signature block on /about and / carries the same button. */
.quote__ifc { margin-top: 1rem; }
.quote__ifc svg { width: .85rem; height: .85rem; }
.staff__name {
    font-family: var(--font-display); font-weight: 800; letter-spacing: -.015em;
    font-size: 1.22rem; line-height: 1.2;
}
.staff__name a { color: inherit; border-bottom: 1px solid var(--line); }
.staff__name a:hover { color: var(--accent); border-bottom-color: currentColor; }
.staff__vacant { color: var(--faint); font-style: italic; font-size: 1.05rem; font-weight: 500; }
.staff__owns { color: var(--muted); font-size: .94rem; margin: 0; }
.staff__reports { font-size: .8rem; color: var(--faint); font-family: var(--font-mono); }
.staff--exec .staff__av { width: 4rem; height: 4rem; font-size: 1.5rem; }
.staff--exec .staff__name { font-size: clamp(1.4rem, 3vw, 1.75rem); }

/* ============================ TIMELINE ===================================
   Used twice: the first twelve months on /about, and the run-up to an event on
   /events. A numbered rail rather than four more boxes in a grid. */
.timeline { display: grid; gap: 0; }
.timeline__row {
    display: grid; gap: .3rem 1.4rem; grid-template-columns: auto 1fr;
    padding: 1.15rem 0; border-top: 1px solid var(--line);
}
.timeline__row:last-child { border-bottom: 1px solid var(--line); }
.timeline__when {
    font-family: var(--font-mono); font-weight: 700; font-size: .82rem;
    color: var(--accent); text-transform: uppercase; letter-spacing: .08em;
    min-width: 7.5rem; padding-top: .15rem;
}
.timeline__what { margin: 0; }
.timeline__what b { display: block; font-family: var(--font-display); font-weight: 700; margin-bottom: .2rem; }
.timeline__what span { color: var(--muted); font-size: .92rem; }
@media (max-width: 34rem) {
    .timeline__row { grid-template-columns: 1fr; gap: .45rem; }
}

/* ============================ PULL QUOTE =================================
   The CEO's message. Set as a statement, not dropped into another card. */
.quote {
    display: grid; gap: 1.4rem;
    border-top: 3px solid var(--am-red); padding-top: clamp(1.6rem, 3vw, 2.2rem);
}
.quote p {
    font-family: var(--font-display); font-size: clamp(1.2rem, 2.4vw, 1.6rem);
    line-height: 1.42; letter-spacing: -.015em; font-variation-settings: 'SOFT' 50, 'WONK' 1;
    max-width: 46ch; margin: 0; color: var(--ink);
}
.quote p + p { font-size: clamp(1.02rem, 1.6vw, 1.14rem); font-family: var(--font-body); color: var(--muted); }
/* Wraps because the signature gained an IFC profile button beside the name in
   the round-2 review, and three items on one line do not fit a narrow column. */
.quote__by { display: flex; align-items: center; gap: .9rem; flex-wrap: wrap; }
.quote__by b { font-family: var(--font-display); font-weight: 800; }
.quote__by span { font-size: .82rem; color: var(--muted); display: block; font-weight: 500; font-family: var(--font-body); }
/* The signature's avatar is a <span> too, so the rule above was recolouring
   the portrait plate and its crest to the muted grey and shrinking the
   monogram to caption size — which is what made the initials invisible on the
   navy plate. Restated at matching specificity rather than by reordering, so
   an edit to either rule cannot quietly bring it back. */
.quote__by span.staff__av {
    display: grid; color: #FFF;
    font-family: var(--font-display); font-weight: 800; font-size: 1.1rem;
}
.quote__by span.staff__av-crest { display: grid; color: #FFF; }

/* ============================ FACT ROWS ==================================
   A definition list set as ruled rows. Used for the identity table on /about
   and the requirement list on /join, both of which were sets of little boxes. */
.facts { display: grid; gap: 0; margin: 0; }
.facts__row {
    display: grid; gap: .2rem 1.6rem; grid-template-columns: 1fr;
    padding: 1rem 0; border-top: 1px solid var(--line-soft);
}
.facts__row:first-child { border-top: 1px solid var(--line); }
.facts__row:last-child { border-bottom: 1px solid var(--line); }
@media (min-width: 40rem) { .facts__row { grid-template-columns: minmax(0, 13rem) minmax(0, 1fr); } }
.facts dt { font-weight: 700; font-family: var(--font-display); letter-spacing: -.01em; }
.facts dd { margin: 0; color: var(--muted); }
.facts dd b { color: var(--ink); font-weight: 600; }

/* ============================ FOOTER COLUMNS =============================
   The review said the footer is too long on mobile, and it was: four stacked
   columns ran to most of a phone screen on their own. The three link columns
   now pair up below the brand instead of queueing. */
.footer__cols { display: grid; gap: 2rem 1.5rem; grid-template-columns: repeat(2, 1fr); }
@media (min-width: 30rem) { .footer__cols { grid-template-columns: repeat(3, 1fr); } }
@media (min-width: 48rem) {
    .footer__grid { grid-template-columns: minmax(0, 1.15fr) minmax(0, 2fr); }
    .footer__cols { gap: 2.5rem; }
}
@media (max-width: 47.99rem) {
    .footer { padding-block: clamp(2.25rem, 6vw, 3rem) 1.5rem; }
    .footer__grid { gap: 1.9rem; }
    .footer__brand .mark { width: 3.2rem; margin-bottom: .8rem; }
    .footer__brand .brandmark { height: 2.1rem; margin-bottom: .8rem; }
    .footer__brand p { font-size: .88rem; }
    .footer ul { gap: .5rem; font-size: .88rem; }
    .disclaimer { font-size: .72rem; }
}

/* ============================ STACKING TABLES ============================
   A data table inside a horizontally scrolling box is the standard answer on a
   phone and it is a bad one: the reader has to scrub sideways to find the
   column that answers their question, and the header scrolls away while they
   do it. Below 37.5rem a `table.stack` becomes one block per row with each
   cell carrying its own column name, which is the same information in the
   shape a narrow screen can actually read.

   Renderers put the column name on each cell as data-th. A cell with no
   data-th just loses its label, so a table can be converted a column at a
   time. */
@media (max-width: 37.5rem) {
    .table-scroll:has(.stack) { overflow-x: visible; }
    table.stack { min-width: 0; display: block; }
    table.stack thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
    table.stack tbody, table.stack tr, table.stack td { display: block; width: auto; }
    table.stack tr {
        padding: 1rem 0;
        border-top: 1px solid var(--line);
    }
    table.stack tr:last-child { border-bottom: 1px solid var(--line); }
    table.stack td {
        border: 0; padding: .2rem 0;
        display: grid; grid-template-columns: minmax(0, 8.5rem) minmax(0, 1fr);
        gap: .2rem 1rem; align-items: baseline;
    }
    table.stack td::before {
        content: attr(data-th);
        font-size: .66rem; font-weight: 700; letter-spacing: .13em;
        text-transform: uppercase; color: var(--muted);
    }
    table.stack td:empty { display: none; }
}

/* ============================ PANELS =====================================
   The one thing on this site that gets a box.

   The rule at the top of the components section still stands — prose is opened
   by a hairline, not wrapped in a card. But a table of sectors is not prose,
   and floated straight onto the greca field it read as loose rows on a
   patterned background with nothing holding them together. That rule always
   allowed a box around "something that is genuinely a surface (a table, a
   framed embed)". A route list is a table. This is that box, and it is the only
   one: do not put a paragraph in it. */
.panel {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: clamp(1rem, 2.5vw, 1.6rem) clamp(1rem, 3vw, 1.9rem);
    box-shadow: var(--shadow);
}
.panel + .panel { margin-top: clamp(1.2rem, 3vw, 1.8rem); }

/* The panel's own edge is the list's edge, so the first and last rules inside
   it would double it up. */
.panel > .tier-head:first-child  { margin-top: 0; }
/* Inside a panel the sector panels need to sit a step off IT, not off the page. */

/* A table is the other genuine surface, and it was floating too. */
.table-scroll {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: clamp(.4rem, 2vw, 1.1rem) clamp(1rem, 3vw, 1.9rem);
    box-shadow: var(--shadow);
}
/* On the navy sections the panel would be a second dark box on a dark ground;
   there the rules alone carry it, which is what the existing override does. */
/* Stacked, the table is a list of rows and the panel padding is all it needs. */
@media (max-width: 37.5rem) {
    .table-scroll { padding-block: .2rem; }
}

/* ================================ THE SKY ================================
   ONE PICTURE SYSTEM, USED BY EVERY PAGE ON THE SITE.

   The home page opens on a sunrise at altitude with the logo's stripes drawn
   in across it. Every other page opens on a different view out of the same aeroplane —
   over the wing, a city at night, trails climbing, an airfield in lights, a row
   of cabin windows, a fin at dusk, the door. They are not eight designs. They
   are one atmosphere, one set of switches and one coordinate frame, with a
   different generated mask laid into it and a different time of day.

   WHAT IS IN A SKY, back to front:

     .sky           the box. Clips, and is the container the frame measures.
     .sky__frame      ONE 1600x1000 frame, sized to COVER the box.
       .sky__air        the gradient, the sun and the two cloud decks
       .sky__geo        the page's view, as a mask
       .sky__geo--lit   a second mask for anything that emits rather than blocks
     .sky::after      the vignette, and the foot that hands over to the page

   THE FRAME IS THE WHOLE TRICK, and it is worth understanding before touching
   any number below. A gradient's horizon is a percentage of its own element. A
   mask mounted at `cover` is cropped to the box's ratio. Those two agree only
   when the box happens to be 1.6:1 — and a page header is a wide, shallow band
   nowhere near it. Measured on the first build of the home page: at 2560x864
   the flight deck's glareshield sat 4.3% below its own sunrise.

   So nothing is mounted on the box. Everything is mounted inside `.sky__frame`,
   which is 1600x1000 scaled to cover the box and centred, exactly as
   `object-fit: cover` would size a photograph — and every layer inside it is a
   plain `inset: 0` child at `mask-size: 100% 100%`. One coordinate system, no
   cropping maths, and the generators can place a runway's vanishing point on
   the horizon by putting it at y=550 and trusting it. The clipping is done by
   `overflow: hidden` on the box.

   That is also why the cloud tiles are sized in PERCENT here and not in `svh`
   as a first pass had them: inside the frame, a tile's aspect ratio is a fixed
   fraction of a fixed box, so it is right at every viewport by construction
   rather than by a number tuned per component.

   NOTHING IN ANY SKY IS DRAWN. brand.css's standing rule is that no pictorial
   artwork may be invented and that geometry emitted from parameters is the one
   exception. Every mask is emitted:

     tools/make-cloudbank.py    the two cumulus decks
     tools/make-views.py        the seven page views

   All are MONOCHROME MASKS, like mark.svg and greca.svg, so whether a view is
   a dark interior or a lit exterior is decided here. Read those three script
   headers before changing a shape; do not edit the SVGs.

   TWO SWITCHES, `--sky-t` AND `--sky-loop`, AND NO SECOND COPY OF THE END
   STATE. `--sky-t` multiplies every duration and delay in every arrival. At 1
   it runs; at 0 every animation is 0s long and `fill-mode: both` lands it on
   its last frame instantly. `--sky-loop` parks the ambient loops — the cloud
   drift, the instrument glow, the view's float — at their first keyframe,
   which every one of them is written to make its resting state. Reduced motion
   sets both, and so does `[data-still]` (a page opened already scrolled) and
   `[data-away]` (scrolled past, or a background tab), from site.js and
   hero.js. A still sky is therefore the same rules with two numbers changed,
   not a second set of declarations that can drift out of step.
   ========================================================================= */

.sky {
    position: absolute; inset: 0; z-index: -1;
    overflow: hidden; pointer-events: none;
    /* The frame measures itself against this box, in cqw/cqh. */
    container-type: size;
}
/* The defaults live on :root, not on .sky, so `[data-still]`/`[data-away]`
   on the hero or header that CONTAINS a sky reach it by inheritance. Declared
   on .sky itself they overrode the inherited value, and the hero's logo and
   stripes, which sit outside .sky, had no value at all. */
:root { --sky-t: 1; --sky-loop: running; }

/* `object-fit: cover`, written out. The larger of "as wide as the box" and
   "as wide as 1.6 times its height" on each axis, then centred by offsetting
   half the overhang — with `left`/`top` rather than a transform, because
   `transform`, `scale` and `translate` are all spoken for by the animations
   that run on the layers inside. */
.sky__frame {
    --fw: max(100cqw, calc(100cqh * 1.6));
    --fh: max(100cqh, calc(100cqw / 1.6));
    position: absolute;
    width: var(--fw); height: var(--fh);
    left: calc((100cqw - var(--fw)) / 2);
    top: calc((100cqh - var(--fh)) / 2);
}
.sky__frame > *, .sky__air > * { position: absolute; inset: 0; }

/* ---- The atmosphere -------------------------------------------------------
   A single vertical ramp through the whole sky, and every stop is a token so
   one set of rules serves dawn, day, dusk and night. THE HORIZON IS AT 55% and
   the generators hard-code it as y=550 of 1000; move it here and the runway,
   the city and the wing tip all come off it at once.

   It carries a little overscan so the float below can move it without opening
   a gap along the edge the frame happens to fit exactly. */
.sky__air {
    inset: -12px;
    background: linear-gradient(180deg,
        var(--sky-void) 0%, var(--sky-night) 9%, var(--sky-high) 20%,
        var(--sky-mid) 31%, var(--sky-low) 40%, var(--sky-haze) 46.5%,
        var(--sky-blush) 51%, var(--sky-ember) 53.4%, var(--sky-sun) 54.8%,
        var(--sky-flare) 55.8%, var(--sky-pale) 56.8%, var(--sky-deep) 65%,
        var(--sky-dusk) 80%, var(--sky-abyss) 100%);
}

/* The sun. Kept TIGHT: screened over the whole lower sky it stops being a sun
   and becomes haze, and the cloud deck loses the dark it needs to sit on.
   `--sun-x` moves it along the horizon, which is most of what separates one
   time of day from another once the ramp above has been re-tinted. */
.sky__sun {
    background: radial-gradient(var(--sun-w, 42%) var(--sun-h, 13%)
        at var(--sun-x, 50%) 55.2%,
        color-mix(in srgb, var(--sun-core) 82%, transparent) 0,
        color-mix(in srgb, var(--sun-halo) 34%, transparent) 38%,
        transparent 76%);
    mix-blend-mode: screen;
}

/* ---- The weather ----------------------------------------------------------
   Two decks off the same generator, at two scales and two speeds: one layer
   drifting is a moving picture, two at different rates is distance. Both start
   at the horizon and hang below it, because that is where a deck seen from
   above goes; the tiles are trimmed to their own cloud tops by the generator,
   so the top edge of the element IS the top of the weather.

   THE TILE WIDTHS ARE DERIVED. Each is the emitted tile's aspect ratio times
   the band's height, expressed as a fraction of the frame's width — a 6.35:1
   tile in a band 20% of a 1.6:1 frame is 6.35 x 0.20 / 1.6 = 79.4% of the
   frame wide. tools/make-cloudbank.py prints that aspect every time it runs,
   because it MEASURES the tile height off the geometry. Retune the clouds,
   carry the number here, or the deck starts stretching.

   The element holds four tiles and the drift moves it by exactly one, which
   lands on a tile boundary so the loop has no seam. Animating `mask-position`
   instead would repaint the mask every frame. */
.sky__cloud {
    inset: auto auto 23.5% 0; height: 20%;
    width: calc(var(--tile) * 4);
    -webkit-mask-repeat: repeat-x; mask-repeat: repeat-x;
    -webkit-mask-size: 25% 100%; mask-size: 25% 100%;
    animation: skyDrift linear infinite;
    animation-play-state: var(--sky-loop);
    will-change: transform;
}
.sky__cloud--near {
    --tile: 79.4%;                       /* 6.35 x 0.200 / 1.6 */
    -webkit-mask-image: url('/assets/img/cloudbank.svg');
            mask-image: url('/assets/img/cloudbank.svg');
    background: linear-gradient(180deg, var(--cloud-lit) 0, var(--cloud-warm) 19%,
                var(--cloud-mid) 48%, var(--cloud-shade) 100%);
    animation-duration: 96s;
}
/* Further off: a smaller tile, so the same geometry comes out as smaller
   cloud. Its lumps sit in the top third of a tile with a long skirt, so it
   starts on the same horizon and runs on down past 73% — under the lowest
   glass in the flight deck and under the foot of a window. Its own bottom edge
   is a straight horizontal cut and the near deck in front of it has gaps. */
.sky__cloud--far {
    inset: auto auto 27% 0; height: 16.5%;
    --tile: 47.9%;                       /* 4.64 x 0.165 / 1.6 */
    -webkit-mask-image: url('/assets/img/cloudbank-far.svg');
            mask-image: url('/assets/img/cloudbank-far.svg');
    background: linear-gradient(180deg, var(--cloud-far-lit) 0, var(--cloud-far-mid) 22%,
                var(--cloud-far-shade) 38%, var(--sky-deep) 100%);
    opacity: .46;
    animation-duration: 182s;
}

/* ---- The view -------------------------------------------------------------
   The page's own geometry. `--geo` is the file and `--geo-paint` is what it is
   made of, which is the whole reason these are masks: a wing lit from above at
   dawn and a cabin wall at almost no light are the same kind of object here. */
/* A view that BLOCKS light — a wing, a wall, a fin. Masked and painted. */
.sky__geo {
    -webkit-mask: var(--geo) no-repeat center / 100% 100%;
            mask: var(--geo) no-repeat center / 100% 100%;
    background: var(--geo-paint);
}
/* A view that EMITS it — a city, a runway's lights, a trail catching the sun.
   THE BLUR IS ON THIS ELEMENT AND THE MASK IS ON ITS ::before, and that order
   is load-bearing rather than tidy: a filter is applied BEFORE the mask clips,
   so a masked element that blurs itself has the glow clipped straight back to
   the shape it came from and there is no glow left at all. Masking the pseudo
   and blurring the parent lets the light spread past its own edge, which is
   what light does.
   `background` on the element itself is therefore free, and is the diffuse
   haze a city or an approach throws up around the points of it. */
.sky__glow {
    filter: blur(var(--glow-blur, 7px));
    opacity: var(--glow-opacity, 1);
}
.sky__glow::before {
    content: ""; position: absolute; inset: 0;
    -webkit-mask: var(--geo) no-repeat center / 100% 100%;
            mask: var(--geo) no-repeat center / 100% 100%;
    background: var(--geo-paint);
}

/* ---- The close ------------------------------------------------------------
   A vignette so the corners fall away, and a foot that goes to the airline's
   navy — every sky on this site hands over to white paper on a crisp edge
   under the tricolour, and that edge is asserted in tools/test-motion.js. */
.sky::after {
    content: ""; position: absolute; inset: 0; z-index: 1;
    background:
        radial-gradient(128% 86% at 50% 42%, transparent 46%,
            color-mix(in srgb, var(--sky-void) 82%, transparent) 100%),
        linear-gradient(180deg,
            color-mix(in srgb, var(--sky-void) 62%, transparent) 0, transparent 20%,
            transparent 74%, color-mix(in srgb, var(--am-navy) 70%, transparent) 93%,
            var(--am-navy) 100%);
}

/* ---- The times of day -----------------------------------------------------
   Four packs of the same fourteen stops. `--dawn` is the home page's, to the
   value — it is the sky the hero was tuned against and the others are read off
   it rather than invented separately.

   A pack is COLOUR ONLY. The horizon does not move, the sun does not change
   size, and no pack touches a layout number: a time of day that also re-framed
   the picture would mean every generated view had to be checked against four
   geometries instead of one. */
.sky--dawn {
    --sky-void: #02070E; --sky-night: #04101F; --sky-high: #0A1D3B;
    --sky-mid: #0C2C64;  --sky-low: #17479A;   --sky-haze: #4A73A2;
    --sky-blush: #98738C; --sky-ember: #D07D4C; --sky-sun: #F2A03D;
    --sky-flare: #F9CB84; --sky-pale: #8A98B4;  --sky-deep: #46587A;
    --sky-dusk: #1E2B45;  --sky-abyss: #101932;
    --sun-core: #FFE6BC;  --sun-halo: var(--am-marigold);
    --cloud-lit: #FFEBD2; --cloud-warm: #E4BEA2; --cloud-mid: #97899E;
    --cloud-shade: #3B4767;
    --cloud-far-lit: #EBD4C4; --cloud-far-mid: #A9A8BA; --cloud-far-shade: #6E7B95;
}
/* Midday at altitude: the sky above is the deepest blue there is, the warm
   band all but closes up, and the cloud tops go white rather than gold. */
.sky--day {
    --sky-void: #020A1A; --sky-night: #05173A; --sky-high: #0B2A6B;
    --sky-mid: #10409B;  --sky-low: #2064C4;   --sky-haze: #4E8ED8;
    --sky-blush: #86B4DF; --sky-ember: #B9D4EA; --sky-sun: #E6F1F8;
    --sky-flare: #F4FAFD; --sky-pale: #C3D2E2;  --sky-deep: #7E93AC;
    --sky-dusk: #46586F;  --sky-abyss: #2B3B51;
    --sun-core: #FFFFFF;  --sun-halo: #CFE4F6;
    --cloud-lit: #FFFFFF; --cloud-warm: #EAF1F8; --cloud-mid: #B6C2D2;
    --cloud-shade: #61748E;
    --cloud-far-lit: #F1F5FA; --cloud-far-mid: #C5CEDB; --cloud-far-shade: #8D9BAF;
}
/* The other end of the day, and warmer for it: more rosa, less marigold, and
   the blue above already gone to indigo. */
.sky--dusk {
    --sky-void: #050310; --sky-night: #0B0A22; --sky-high: #17163E;
    --sky-mid: #262357;  --sky-low: #45336E;   --sky-haze: #74406F;
    --sky-blush: #A8496A; --sky-ember: #D45D4E; --sky-sun: #F08A3C;
    --sky-flare: #FFC178; --sky-pale: #9D8098;  --sky-deep: #4E4265;
    --sky-dusk: #211C38;  --sky-abyss: #120F24;
    --sun-core: #FFD7A0;  --sun-halo: var(--am-rosa);
    --cloud-lit: #FFD9B4; --cloud-warm: #E0A08E; --cloud-mid: #8C6E8A;
    --cloud-shade: #362F52;
    --cloud-far-lit: #DFB9AE; --cloud-far-mid: #9A86A4; --cloud-far-shade: #5F5878;
}
/* Night, and the one pack with no warm band at all: the horizon is a cold
   line and everything under it is the city's own light, which is why the only
   view that uses this is the one that supplies its own. */
.sky--night {
    --sky-void: #01030A; --sky-night: #030716; --sky-high: #061026;
    --sky-mid: #0A1938;  --sky-low: #10254B;   --sky-haze: #17325F;
    --sky-blush: #1E3C6D; --sky-ember: #27497C; --sky-sun: #35598C;
    --sky-flare: #4A6E9E; --sky-pale: #22385A;  --sky-deep: #101E36;
    --sky-dusk: #080F1E;  --sky-abyss: #03060E;
    --sun-core: #7FA6E8;  --sun-halo: #1B5FC1;
    --cloud-lit: #2E4467; --cloud-warm: #253A5B; --cloud-mid: #172940;
    --cloud-shade: #0A1322;
    --cloud-far-lit: #26395A; --cloud-far-mid: #1A2A44; --cloud-far-shade: #101C2E;
}

/* ---- The loops ------------------------------------------------------------
   The ones that never stop. Every one begins on its resting frame, so pausing
   at 0% — which is what `--sky-loop: paused` does — is a still picture rather
   than a half-finished one. A keyframe list that started at an extreme would
   make the still version subtly wrong in a way nothing would catch. */
@keyframes skyDrift {
    from { transform: translateX(0); }
    to   { transform: translateX(-25%); }
}
/* The aeroplane riding. Five frame-units either way over seventeen seconds —
   small enough that you would not catch it holding still, and it is the whole
   difference between a picture and being in one. Only the VIEW moves: the
   structure does not shift relative to the seat, the horizon does. */
@keyframes skyFloat {
    0%, 100% { translate: 0 0; }
    30%      { translate: 0 -0.5%; }
    70%      { translate: 0 0.5%; }
}
.sky__air {
    animation: skyFloat 17s ease-in-out infinite;
    animation-play-state: var(--sky-loop);
}
@keyframes skyBreathe {
    0%, 100% { opacity: 1; }
    50%      { opacity: .74; }
}

/* ============================== RULED HERO ===============================
   THE SKY, THEN THE STRIPES, THEN THE EAGLE. NO WORDS ON IT AT ALL.

   Eight heroes have been thrown out of this repo. The list is kept because the
   instinct that produced each one comes back:

     1. A two-column white header. Fine, and not memorable.
     2. A drawn poster, one click from photographs of the same airframe.
     3. Those photographs with a marketing tagline laid over them.
     4. The photograph as a panel in the flow, read as a card in the page.
     5. Full bleed and full height with the headline over it — which cost the
        light theme and cropped the aeroplane on a phone.
     6. Words on the page's own paper, photograph in a band beneath them:
        nothing cropped, light in light mode, and quiet.
     7. Back to full bleed with the lockup and the headline on the photograph,
        which bought the presence back and bought #5's two problems with it.

     8. A passenger window, then a move forward into the flight deck. It was
        the hero being about an AEROPLANE; the VA wanted it to be about the
        AIRLINE, and asked for the lines Aeroméxico is known for instead.

   Every one of 1-7 was a fight between TYPE and a PICTURE. There is still no
   type on this one, so it is still dark in both themes for the same reason #8
   was: it is a sunrise at altitude, and nothing on it has to stay legible.

   THE STRIPES ARE MEASURED, NOT DRAWN. They are the ruled-feather device off
   the mark — stripes.svg and stripes-mirror.svg, whose bar lengths
   tools/make-stripes.py reads off full-logo.webp. One block per side, flush to
   the screen edge and ragged towards the lockup, so each bar reads as a line
   arriving from outside the frame. Both are stripes-mirror.svg (flush LEFT);
   the right one is turned round with `scale: -1 1`, so the two sides are one
   set of rules rather than two that could drift apart. They are stretched to the gap between the edge
   and the lockup with the tile's own preserveAspectRatio="none", so the rhythm
   is the logo's at every width and the bar ends go to long tapers.

   White, like the lockup they lead into, ramping from almost nothing at the
   edge to nearly solid at the inner ends — the lines gather as they arrive.

   THE MOVE. Once, and then it holds:

     0.0s  the sky comes up out of black and settles back
     0.45s the stripes draw in from both edges — a clip-path wipe from the
           flush side, so the short bars finish first and the long ones keep
           running in: the ragged profile IS the stagger
     1.55s the lockup settles between them
     2.9s  it is done; the weather drifts, and now and then a glint of sunrise
           runs in along the stripes

   It runs ONCE on load: a hero that re-animates every time you scroll back to
   the top is a hero you learn to scroll past. It is CSS, not script, so
   scripting off still gets the whole sequence. `--sky-t` and `--sky-loop`
   (THE SKY, above) are how it is switched off.

   EVERY LAYER IS OPTIONAL. The hero's own ground is the airline's navy, so a
   mask that will not load, an SVG that 404s or a browser without `mask` leaves
   the lockup on navy — quieter than intended, never broken. */

.hero--live {
    position: relative; isolation: isolate; overflow: hidden;
    display: grid; align-content: center;
    /* The bar floats ON the hero rather than above it, so the hero is pulled
       up under the sticky block and given the height back as padding.
       See `main > .hero--live:first-child` in the NAV block. */
    min-height: clamp(34rem, 100svh, 54rem);
    /* THE BOTTOM PADDING IS WHAT PLACES THE LOCKUP. `align-content: center`
       centres the content in what is left after the padding, so the deeper
       foot lifts it to about 43% of the frame — clear above the horizon at
       55%. Centred properly it lands across the brightest band in the picture,
       which is the one place white artwork on a sunrise cannot be read. */
    padding-block: clamp(4rem, 9vw, 6rem) clamp(9rem, 18vw, 13rem);
    background: var(--am-navy); color: #FFF;
}

/* The field of view settling. TWO ANIMATIONS ON ONE ELEMENT AND NEITHER IS
   `transform`: the settle is `scale` and the float is `translate`, the
   individual transform properties, which compose. Written as two `transform`
   animations the last would simply win. */
.hero--rules .sky__air {
    transform-origin: 50% 47%;
    animation: heroSky calc(3.2s * var(--sky-t)) cubic-bezier(.3, 0, .18, 1) both,
               skyFloat 17s ease-in-out infinite;
    animation-play-state: running, var(--sky-loop);
}
.hero--rules .sky { animation: heroWake calc(1s * var(--sky-t)) var(--ease) both; }

/* THE VIEW CAN BE A FILM. AMV_DATA.video is empty as the site ships, and if
   the VA ever fills it hero.js drops the clip in here and adds this class. The
   film then IS the weather — it inherits the sky's own settle, and the stripes
   and the lockup are untouched. `cover` on a black ground, because a clip and
   a viewport rarely share a ratio and a letterboxed hero is a bug. */
/* The near deck's foot is a straight cut, which the flight deck used to sit
   in front of. With nothing in front of it any more, its shade runs out into
   the sky's own colour at that height, so the deck fades into the dusk under
   it rather than stopping on a line. */
.hero--rules .sky__cloud--near {
    background: linear-gradient(180deg, var(--cloud-lit) 0, var(--cloud-warm) 19%,
                var(--cloud-mid) 44%, var(--cloud-shade) 70%,
                color-mix(in srgb, var(--sky-deep) 22%, var(--sky-dusk)) 100%);
}

.sky__air--film { background: var(--sky-void); }
.sky__air--film > .sky__sun,
.sky__air--film > .sky__cloud { display: none; }
.hero__film { width: 100%; height: 100%; object-fit: cover; }

/* The tricolour closes every sky on the airline's own flag rather than on a
   gradient — the same device the nav island wears along its top edge. */
.hero--live::after, .skyhead::after {
    content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 4px;
    background: linear-gradient(90deg,
        var(--mx-green) 0 33.333%, #FFF 0 66.666%, var(--mx-red) 0);
    z-index: 1;
}

/* ---- What is on it --------------------------------------------------------
   The lockup, the stripes either side of it, and an <h1> that is there for a
   screen reader and a crawler and nobody else. `--logo-w` is here rather than
   on the logo because the stripes are measured off it. */
.hero__content {
    position: relative; z-index: 1;
    display: grid; justify-items: center; text-align: center;
    max-width: 48rem; margin-inline: auto;
    --logo-w: min(78vw, 31rem);
    --rule-gap: clamp(1rem, 3.2vw, 2.6rem);
}

/* THE LOGO, at the size it deserves. Capped in rem as well as vw so a very
   wide screen does not turn it into a banner. Two shadows: a warm bloom, which
   is the sunrise behind it, and a hard dark one, which is what keeps white
   artwork off a pale cloud top. */
.hero__logo {
    width: var(--logo-w);
    color: #FFF;
    filter: drop-shadow(0 0 46px rgba(255, 214, 158, .34))
            drop-shadow(0 8px 34px rgba(2, 6, 14, .75));
    animation: heroLogo calc(1.3s * var(--sky-t)) var(--ease-rise)
               calc(1.55s * var(--sky-t)) both;
}

/* ---- The stripes ----------------------------------------------------------
   Centred on the lockup because they are positioned inside its box: top 50%
   of the content is the middle of the logo, whatever the padding above does.
   Each runs from its inner end, a gap off the lockup, out past the edge of the
   screen; `50vw` includes the scrollbar, so the flush end lands a few pixels
   outside the viewport, and the hero's `overflow: hidden` trims it. */
.hero__rule {
    position: absolute; top: 50%;
    width: calc(50vw - var(--logo-w) / 2 - var(--rule-gap));
    height: clamp(13rem, 50svh, 27rem);
    translate: 0 -50%;
    pointer-events: none;
    -webkit-mask: url('/assets/img/stripes-mirror.svg') no-repeat center / 100% 100%;
            mask: url('/assets/img/stripes-mirror.svg') no-repeat center / 100% 100%;
    /* The glint (a band of sunrise, parked off the flush end at rest) over
       the ramp. */
    background:
        linear-gradient(90deg, transparent 42%,
            color-mix(in srgb, #FFE0B4 90%, transparent) 50%, transparent 58%)
            no-repeat 100% 0 / 300% 100%,
        linear-gradient(90deg,
            color-mix(in srgb, #FFF 5%, transparent) 0,
            color-mix(in srgb, #FFF 34%, transparent) 48%,
            color-mix(in srgb, #FFF 88%, transparent) 100%);
    animation: ruleIn calc(1.7s * var(--sky-t)) cubic-bezier(.62, 0, .18, 1)
               calc(.45s * var(--sky-t)) both,
               ruleGlint 9s ease-in-out calc(3.2s * var(--sky-t)) infinite;
    animation-play-state: running, var(--sky-loop);
}
.hero__rule--l { right: calc(50% + var(--logo-w) / 2 + var(--rule-gap)); }
/* The same rule turned round: mask, ramp, wipe and glint all mirror with it.
   `scale`, not `transform`, so it composes with the centring `translate`. */
.hero__rule--r { left: calc(50% + var(--logo-w) / 2 + var(--rule-gap)); scale: -1 1; }

/* ---- Where to go next -----------------------------------------------------
   A chevron and no word on it. The hero is a full screen, and a full screen
   with nothing visible under the fold is a screen people stop at. Drawn from
   two borders rather than an icon file: it is two lines at right angles, which
   is not worth a request. */
.hero__next {
    position: absolute; left: 50%; bottom: clamp(1.4rem, 3.4vw, 2.3rem);
    z-index: 2; width: 2.6rem; height: 2.6rem; border-radius: 50%;
    display: grid; place-items: center;
    color: color-mix(in srgb, #FFF 58%, transparent);
    transition: color .25s var(--ease);
    transform: translateX(-50%);
    animation: heroNext calc(.9s * var(--sky-t)) var(--ease)
               calc(2.6s * var(--sky-t)) both,
               heroBob 2.6s ease-in-out calc(3.5s * var(--sky-t)) infinite;
    animation-play-state: running, var(--sky-loop);
}
.hero__next::before {
    content: ""; width: .72rem; height: .72rem;
    border-right: 2px solid currentColor; border-bottom: 2px solid currentColor;
    border-radius: 0 0 2px 0;
    transform: translateY(-20%) rotate(45deg);
}
.hero__next:hover { color: #FFF; }
.hero__next:focus-visible { outline: 2px solid var(--am-marigold); outline-offset: 3px; }

/* ---- The move -------------------------------------------------------------
   Every one carries `both`, so the first frame holds before its delay runs
   out and the last frame holds forever after. That fill is what makes
   `--sky-t: 0` a complete still version rather than a blank screen. */
@keyframes heroWake { from { opacity: 0; } to { opacity: 1; } }
@keyframes heroNext { from { opacity: 0; } to { opacity: 1; } }

@keyframes heroSky {
    from { scale: 1.12; }
    to   { scale: 1; }
}

/* Drawn in from the flush edge. `inset()` rather than a scale, so the bars
   are revealed rather than stretched — a line being drawn, not a spring. */
@keyframes ruleIn {
    from { clip-path: inset(0 100% 0 0); }
    to   { clip-path: inset(0); }
}

/* A glint of sunrise running in along the stripes, then nine seconds of
   nothing. The first keyframe is off the flush end, which is what a parked
   `--sky-loop` holds. */
@keyframes ruleGlint {
    0%        { background-position: 100% 0, 0 0; }
    22%, 100% { background-position: 0 0, 0 0; }
}

@keyframes heroLogo {
    from { opacity: 0; transform: scale(1.09); }
    to   { opacity: 1; transform: none; }
}

@keyframes heroBob {
    0%, 100% { transform: translate(-50%, 0); }
    50%      { transform: translate(-50%, 5px); }
}
/* heroBob carries the centring translate through every frame. Any animated
   element with a transform of its own has to, or `both` persists the
   keyframe's transform and throws it half a screen to the right. */

/* ============================= PAGE HEADERS ==============================
   EVERY OTHER PAGE OPENS ON THE SAME AEROPLANE, LOOKING SOMEWHERE ELSE.

   The home page is the exception that carries no words. These do: they are
   pages about information, and a page whose heading is a picture is a page
   nobody can scan. So the band is short, the type sits on it, and the lede
   stays below on the page's own paper where long copy belongs — which is the
   one rule that keeps this from becoming hero #5 seven more times.

   WHAT IS SHARED AND WHAT IS NOT. Every header is the sky system above with a
   `--geo` and a time of day. Nothing below is page-specific except those two
   and the paint, which is the point: adding an eighth page is a class, a mask
   and four declarations, not a new design.

   THE BAND IS SHALLOW ON PURPOSE. About half a screen: enough to be a picture,
   not so much that the fleet table starts below the fold. The frame above is
   what makes a shallow band safe — at this ratio a `cover`-mounted mask would
   sit several percent off its own horizon. */
.skyhead {
    position: relative; isolation: isolate; overflow: hidden;
    display: grid; align-content: end;
    min-height: clamp(19rem, 54svh, 30rem);
    /* The top padding is set with the pull-up, in the NAV block; this is the
       foot, which is what sits the heading off the tricolour. */
    padding-bottom: clamp(2.4rem, 5vw, 3.6rem);
    background: var(--am-navy); color: #FFF;
}

.skyhead__inner { position: relative; z-index: 1; }
.skyhead .eyebrow { color: color-mix(in srgb, #FFF 72%, transparent); }
.skyhead h1 {
    margin: .7rem 0 0;
    font-size: clamp(2rem, 5vw, 3.4rem);
    color: #FFF; text-wrap: balance;
    text-shadow: 0 2px 26px rgba(2, 6, 14, .6);
}
/* The rule under the heading. Its hairline is an INSET DARK one everywhere
   else, because everywhere else it sits on white and the flag's middle third
   is white too. Here the ground is a picture, so the hairline flips: a dark
   one against a dark sky is the band vanishing all over again. Stated rather
   than themed, because a header is dark in both themes. */
.skyhead .rule {
    margin-top: 1.1rem;
    box-shadow: inset 0 0 0 1px color-mix(in srgb, #FFF 26%, transparent),
                0 2px 12px rgba(2, 6, 14, .5);
}

/* THE HERO'S STRIPES, ARRIVING FROM THE RIGHT. The heading sits on the left,
   so the rules come in from the other edge — stripes.svg, flush RIGHT and
   ragged towards the type — and stop short of it. A block level with the
   heading rather than the whole band, so the view above keeps its sky, and at
   the tile's own pitch so it shows fewer rules rather than squeezed ones. The ramp runs from almost nothing at the
   edge to the brightest at the ragged ends, and the brightest is still well
   under the type: the view is the picture, the rules are the airline's grain
   laid over it. Drawn in once, on the same `--sky-t` switch. */
.skyhead::before {
    content: ""; position: absolute; right: 0; z-index: 0;
    bottom: clamp(1.6rem, 4vw, 2.6rem); height: clamp(7rem, 15vw, 11.5rem);
    width: clamp(9rem, 40vw, 38rem);
    pointer-events: none;
    -webkit-mask: url('/assets/img/stripes.svg') no-repeat right center / 100% var(--stripe-tile);
            mask: url('/assets/img/stripes.svg') no-repeat right center / 100% var(--stripe-tile);
    background: linear-gradient(270deg,
        color-mix(in srgb, #FFF 4%, transparent) 0,
        color-mix(in srgb, #FFF 18%, transparent) 50%,
        color-mix(in srgb, #FFF var(--skyhead-rule, 44%), transparent) 100%);
    animation: skyheadRules calc(1.6s * var(--sky-t)) cubic-bezier(.62, 0, .18, 1)
               calc(.15s * var(--sky-t)) both;
}
@keyframes skyheadRules {
    from { clip-path: inset(0 0 0 100%); }
    to   { clip-path: inset(0); }
}
/* A phone's heading runs the full width, so the rules are behind it there:
   narrower, and quieter at the ends that meet the type. */
@media (max-width: 47.99rem) {
    .skyhead::before { width: 34vw; --skyhead-rule: 24%; }
}

/* One stagger, once, on load — the same `--sky-t` switch as the hero, so a
   page opened already scrolled and a reader who asked for stillness both get
   the finished header with no motion and no second set of rules. */
.skyhead__inner > * { animation: skyRise .7s var(--ease-rise) both; }
.skyhead .eyebrow { animation-duration: calc(.7s * var(--sky-t)); animation-delay: calc(.12s * var(--sky-t)); }
.skyhead h1       { animation-duration: calc(.8s * var(--sky-t)); animation-delay: calc(.22s * var(--sky-t)); }
.skyhead .rule    { animation-duration: calc(.9s * var(--sky-t)); animation-delay: calc(.4s * var(--sky-t)); }
/* The flag under the heading draws out from its left end, the way the rules
   draw in from the right — the two meet in the middle of the band. */
.skyhead .rule { animation-name: skyRuleDraw; transform-origin: left center; }
@keyframes skyRuleDraw {
    from { opacity: 0; transform: scaleX(0); }
    to   { opacity: 1; transform: none; }
}
@keyframes skyRise {
    from { opacity: 0; transform: translateY(14px); }
    to   { opacity: 1; transform: none; }
}

/* The band under a header, which is where its lede lives. */
.skyhead-under { padding-top: clamp(1.8rem, 4vw, 2.8rem); }

/* CLOUD ONLY WHERE YOU ARE AT CRUISE, and this is the rule the views are
   sorted by rather than a per-page taste. A cloud deck sits BELOW the horizon,
   which is where you see it from thirty thousand feet and nowhere else. Three
   of these views are of the ground — a city at night, an airfield on final, a
   fin on stand — and one is the door standing open, which does not happen in
   the air. Leaving the deck under them put an aeroplane on a stand on top of a
   cloud, which is the sort of thing nobody can name and everybody can see.

   What is left under the horizon on those four is the sky's own ramp, which at
   dusk and at night is ground. */
.skyhead--city .sky__cloud,
.skyhead--field .sky__cloud,
.skyhead--tail .sky__cloud,
.skyhead--door .sky__cloud { display: none; }

/* ---- The type's ground ----------------------------------------------------
   The home page carries no words, so its scrim only has to close the frame. A
   page header carries a heading, and a heading needs a ground it is legible on
   whatever the view behind it is doing — a lit wing, a bright cloud top, a
   row of windows. So the foot goes dark earlier and harder here, which is the
   same device the hero's scrim uses, tuned for a band with type on it. */
.skyhead .sky::after {
    background:
        radial-gradient(128% 92% at 50% 34%, transparent 42%,
            color-mix(in srgb, var(--sky-void) 76%, transparent) 100%),
        linear-gradient(180deg,
            color-mix(in srgb, var(--sky-void) 58%, transparent) 0, transparent 22%,
            transparent 38%, color-mix(in srgb, var(--sky-void) 52%, transparent) 70%,
            color-mix(in srgb, var(--am-navy) 86%, transparent) 92%,
            var(--am-navy) 100%);
}

/* ---- The seven views ------------------------------------------------------
   A class, a mask and the paint it is made of. The comment on each is what the
   picture IS, because the class name cannot say it and the mask is a path.

   Lit or dark is the whole decision here. A wing at dawn is outside and catches
   the sun along its leading edge; a cabin wall is inside and catches almost
   nothing. Both come out of the same generator as the same white shape. */

/* /fleet — over the wing. The top surface takes the light along the leading
   edge and falls away aft into the shadow the fuselage throws. */
.skyhead--wing {
    --geo: url('/assets/img/view-wing.svg');
    --geo-paint: linear-gradient(168deg,
        #E9D9C8 0%, #BFB0AC 26%, #6E6E86 58%, #333a57 82%, #1B2138 100%);
}
/* /network — a city at night, under the wing. It emits rather than blocks, so
   it is a glow: warm sodium in the mass of it, cooler where the avenues run. */
.skyhead--city {
    --geo: url('/assets/img/view-city.svg');
    --geo-paint: radial-gradient(70% 130% at 50% 108%,
        #FFE7C0 0%, #FFC275 30%, #F0904E 58%, #A96BB0 100%);
    /* Barely any: the lamps ARE the picture here, and a blur wide enough to
       make them glow is a blur wide enough to erase them. The haze below does
       the glowing. */
    --glow-blur: 1.4px;
    /* The light a city throws into the air above itself. Unmasked, so it is
       not points — it is the reason you can see there is one from sixty miles
       out, before any single light resolves. */
    background: radial-gradient(64% 40% at 50% 74%,
        color-mix(in srgb, #FF9F4A 46%, transparent) 0,
        color-mix(in srgb, #C4613F 18%, transparent) 46%, transparent 78%);
}
/* The one view whose SUBJECT is under the horizon, so the foot of the scrim
   that gives every other header's type its ground would be sitting on top of
   the picture. It keeps the vignette and gives up most of the foot; the
   heading has the city's own dark to read against. */
.skyhead--city .sky::after {
    background:
        radial-gradient(128% 92% at 50% 34%, transparent 46%,
            color-mix(in srgb, var(--sky-void) 62%, transparent) 100%),
        linear-gradient(180deg,
            color-mix(in srgb, var(--sky-void) 58%, transparent) 0, transparent 24%,
            transparent 82%, color-mix(in srgb, var(--am-navy) 62%, transparent) 95%,
            var(--am-navy) 100%);
}
/* /ranks — trails climbing out of the deck. Thin, and the blur is most of the
   width you see: a contrail is an edge, not a stripe. */
.skyhead--climb {
    --geo: url('/assets/img/view-contrails.svg');
    --geo-paint: linear-gradient(74deg,
        color-mix(in srgb, #FFE9CC 30%, transparent) 0%,
        color-mix(in srgb, #FFF4E4 76%, transparent) 46%,
        color-mix(in srgb, #FFFFFF 92%, transparent) 100%);
    --glow-blur: 6px;
    --glow-opacity: .8;
}
/* /events — an airfield in lights, lined up. No surface: see the note in
   tools/make-views.py about what a runway does to a shallow band. */
.skyhead--field {
    --geo: url('/assets/img/view-runway.svg');
    --geo-paint: linear-gradient(180deg, #FFF6E2 0%, #FFD089 40%, #F5A04A 100%);
    --glow-blur: 2px;
    /* What the approach throws into the air around itself, the same device the
       city uses — unmasked, so it is not lamps. */
    background: radial-gradient(34% 34% at 50% 72%,
        color-mix(in srgb, #FFC078 26%, transparent) 0, transparent 76%);
}
/* /about — a row of cabin windows, receding. The wall is the darkest thing on
   the site and is lit only by what comes through the apertures. */
.skyhead--cabin {
    --geo: url('/assets/img/view-cabin-row.svg');
    --geo-paint: radial-gradient(74% 60% at 30% 48%,
        #232F3E 0%, #151D29 34%, #0A0F18 68%, #04070C 100%);
}
/* /staff — a fin against a dusk sky. A silhouette, and deliberately not a
   livery: see the note in tools/make-views.py about the mark. */
.skyhead--tail {
    --geo: url('/assets/img/view-tail.svg');
    --geo-paint: linear-gradient(180deg, #2A2140 0%, #171331 46%, #0B0918 100%);
}
/* /apply — the door, open on the morning. */
.skyhead--door {
    --geo: url('/assets/img/view-door.svg');
    --geo-paint: radial-gradient(70% 62% at 46% 50%,
        #27313F 0%, #171F2B 34%, #0A0F17 70%, #03060A 100%);
}

/* ---- Phones ---------------------------------------------------------------
   The frame cuts in from the sides, which is what these views are built to
   survive. The band comes down a little so a heading and a lede are not most
   of a first screen. */
@media (max-width: 47.99rem) {
    .hero--live { padding-bottom: clamp(10rem, 30vw, 13rem); }
    /* Narrower than the desktop's 78vw so the stripes keep a hand's width
       either side of it rather than a sliver. */
    .hero__content { --logo-w: min(66vw, 22rem); --rule-gap: .7rem; }
    .skyhead { min-height: clamp(16rem, 44svh, 22rem); }
}

/* Asked for less motion, there is none: `--sky-t: 0` collapses every duration
   and delay to zero, so the page opens ON the finished picture, and
   `--sky-loop` parks the drift, the glow and the float at their resting frame.
   Not a shorter sequence — none, and nothing that depends on one having run. */
@media (prefers-reduced-motion: reduce) {
    .sky, .hero--rules, .skyhead { --sky-t: 0; --sky-loop: paused; }
}
/* The same stillness for a page that opens already scrolled — a #hash or a
   restored position — and for one scrolled past or in a background tab, where
   a cloud deck drifting is work nobody asked for on a battery nobody is
   charging. site.js sets both attributes; neither touches `--sky-t` on its
   own, so coming back to a picture does not replay its arrival. */
[data-still] { --sky-t: 0; --sky-loop: paused; }
[data-away] { --sky-loop: paused; }

/* The band under the hero carries the counted figures and the flown strip. */
.hero-under { padding-top: clamp(1.8rem, 4vw, 2.8rem); }
.hero-under .hero__log { margin-top: clamp(1.6rem, 3.5vw, 2.4rem); }

/* ---- Who has been flying it ----------------------------------------------
   Approved sectors off the crew centre, sampled at random. Rules rather than
   boxes, like everything else on this site that is information. */
.hero__log-head {
    display: flex; align-items: center; gap: .5rem; margin: 0 0 .85rem;
    font-size: .72rem; font-weight: 700; letter-spacing: .14em;
    text-transform: uppercase; color: var(--faint);
}
.hero__legs {
    list-style: none; margin: 0; padding: 0;
    display: grid; gap: .8rem; grid-template-columns: 1fr;
    transition: opacity .3s var(--ease);
}
.hero__legs.is-swapping { opacity: 0; }
.hero__leg {
    display: grid; gap: .18rem;
    padding-top: .7rem; border-top: 1px solid var(--line);
}
.hero__leg-route { font-size: 1rem; font-weight: 700; color: var(--ink); letter-spacing: -.01em; }
.hero__leg-route i { font-style: normal; color: var(--am-red); padding-inline: .1rem; }
/* "Flown by _ServerNoob", which is the whole of the review's note about this
   strip. The four columns of 11px grey that used to sit under each sector —
   flight number, type, block time, how long ago — are gone: a reader skipping
   between them could not see what they came for, and every one of them is a
   click away in the crew centre. */
.hero__leg-who { font-size: .88rem; color: var(--muted); }
.hero__leg-who b { color: var(--ink); font-weight: 700; }
.hero__leg-meta { font-size: .74rem; color: var(--faint); }
/* One leg on a phone, two on a large one, three from tablet up. The strip is
   context, not a list to read — three sectors stacked on a 375px screen would
   push the counted facts off the opening screen entirely. */
.hero__leg:nth-child(n+2) { display: none; }
@media (min-width: 34rem) {
    .hero__legs { grid-template-columns: repeat(2, 1fr); gap: 1rem; }
    .hero__leg:nth-child(2) { display: grid; }
}
@media (min-width: 48rem) {
    .hero__legs { grid-template-columns: repeat(3, 1fr); }
    .hero__leg:nth-child(3) { display: grid; }
}

/* Asked for less motion, the flight strip does not re-sample at all — hero.js
   never starts the timer — so the fade it would have swapped on comes off too.
   The hero's own stillness is two custom properties; see FLIGHT DECK HERO. */
@media (prefers-reduced-motion: reduce) {
    .hero__legs { transition: none; }
}

/* The four figures as one ruled band under the copy, rather than the column
   they were in beside it. */
.hero__facts--band {
    display: grid; gap: 0 clamp(1rem, 4vw, 2.5rem);
    grid-template-columns: repeat(2, 1fr);
    margin-top: clamp(2rem, 4.5vw, 3rem);
    text-align: left;
}
@media (min-width: 52rem) { .hero__facts--band { grid-template-columns: repeat(4, 1fr); } }
/* The base .hero__facts rules rule each row off along the bottom, which in four
   columns draws a broken line. Each figure takes its own rule along the TOP
   instead — including :last-child, which the base sheet also rules. */
.hero__facts--band > div,
.hero__facts--band > div:last-child {
    border-top: 1px solid var(--line);
    border-bottom: 0;
    padding: 1.05rem 0 .3rem;
    display: grid; grid-template-columns: auto 1fr; gap: .1rem .7rem; align-items: baseline;
}
.hero__facts--band dt {
    font-family: var(--font-mono); font-weight: 700; letter-spacing: -.02em;
    font-size: clamp(1.5rem, 3.4vw, 2.1rem); color: var(--accent);
    grid-row: span 2;
}
.hero__facts--band dd { margin: 0; display: grid; align-content: center; }
.hero__facts--band dd b { font-size: .92rem; font-weight: 700; color: var(--ink); }
.hero__facts--band dd span { font-size: .78rem; color: var(--muted); line-height: 1.35; }
@media (max-width: 34rem) {
    .hero__facts--band dd span { display: none; }
    .hero__facts--band > div { padding: .85rem 0; }
}

/* ============================== THE WALL =================================
   The Instagram posts staff hung on the crew center, drawn on the website.

   EVERY TILE IS A PLACEHOLDER UNTIL IT IS SCROLLED TO. An Instagram embed
   loads a whole Instagram page and its scripts; nine of them mounted at once
   is several megabytes for a section most visitors never reach. The tile is a
   button until it comes into view, and the frame is built then — see the
   observer in index.html.

   The frame is a fixed 326×470 because Instagram's embed refuses to lay out
   under 326px and cannot be measured across origins. The tile scales the whole
   frame with a transform rather than letting it be clipped, which is why the
   host carries an explicit height and the frame is absolutely placed. */
.wall {
    display: grid; gap: clamp(.9rem, 2vw, 1.4rem);
    grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
}
.wall__tile {
    position: relative; overflow: hidden; padding: 0;
    border: 1px solid var(--line); border-radius: var(--radius);
    background: var(--surface-2);
    /* 326 × 470 is 1 : 1.442. Held so the placeholder and the frame that
       replaces it occupy exactly the same box — no reflow on mount. */
    aspect-ratio: 326 / 470;
    display: grid; place-items: center;
    color: var(--muted); font: inherit; text-align: center;
    cursor: pointer; transition: border-color .2s ease, transform .2s ease;
}
.wall__tile:hover { border-color: var(--accent); transform: translateY(-2px); }
.wall__tile:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.wall__ph { display: grid; gap: .5rem; justify-items: center; padding: 1.2rem; }
.wall__ph span {
    font-family: var(--font-mono); font-size: .74rem; letter-spacing: .1em;
    text-transform: uppercase; color: var(--faint);
}
.wall__ph b { font-family: var(--font-display); font-weight: 700; color: var(--ink); }
.wall__tile iframe {
    position: absolute; top: 0; left: 0; width: 326px; height: 470px;
    border: 0; transform-origin: top left;
}
/* The follow line under the wall. Mono, because it is a handle. */
.wall__handle {
    margin-top: clamp(1.2rem, 3vw, 1.8rem);
    font-family: var(--font-mono); font-size: .86rem; color: var(--muted);
}
.wall__handle a { color: var(--accent); font-weight: 700; text-decoration: none; }
.wall__handle a:hover { text-decoration: underline; }
