:root{
    --royal:#0032DB;
    --royal-dark:#0023C2;
    --dark:#01114A;
    --cyan:#00D3C6;
    --pure-cyan:#45F3DC;
    --ink:#01114A;
    --body:#434343;
    --gray:#666666;
    --line:#e2e5ee;
    --line-dk:#c9cee0;
    --bg:#ffffff;
    --panel:#f5f5f5;
    --grad:linear-gradient(57deg,#0032DB 0%,#01114A 100%);
    color-scheme:light;
  }
  *{box-sizing:border-box}
  html{scroll-behavior:smooth}
  body{
    margin:0;background:var(--bg);color:var(--body);
    font-family:'Poppins',-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;
    font-weight:400;line-height:1.5;-webkit-font-smoothing:antialiased;
  }
  .wrap{max-width:840px;margin:0 auto;padding:0 20px}
  a{color:var(--royal);text-decoration:none}
  /* Single source of truth for icon size — the JS emits no inline width/height.
     geometricPrecision stops the 24-unit grid snapping and looking crumpled. */
  svg.ic{display:block;width:20px;height:20px;flex:none;shape-rendering:geometricPrecision}

  /* logo */
  .logo{display:inline-block;line-height:0}
  .logo svg{display:block;height:34px;width:auto}
  .hero .logo svg,.foot .logo svg{color:#fff}

  /* HERO (landing) */
  .hero{position:relative;overflow:hidden;color:#fff;text-align:center;min-height:clamp(460px,74vh,640px);display:grid;place-items:center;isolation:isolate}
  .hero-bg{position:absolute;inset:0;z-index:0;background:var(--grad-h);background-size:cover;background-position:center}
  /* photo sits ON the gradient, so the brand ground still shows if the jpg is slow or missing */
  .hero.has-photo .hero-bg{background-image:var(--hero-img),var(--grad-h)}
  /* Legibility is structural, not lucky: the middle radial guarantees a dark ground
     under the text column whatever the photo does there. Verified worst-case contrast
     (brightest photo pixel in each text band) — see the numbers in the handover notes. */
  .hero-tint{position:absolute;inset:0;z-index:1;background:
     radial-gradient(54% 44% at 16% 4%,rgba(69,243,220,.14),transparent 62%),
     radial-gradient(86% 76% at 50% 50%,rgba(1,17,74,.42),rgba(1,17,74,.14) 68%,transparent 100%),
     linear-gradient(140deg,rgba(1,17,74,.52) 0%,rgba(1,17,74,.18) 46%,rgba(0,50,219,.40) 100%)}
  .hero-inner{position:relative;z-index:2;padding:64px 24px;width:100%}
  .hero-badge{margin:0 auto 26px;max-width:min(420px,84vw)}
  .hero-badge img,.hero-badge>svg{width:100%;height:auto;display:block;filter:drop-shadow(0 14px 30px rgba(1,17,74,.42))}
  .hero h1{font-size:clamp(30px,6.2vw,50px);line-height:1.03;font-weight:600;margin:0;letter-spacing:-.01em}
  .hero h1 b{font-weight:700;color:var(--pure-cyan)}
  .hero .loc{color:var(--pure-cyan);font-weight:500;font-size:clamp(14px,3.2vw,17px);letter-spacing:.14em;text-transform:uppercase;margin-top:12px}
  /* Shrink-to-fit: inline-flex already sizes to content, so the bar is only ever
     as wide as the chips inside it. Nothing here may set an explicit width. */
  .chips{display:inline-flex;flex-wrap:wrap;gap:0;justify-content:center;
    width:fit-content;max-width:100%;margin:30px auto 0;
    border:1px solid rgba(255,255,255,.22);border-radius:var(--r);overflow:hidden;
    background:rgba(255,255,255,.06);backdrop-filter:blur(6px)}
  .chip{display:inline-flex;align-items:center;gap:8px;padding:12px 16px;font-size:12.5px;font-weight:500;color:#fff;border-right:1px solid rgba(255,255,255,.16)}
  .chip:last-child{border-right:0}
  .chip .ic{width:17px;height:17px;color:var(--pure-cyan)}
  .chip b{font-weight:600}
  /* Stack at the width where all three stop fitting on one line, so the bar goes
     straight from one row to a tidy column — never through a ragged two-row state
     with dead space on the short row. Column width hugs the widest chip.
     The row needs a ~764px viewport (bar max-content is 716px); 800px leaves
     slack in case the Poppins webfont fails and metrics shift. */
  @media(max-width:800px){
    .chips{flex-direction:column}
    .chip{border-right:0;border-bottom:1px solid rgba(255,255,255,.14);justify-content:flex-start}
    .chip:last-child{border-bottom:0}
  }

  /* WARNING */
  .warn{display:flex;gap:14px;align-items:flex-start;background:var(--panel);border-left:4px solid var(--royal);padding:16px 18px;margin:24px 0;font-size:13.5px;color:var(--ink);line-height:1.5}
  .warn .ic{width:22px;height:22px;color:var(--royal);margin-top:1px}
  .warn b{font-weight:600}

  /* STICKY CONTROLS */
  .controls{position:sticky;top:0;z-index:20;background:#fff;border-bottom:1px solid var(--line);padding:9px 0}
  .controls .row{display:flex;gap:0;flex-wrap:wrap;align-items:center;justify-content:center}
  .controls .row+.row{margin-top:8px}
  .tab{border:1px solid var(--line-dk);border-left-width:0;background:#fff;color:var(--ink);padding:7px 14px;font:inherit;font-size:12.5px;font-weight:600;cursor:pointer}
  .tab:first-child{border-left-width:1px}
  .tab:hover{background:var(--panel)}
  .tab.plane{display:inline-flex;align-items:center;gap:6px}
  .tab.plane .ic{width:16px;height:16px}
  .flabel{font-size:11px;color:var(--gray);text-transform:uppercase;letter-spacing:.1em;margin-right:12px;font-weight:600}
  .fbtn{border:1px solid var(--line-dk);border-left-width:0;background:#fff;color:var(--ink);padding:7px 13px;font:inherit;font-size:11.5px;font-weight:600;cursor:pointer;text-transform:uppercase;letter-spacing:.04em}
  .fbtn:first-of-type{border-left-width:1px}
  .fbtn.active{color:#fff;border-color:var(--royal)}
  .fbtn[data-g="ALL"].active{background:var(--royal)}
  .fbtn[data-g="GC"].active{background:var(--royal)}
  .fbtn[data-g="MK"].active{background:var(--dark)}
  .fbtn[data-g="TF"].active{background:var(--cyan);color:var(--dark)}

  /* DAY */
  .day{margin:28px 0}
  .day-head{background:var(--royal);color:#fff;padding:17px 22px;display:flex;align-items:center;justify-content:space-between;gap:14px}
  .day-head .date{font-weight:700;font-size:19px;letter-spacing:.03em}
  .day-head .date span{font-weight:400;opacity:.72;font-size:13px;margin-left:8px}
  /* white only — hierarchy comes from weight and opacity, not a second colour */
  .day-head .theme{font-size:12.5px;font-weight:500;color:#fff;opacity:.78;text-align:right;text-transform:uppercase;letter-spacing:.05em;max-width:52%}

  /* EVENTS */
  .tl{list-style:none;margin:0;padding:18px;display:flex;flex-direction:column;gap:14px}
  .ev{display:grid;grid-template-columns:72px 1fr;gap:14px;align-items:stretch}
  .timebox{display:grid;place-items:center;text-align:center;background:var(--panel);border:1px solid var(--line-dk);border-radius:var(--r-sm);font-weight:600;font-size:13.5px;color:var(--royal);font-variant-numeric:tabular-nums;padding:10px 6px;line-height:1.2}
  .card{display:flex;gap:15px;align-items:flex-start;background:#fff;border:1px solid var(--line);border-radius:var(--r-sm);padding:16px 18px}
  .iconbox{width:40px;height:40px;flex:none;border:1px solid var(--line-dk);background:var(--panel);border-radius:var(--r-sm);display:grid;place-items:center;color:var(--royal)}
  .iconbox .ic{width:22px;height:22px}
  /* flex:1 so the text column fills the card — without it the column shrinks to
     its longest word and the wardrobe tooltip (left:0;right:0) inherits that. */
  .txt{flex:1 1 auto;min-width:0;padding-top:1px}
  .t{font-weight:600;font-size:14.5px;line-height:1.4;margin:0;color:var(--ink)}
  .desc{color:var(--gray);font-size:12.5px;line-height:1.5;margin:5px 0 0}
  /* venue -> map lookup */
  a.venue{color:var(--royal);font-weight:500;display:inline;
          border-bottom:1px solid rgba(0,50,219,.28);
          transition:color var(--dur-1) var(--ease),border-color var(--dur-1) var(--ease)}
  a.venue .ic{display:inline-block;width:12px;height:12px;vertical-align:-1px;margin-left:3px;opacity:.7}
  a.venue:hover{color:var(--royal-dark);border-bottom-color:var(--royal)}
  a.venue:hover .ic{opacity:1}
  .chip-link{color:inherit;border-bottom:1px solid rgba(255,255,255,.34);
             transition:border-color var(--dur-1) var(--ease)}
  .chip-link:hover{border-bottom-color:var(--pure-cyan)}
  .tags{display:flex;gap:6px;flex-wrap:wrap;margin-top:11px}
  .tag{font-size:10px;font-weight:600;letter-spacing:.06em;padding:4px 9px;color:#fff;text-transform:uppercase;border-radius:4px}
  .tag.GC{background:var(--royal)}
  .tag.MK{background:var(--dark)}
  .tag.TF{background:var(--cyan);color:var(--dark)}
  .tag.OPT{background:#fff;color:var(--gray);border:1px solid var(--line-dk)}
  .tag.MEET{display:inline-flex;align-items:center;gap:5px;background:var(--dark);color:#fff}
  .tag.MEET .ic{width:13px;height:13px}
  /* key (punctual) events */
  .ev.key .timebox{background:var(--royal);border-color:var(--royal);color:#fff}
  .ev.key .iconbox{background:var(--royal);border-color:var(--royal);color:#fff}
  .ev.soft .card{background:#fafbfe}
  .ev.soft .iconbox{background:#fff;color:var(--gray)}
  .ev.soft .t{color:var(--body);font-weight:500}

  /* FLIGHTS */
  .section-title{display:flex;align-items:center;gap:14px;margin:52px 0 22px}
  .section-title .ico{width:44px;height:44px;background:var(--royal);display:grid;place-items:center;color:#fff;flex:none}
  .section-title .ico .ic{width:24px;height:24px}
  .section-title h2{margin:0;font-size:22px;font-weight:600;color:var(--ink)}
  .section-title p{margin:2px 0 0;font-size:12px;color:var(--gray)}
  /* One landscape pass per row. Outbound and return sit side by side inside
     it, so each card stays short and the set reads as boarding passes rather
     than a column of blocks. */
  .flights{display:grid;gap:16px}
  /* explicit surface — the body used to be transparent and borrow the page
     background, which broke the moment it sat on the dark flights panel */
  /* ---- BOARDING PASS -------------------------------------------------
     Hierarchy per stop: airport CODE (largest) -> time -> city name.
     No flight durations anywhere: these routes cross time zones, so the
     clock difference is not the flight length and printing it would lie. */
  .ticket{background:#fff;border:1px solid var(--line-dk);overflow:hidden}
  .ticket{background:#fff;border:1px solid var(--line-dk);overflow:hidden}
  .tk-head{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:13px 18px;color:#fff}
  .ticket[data-g="GC"] .tk-head{background:var(--royal)}
  .ticket[data-g="MK"] .tk-head{background:var(--dark)}
  .ticket[data-g="TF"] .tk-head{background:var(--cyan);color:var(--dark)}
  .tk-head .rt{font-weight:600;font-size:15px;letter-spacing:.01em}
  .tk-head .meta{font-size:10.5px;font-weight:600;text-transform:uppercase;letter-spacing:.08em;opacity:.82}
  .tk-id{display:flex;flex-direction:column;gap:3px;min-width:0}

  /* Official carrier marks (see AIRLINES in agenda.js for provenance).
     Knocked out to white so two different brand palettes sit consistently on
     the coloured header; the cyan header takes the dark knockout instead. */
  /* the SVGs are already solid white with the crane knocked out — no CSS
     filter, so the mark reads the same on every header colour */
  .tk-logo{flex:none;height:22px;width:auto;max-width:132px;object-fit:contain}
  .tk-logo-type{flex:none;font-size:12px;font-weight:700;letter-spacing:.11em;
                text-transform:uppercase;color:currentColor;opacity:.95}

  /* outbound | return side by side, split by a vertical perforation */
  .tk-body{display:grid;grid-template-columns:1fr 1fr}
  .tk-stub{padding:16px 18px 18px;position:relative}
  .tk-stub+.tk-stub{border-left:1.5px dashed var(--line-dk)}

  .tk-dir{display:flex;align-items:baseline;justify-content:space-between;gap:10px;margin-bottom:12px}
  .tk-dir .lbl{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.14em;color:var(--royal)}
  .tk-dir .date{font-size:11px;font-weight:600;color:var(--gray)}

  .hop{display:grid;grid-template-columns:auto 1fr auto;align-items:center;gap:12px}
  .pt{display:grid;min-width:70px}
  .pt.to{text-align:right;justify-items:end}
  .pt .code{font-size:24px;font-weight:700;line-height:1.05;color:var(--ink);letter-spacing:.02em}
  .pt .time{font-size:15px;font-weight:600;color:var(--royal);font-variant-numeric:tabular-nums;margin-top:2px}
  .pt .place{font-size:10px;font-weight:500;color:var(--gray);text-transform:uppercase;letter-spacing:.06em;margin-top:3px}
  .pt .nx{color:var(--royal);font-weight:600;white-space:nowrap}

  .path{display:grid;justify-items:center;gap:4px;min-width:0}
  .path .fno{font-size:10px;font-weight:600;letter-spacing:.08em;color:var(--gray);text-transform:uppercase}
  .path .rail{position:relative;display:block;width:100%;height:16px}
  .path .rail::before{content:"";position:absolute;top:50%;left:0;right:0;height:0;
    border-top:1.5px dashed var(--line-dk);transform:translateY(-50%)}
  .path .rail .ic{position:absolute;top:50%;left:50%;width:17px;height:17px;
    transform:translate(-50%,-50%) rotate(90deg);color:var(--royal);background:#fff;padding:0 3px;box-sizing:content-box}

  .cx{display:flex;align-items:center;gap:10px;margin:12px 0 10px;
      font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.09em;color:var(--gray)}
  .cx::before,.cx::after{content:"";flex:1;border-top:1px dashed var(--line-dk)}

  /* stack the two halves once side-by-side gets too tight */
  @media(max-width:700px){
    .tk-body{grid-template-columns:1fr}
    .tk-stub+.tk-stub{border-left:0;border-top:1.5px dashed var(--line-dk)}
    .tk-head{flex-direction:column;align-items:flex-start;gap:8px}
    .tk-stub{padding:14px 14px 16px}
    .pt{min-width:58px}
    .pt .code{font-size:21px}
    .pt .time{font-size:14px}
  }

  /* FOOTER */
  .foot{background:var(--grad);color:#fff;text-align:center;padding:36px 20px;margin-top:48px;position:relative}
  .foot p{font-size:12px;letter-spacing:.06em;text-transform:uppercase;opacity:.82;margin:16px 0 0}

  .hide{display:none !important}

  @media(max-width:480px){
    .ev{grid-template-columns:56px 1fr}
    .day-head{flex-direction:column;align-items:flex-start;gap:5px}
    .day-head .theme{text-align:left;max-width:100%}
    .tab,.fbtn{padding:7px 10px}
  }

  /* --- rounding: intentional, restrained (brand button radius family) --- */
  :root{--r:8px;--r-sm:5px}
  .warn{border-radius:var(--r)}
  .chips{border-radius:var(--r)}
  .controls .tab:first-child{border-top-left-radius:var(--r-sm);border-bottom-left-radius:var(--r-sm)}
  .controls .row .tab:last-child{border-top-right-radius:var(--r-sm);border-bottom-right-radius:var(--r-sm)}
  .fbtn:first-of-type{border-top-left-radius:var(--r-sm);border-bottom-left-radius:var(--r-sm)}
  .fbtn:last-of-type{border-top-right-radius:var(--r-sm);border-bottom-right-radius:var(--r-sm)}
  /* no overflow:hidden — the wardrobe note needs to be able to overlay the row.
     The header carries the top corners itself instead. */
  .day{border:1px solid var(--line);border-radius:var(--r)}
  .day-head{border-radius:calc(var(--r) - 1px) calc(var(--r) - 1px) 0 0}
  .tag{border-radius:4px}
  .section-title .ico{border-radius:var(--r-sm)}
  .fcard{border-radius:var(--r);overflow:hidden}

  /* ============ MODERN DEPTH PASS ============ */
  :root{
    --bg:#f4f6fc;
    --r:10px;--r-sm:6px;
    --sh-sm:0 1px 2px rgba(1,17,74,.05),0 1px 3px rgba(1,17,74,.07);
    --sh-md:0 6px 18px rgba(1,17,74,.08),0 2px 6px rgba(1,17,74,.05);
    --sh-lg:0 20px 44px rgba(1,17,74,.18);
    --grad-h:linear-gradient(135deg,#123CF2 0%,#0032DB 44%,#01114A 100%);
    --grad-dk:linear-gradient(135deg,#0A2170 0%,#01114A 100%);
    --grad-cy:linear-gradient(135deg,#45F3DC 0%,#00D3C6 100%);
    /* Hero photo — Ronda (Puente Nuevo), Málaga province. Supplied by the design team. */
    --hero-img:url("assets/malaga-hero.jpg");
  }
  @media(max-width:759.98px){:root{--hero-img:url("assets/malaga-hero-sm.jpg")}}
  body{background:var(--bg)}

  /* hero */
  .hero{box-shadow:var(--sh-lg)}

  /* frosted sticky nav */
  .controls{background:rgba(255,255,255,.82);backdrop-filter:saturate(160%) blur(10px);transition:box-shadow .2s ease}
  .controls.scrolled{box-shadow:0 8px 22px rgba(1,17,74,.09)}
  .tab,.fbtn{background:#fff;transition:background .15s,color .15s}
  .tab{position:relative}
  .tab:not(.cur):hover,.fbtn:not(.active):hover{background:#eef1fb;color:var(--royal)}
  .fbtn.active:hover{filter:brightness(1.06)}
  .tab.cur{color:var(--royal)}
  /* .tab::after underline is defined in the INTERACTION & MOTION SYSTEM block below */

  /* warning as elevated card */
  .warn{background:#fff;border:1px solid #eaecf5;border-left:4px solid var(--royal);box-shadow:var(--sh-sm)}

  /* section header icon */
  .section-title .ico{background:var(--grad-h);box-shadow:0 8px 18px rgba(0,50,219,.26)}

  /* day container + gradient header */
  .day{background:#fff;border-color:#eceef6;box-shadow:var(--sh-md)}
  .day-head{background:var(--grad-h)}

  /* event cards */
  .card{border-color:#edeff7;box-shadow:var(--sh-sm);
        transition:transform var(--dur-2) var(--ease),box-shadow var(--dur-2) var(--ease)}
  /* The row lifts as ONE object — the time and the card together. Hovering the
     card alone used to leave the timestamp behind, which read as the card
     detaching from its own time. Hover-capable pointers only, so the lift
     doesn't stick after a tap on a touchscreen. */
  @media(hover:hover){
    .ev:hover .card{transform:translateY(-2px);box-shadow:var(--sh-md)}
    .ev:hover .timebox{transform:translateY(-2px)}
    .ev.key:hover .timebox{box-shadow:0 9px 20px rgba(0,50,219,.38)}
  }
  .timebox{border-color:#e4e7f2;background:linear-gradient(180deg,#ffffff 0%,#eef1fb 100%);box-shadow:var(--sh-sm)}
  .iconbox{border-color:#e4e7f2;background:linear-gradient(180deg,#ffffff 0%,#eaeefb 100%);box-shadow:var(--sh-sm)}
  .ev.key .timebox{background:var(--grad-h);border-color:transparent;box-shadow:0 5px 14px rgba(0,50,219,.34)}
  .ev.key .iconbox{background:var(--grad-h);border-color:transparent;box-shadow:0 4px 12px rgba(0,50,219,.30)}
  .ev.soft .card{background:#fbfcfe}
  .ev.soft .iconbox{background:#fff;box-shadow:none}

  /* flight cards */
  .fcard{border-color:#eceef6;box-shadow:var(--sh-md);transition:transform var(--dur-2) var(--ease),box-shadow var(--dur-2) var(--ease)}
  .fcard:hover{transform:translateY(-2px);box-shadow:var(--sh-lg)}
  .ticket[data-g="GC"] .tk-head{background:var(--grad-h)}
  .ticket[data-g="MK"] .tk-head{background:var(--grad-dk)}
  .ticket[data-g="TF"] .tk-head{background:var(--grad-cy);color:var(--dark)}

  /* --- FLIGHTS PANEL ----------------------------------------------------
     Travel is a different kind of information from the day-by-day plan, so it
     gets its own dark surface rather than sharing the itinerary's white
     rhythm. The days then get their own "Daily agenda" heading, so the two
     sections read as two sections. */
  /* --- FLIGHTS PANEL ----------------------------------------------------
     A soft tinted section rather than a hard navy slab — it separates travel
     from the itinerary without shouting over the boarding passes on it. */
  .fpanel{
    position:relative;overflow:hidden;
    background:linear-gradient(180deg,#f6f9ff 0%,#e9effc 100%);
    border:1px solid #dde4f7;border-radius:var(--r);
    padding:26px 24px 28px;margin:46px 0 4px;box-shadow:var(--sh-sm);
    /* Break out past the 840px text column. Margin-based, NOT transform — the
       scroll-reveal animates transform here and would cancel a translateX. */
    --bleed:max(0px, min(128px, calc((100vw - 40px - 100%) / 2)));
    margin-left:calc(-1 * var(--bleed));
    margin-right:calc(-1 * var(--bleed));
  }
  .fpanel::before{content:"";position:absolute;top:0;left:0;right:0;height:3px;background:var(--grad-cy)}
  .fpanel .section-title{margin:0 0 20px}
  @media(max-width:560px){.fpanel{padding:20px 14px 22px;margin:34px 0 4px}}

  /* footer */
  .foot{background:var(--grad-h)}

  .intro{max-width:640px;margin:30px auto 0;text-align:center;font-size:14px;line-height:1.6;color:var(--gray)}
  .intro b{color:var(--ink);font-weight:600}

  /* ============ INTERACTION & MOTION SYSTEM ============ */
  /* One easing family, three durations. Every transition below picks from these —
     so nothing drifts out of step and the whole page moves as one object. */
  :root{
    --dur-1:180ms;   /* colour / opacity */
    --dur-2:300ms;   /* lift, scale, borders */
    --dur-3:460ms;   /* reveal, indicator travel — the motion you actually watch */
    /* Long tail, no overshoot: movement eases out and comes to rest rather than
       arriving with a snap. Nothing on the page bounces or loops. */
    --ease:cubic-bezier(.22,.7,.24,1);
    --ease-soft:cubic-bezier(.33,.6,.28,1);
    --focus:0 0 0 3px #fff,0 0 0 5.5px var(--royal);
  }

  /* --- focus: the page is almost entirely <button>s and had no focus styles at all --- */
  :where(a,button,[tabindex]):focus-visible{
    outline:none;box-shadow:var(--focus);border-radius:var(--r-sm);
    position:relative;z-index:5;
  }
  .hero :where(a,button):focus-visible{box-shadow:0 0 0 3px var(--dark),0 0 0 5.5px var(--pure-cyan)}

  /* --- navigation: one indicator that glides between tabs ---------------
     A shared element rather than a per-tab ::after, so following the scroll
     down the page reads as one continuous movement instead of four separate
     underlines blinking on and off. JS positions it; it also handles wrapping
     to a second row, hence translating on Y as well as X. */
  .controls .row{position:relative}
  .tab-ind{
    position:absolute;left:0;top:0;height:2px;width:0;
    background:var(--grad-cy);border-radius:2px;pointer-events:none;
    opacity:0;transform:translate3d(0,0,0);
    transition:transform var(--dur-3) var(--ease),
               width var(--dur-3) var(--ease),
               opacity var(--dur-2) var(--ease-soft);
  }
  .tab-ind.on{opacity:1}
  /* first paint: appear in place rather than sliding in from the left edge */
  .tab-ind.no-anim{transition:none}

  .tab:active,.fbtn:active{transform:translateY(.5px)}
  .tab,.fbtn{transition:background var(--dur-1) var(--ease),color var(--dur-1) var(--ease),
             transform var(--dur-1) var(--ease),box-shadow var(--dur-1) var(--ease)}
  .fbtn{position:relative}
  .fbtn.active{box-shadow:inset 0 0 0 1px rgba(255,255,255,.14)}

  /* --- event card: lift + a cyan edge that wipes in from the top --- */
  /* no overflow:hidden here — it would clip the focus ring on the "?" button.
     The bar carries the card's own left radius instead. */
  .card{position:relative}
  .card::after{
    content:"";position:absolute;left:0;top:0;bottom:0;width:3px;
    border-radius:var(--r-sm) 0 0 var(--r-sm);
    background:var(--grad-cy);transform:scaleY(0);transform-origin:top;
    transition:transform var(--dur-2) var(--ease);
  }
  .ev:hover .card::after,.ev:focus-within .card::after{transform:scaleY(1)}
  /* :not(.key) — a key event's timebox is a blue gradient with white text, so
     recolouring it on hover made the time vanish into its own background. */
  .ev:not(.key):hover .timebox{border-color:#cdd5f0;color:var(--royal-dark)}
  .iconbox{transition:transform var(--dur-2) var(--ease),box-shadow var(--dur-2) var(--ease)}
  .ev:hover .iconbox{transform:scale(1.06)}
  .timebox{transition:border-color var(--dur-2) var(--ease),color var(--dur-2) var(--ease),
           transform var(--dur-2) var(--ease),box-shadow var(--dur-2) var(--ease)}

  /* --- wardrobe disclosure ("?" indicator) --- */
  /* The "?" flows inline as the last word of the title, so it stays attached
     to the text however the title wraps. */
  /* title takes the space, "?" is pushed to the far right edge of the card */
  .t-row{display:flex;align-items:flex-start;gap:12px}
  .t-row .t{flex:1 1 auto;min-width:0}
  .wq{
    margin-top:1px;                              /* sits on the title's first line */
    display:grid;place-items:center;
    width:19px;height:19px;padding:0;
    border:1px solid var(--line-dk);background:#fff;color:var(--gray);
    border-radius:50%;font:inherit;font-size:11px;font-weight:700;line-height:1;
    cursor:pointer;place-items:center;
    transition:background var(--dur-1) var(--ease),color var(--dur-1) var(--ease),
               border-color var(--dur-1) var(--ease),transform var(--dur-2) var(--ease);
  }
  .wq:hover,.wq:focus-visible,.ev.wear-open .wq{background:var(--royal);border-color:var(--royal);color:#fff}
  /* The dot stays 19px, but the touch target is padded out to ~40px so it is
     comfortably tappable without changing the layout. */
  .wq::after{content:"";position:absolute;top:50%;left:50%;
             width:40px;height:40px;transform:translate(-50%,-50%)}
  .wq{position:relative}

  /* The note is absolutely positioned at its OWN static position — directly under
     the title — so showing it on hover overlays the row instead of reflowing the
     page. left/right:0 pin it to the text column, so it can never overflow the
     card sideways at any width. */
  .txt{position:relative}
  /* Lift the WHOLE row, not just the note. `.card:hover` applies a transform,
     which makes the card a stacking context and traps any z-index inside it —
     so the note alone can't rise above the rows that follow it in the DOM.
     Raising the <li> clears sibling rows and the sticky nav (z-index 20) too. */
  .ev{position:relative}
  /* 5 is enough to clear sibling rows (z-index auto) so the wardrobe note can
     overlay them. It must stay BELOW .controls (20) — at 40 a hovered row drew
     on top of the sticky nav, which looked broken. */
  .ev:hover,.ev:focus-within,.ev.wear-open{z-index:5}
  /* anchored to the button, so the note appears right where you're pointing */
  .wq-wrap{position:relative;display:inline-grid;flex:none;margin-left:auto}
  .wear{
    position:absolute;top:calc(100% + 9px);right:-3px;z-index:6;
    width:max-content;max-width:min(300px,62vw);
    opacity:0;visibility:hidden;transform:translateY(-4px);
    transition:opacity var(--dur-2) var(--ease-soft),
               transform var(--dur-2) var(--ease-soft),
               visibility 0s linear var(--dur-2);
  }
  /* Hover reveal is gated to hover-capable pointers: on a touchscreen :hover
     sticks after a tap and the note would stay stuck open. Touch uses the
     .wear-open tap state below instead. */
  @media(hover:hover){
    .ev:has(.wq:hover) .wear{
      opacity:1;visibility:visible;transform:none;
      transition:opacity var(--dur-2) var(--ease-soft),
                 transform var(--dur-2) var(--ease-soft),visibility 0s;
    }
  }
  .ev:has(.wq:focus-visible) .wear,
  .ev.wear-open .wear{
    opacity:1;visibility:visible;transform:none;
    transition:opacity var(--dur-2) var(--ease-soft),
               transform var(--dur-2) var(--ease-soft),visibility 0s;
  }
  .wear .inner{
    position:relative;display:flex;gap:10px;align-items:flex-start;
    background:#fff;border:1px solid #dfe5f8;border-left:3px solid var(--cyan);
    border-radius:var(--r-sm);padding:11px 13px;font-size:12.5px;line-height:1.55;color:var(--ink);
    box-shadow:var(--sh-md);
  }
  /* little caret pointing back up at the "?" */
  .wear .inner::before{
    content:"";position:absolute;top:-5px;right:12px;width:9px;height:9px;
    background:#fff;border-left:1px solid #dfe5f8;border-top:1px solid #dfe5f8;
    transform:rotate(45deg);
  }
  .wear .inner .ic{width:17px;height:17px;color:var(--royal);margin-top:1px;flex:none}
  .wear .inner b{font-weight:600}

  /* --- scroll reveal: runs once, then the page is still --- */
  /* --rd is an optional per-element stagger; JS removes .reveal once it has run,
     so this transition never lingers to delay a later hover. */
  .reveal{opacity:0;transform:translateY(14px);
          transition:opacity var(--dur-3) var(--ease-soft) var(--rd,0ms),
                     transform var(--dur-3) var(--ease) var(--rd,0ms)}
  /* rows travel a little further and settle a touch slower, so a run of them
     reads as a cascade rather than a block of text blinking on */
  .reveal-row{transform:translateY(20px) scale(.994);
              transition-duration:.52s,.52s}
  .reveal.in{opacity:1;transform:none}

  /* filter entrance — JS removes .pop when it ends, so it never lingers */
  @keyframes popIn{from{opacity:0;transform:translateY(9px) scale(.988)}to{opacity:1;transform:none}}
  .pop{animation:popIn .42s var(--ease) both var(--pd,0ms)}

  /* --- DEPARTURE BOARD INTRO ---------------------------------------------
     Runs ~4s so it can actually be read. The fade-out is a CSS animation with
     `forwards`, so the overlay clears itself even if the JS that drives the
     letters never runs. */
  .board{
    position:fixed;inset:0;z-index:9999;display:grid;place-items:center;
    background:var(--grad-dk);overflow:hidden;
    /* --board-hold is set by JS from the measured flap timing plus the hold on
       the finished lockup, so this delay can never drift out of sync with the
       animation it is waiting for. The default is the no-JS fallback. */
    animation:boardOut .65s var(--ease) var(--board-hold,4.4s) forwards;
  }
  html.no-board .board{display:none}
  @keyframes boardOut{to{opacity:0;visibility:hidden;pointer-events:none}}

  .board-in{position:relative;text-align:center;padding:24px}
  .board-kicker{
    color:rgba(255,255,255,.72);font-size:clamp(12px,3vw,16px);font-weight:500;
    letter-spacing:.42em;text-transform:uppercase;margin-bottom:20px;
    animation:rise .8s var(--ease) both .2s;
  }
  .board-line{display:flex;gap:7px;justify-content:center;font-variant-numeric:tabular-nums}
  .flap{
    display:grid;place-items:center;
    width:clamp(40px,10vw,66px);height:clamp(56px,14vw,92px);
    background:linear-gradient(180deg,#0b2478 0%,#061a5e 49.5%,#04154e 50.5%,#030f3c 100%);
    border:1px solid rgba(255,255,255,.10);border-radius:6px;
    box-shadow:0 8px 20px rgba(0,0,0,.34);
    font-size:clamp(26px,6.4vw,44px);font-weight:700;color:#fff;line-height:1;
  }
  .flap.sp{background:none;border:0;box-shadow:none;width:clamp(14px,3vw,22px)}
  .flap.set{animation:flapSet .34s var(--ease)}
  @keyframes flapSet{0%{transform:rotateX(-58deg);opacity:.55}100%{transform:none;opacity:1}}
  .board-sub{
    margin-top:22px;color:var(--pure-cyan);
    font-size:clamp(10px,2.6vw,13px);font-weight:600;
    letter-spacing:.34em;text-transform:uppercase;
    animation:rise .8s var(--ease) both 2.6s;
  }

  /* --- page entrance ------------------------------------------------------
     The hero is VISIBLE BY DEFAULT. These animations only apply once JS adds
     `hero-go`, which it does the moment the intro starts clearing. Nothing
     here gates visibility on an animation finishing: an animation clock is
     frozen in a background tab, so a hero that depended on a delay elapsing
     would sit blank for seconds after the reader finally looked at it. If the
     script never runs at all, the hero is simply there, unanimated. */
  @keyframes rise{from{opacity:0;transform:translateY(16px)}to{opacity:1;transform:none}}
  @keyframes settle{from{transform:scale(1.05)}to{transform:none}}
  /* `forwards`, never `both`. A backwards fill would paint the from-state
     (opacity 0) during the delay AND for as long as a frozen animation clock
     has not started — which is exactly the background-tab case. With forwards
     only, an animation that never runs simply leaves the element as it is. */
  html.hero-go .hero-bg   {animation:settle 1.6s var(--ease) forwards}
  html.hero-go .hero-badge{animation:rise .75s var(--ease) forwards .04s}
  html.hero-go .hero h1   {animation:rise .75s var(--ease) forwards .14s}
  html.hero-go .hero .loc {animation:rise .75s var(--ease) forwards .22s}
  html.hero-go .hero .chips{animation:rise .75s var(--ease) forwards .30s}

  /* --- honour reduced motion: kill animation too, not just transitions --- */
  @media(prefers-reduced-motion:reduce){
    *,*::before,*::after{
      transition-duration:.01ms!important;
      animation-duration:.01ms!important;animation-iteration-count:1!important;
      /* delay too, not just duration: the hero entrance is delayed ~1.8s to sit
         under the board, and killing only the duration would leave it invisible
         for that delay if the head script never applied .no-board */
      animation-delay:0s!important;transition-delay:0s!important;
      scroll-behavior:auto!important;
    }
    .board{display:none!important}
    .reveal{opacity:1;transform:none}
    .ev:hover .card,.ev:hover .timebox,.fcard:hover{transform:none}
  }
