/* TravelPlans — the shared design layer for every page.
   ⚠️ THIS WAS 30 KB OF INLINE <style> IN base.html UNTIL 19 SEPTEMBER 2026, and it was
   re-sent and re-parsed on EVERY navigation, because markup inside a page cannot be
   cached. Kay: *"Clicking on the nav bar has [taken] long and clicking on an activity is
   also taking too long to open."* The audit found the server rendering a trip page in
   27-57ms while shipping 650 KB of HTML, 75-86%% of it inline CSS and JS.
   It is served from /static with `expires 30d, immutable` and asked for through
   `static_v()`, which stamps the file's mtime into the URL — so a change to this file
   ALWAYS reaches a phone that already has the old one, and an unchanged one is never
   fetched twice. That is the whole point: cached, but never stale.
   It loads AFTER tp_reskin.css and BEFORE anything a page adds in its own head block,
   which is exactly where it sat as inline markup — the cascade is unchanged. */
/* ══════════════════════════════════════
           DESIGN TOKENS
        ══════════════════════════════════════ */
        :root {
            --tp-blue:          #2B3FBF;
            --tp-blue-dark:     #1e2d8a;
            --tp-blue-light:    #4555d4;
            --tp-blue-muted:    rgba(43,63,191,0.08);
            --tp-orange:        #F97316;
            /* ⚠️ THE WORDS ON AN ORANGE FILL. White on #F97316 measures 2.80:1 against
               the 4.5:1 floor — a property of the colour, not a choice; the deep navy
               measures 6.55:1 and was tried for a day. Kay, 14 Sep 2026, having seen it: *"the dark text in the orange tile
               doesn't really work and I'm keen to revert that back to white... the design
               integrity does not look great with the dark writing on the orange. Let's go
               back to white and address maybe in future on how to solve this differently."*
               So this is WHITE by his decision, knowing it measures 2.80:1. The token is
               kept rather than deleted: the day we find a better answer, it is one line. */
            --tp-orange-ink:    #FFFFFF;
            /* ...and orange as SMALL TEXT OR AN ICON ON A LIGHT CARD is the opposite job:
               the brand orange is 2.80:1 on white, so those keep the deep rust (5.18:1). */
            --tp-orange-text:   #C2410C;
            --tp-orange-dark:   #d96a10;
            --tp-orange-muted:  rgba(249,115,22,0.12);
            --tp-bg:            #F2F4FB;
            --tp-surface:       #ffffff;
            --tp-border:        #E4E7F4;
            --tp-text:          #0F1430;
            /* ⚠️ MEASURED ON THE PAGE'S OWN BACKGROUND, NOT ON WHITE (P11, 15 Sep 2026).
               #686E8B cleared 4.5:1 on white (4.80) and failed on --tp-bg (4.36) and on
               the tint (4.46) — and most muted text in this app sits on those, not on
               white, so the one surface it was checked against was the one it was rarely
               used on. Three units darker per channel clears all three (4.56 / 4.65 /
               5.01). That is about a 1% change: this is NOT the palette darkening Kay
               reserved a decision on, which is a different and much larger question. */
            --tp-text-muted:    #686E8B;
            --tp-shadow-sm:     0 1px 4px rgba(43,63,191,0.06);
            --tp-shadow:        0 4px 20px rgba(43,63,191,0.10);
            --tp-shadow-lg:     0 8px 32px rgba(43,63,191,0.14);
            --tp-radius:        14px;
            --tp-radius-sm:     8px;
            --tp-radius-lg:     20px;
            --tp-nav-h:         64px;
            /* ⚠️ 66px → 56px (Kay, 23 Sep 2026, from the app: *"The bottom nav bar is
               too high and should be a little less higher vertically. It is also worse
               in the inspire tab."*).

               What the bar has to hold is 47px: a 20.8px icon, a 3px gap, a ~13px label
               and 10px of padding under it. At 66 there were nineteen spare pixels doing
               nothing, and iOS's own tab bar is 49pt — so ours stood a full seventeen
               points taller than every native app beside it. 56 leaves nine pixels of
               breathing room above the icon and lands within a few points of native.

               ⚠️ IT IS NOT THE WHOLE STORY ON A PHONE, AND I CANNOT TEST THE REST FROM
               HERE. In the app the bar also carries `--safe-bottom`, and `contentInset`
               was `always` in build 1 — which I believe made iOS add the home-indicator
               inset a second time, on top of this. That is reasoning, not a measurement:
               there is no iOS device on this droplet and Safari reports no such inset.
               Build 2 ships `contentInset: never` and will settle it. If the bar then
               reads as too SHORT, this number is the one to move back. */
            --tp-bottom-nav-h:  56px;
            --safe-bottom:      env(safe-area-inset-bottom, 0px);
            /* ⚠️ THE TOP INSET IS NOT DECORATION — IT IS WHERE THE CLOCK LIVES (Kay,
               22 Sep 2026, on the first TestFlight build: *"On scrolling up on the
               timelines. Then anchored timeline, searches and activities also go behind
               the time but must stay visible."*).

               In a browser this is 0 and nothing changes. In the app the page draws to
               the very top of the screen, so ANYTHING that parks itself at `top: 0`
               parks under the clock and battery: a sticky bar is pinned to the
               scrollport, not to the padded body, so `body { padding-top }` does not
               move it. Every sticky or fixed bar measures from this. */
            --safe-top:         env(safe-area-inset-top, 0px);
        }

        /* ══════════════════════════════════════
           RESET / BASE
        ══════════════════════════════════════ */
        *, *::before, *::after { box-sizing: border-box; }

        /* The page must never scroll sideways (Kay, 5 Aug 2026: "when you are on mobile
           and scroll up and down, sometimes you can scroll sideways ... can we lock that
           down"). One stray element wider than the viewport is enough to slide the whole
           layout, and the cause is invisible — the offender is often transparent or
           off-screen. `clip` rather than `hidden` on purpose: `hidden` turns the root
           into a scroll container and breaks `position: sticky` inside it, `clip` does
           not. `overscroll-behavior-x` also stops the sideways rubber-band. Deliberately
           NOT `touch-action: pan-y`, which would kill the horizontal scrollers the design
           relies on — the hero filter pills and the sort chips are meant to swipe. */
        html { overflow-x: clip; overscroll-behavior-x: none; }
        body { overflow-x: clip; overscroll-behavior-x: none; max-width: 100%; }
        @supports not (overflow: clip) {
            html, body { overflow-x: hidden; }
        }

        body {
            background: var(--tp-bg);
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
                         'Helvetica Neue', Arial, sans-serif;
            color: var(--tp-text);
            font-size: 0.9375rem;
            line-height: 1.55;
            -webkit-font-smoothing: antialiased;
        }

        /* Reserve space for fixed bars — only where the bar actually exists. The tab bar
           is mobile-only (`display: flex` below 992px), but this padding was not scoped,
           so every DESKTOP page also reserved 66px for a bar that is never drawn: dead
           space at the foot of the page, and it held the footer clear of the bottom. */
        @media (max-width: 991.98px) {
            body.has-bottom-nav {
                padding-bottom: calc(var(--tp-bottom-nav-h) + var(--safe-bottom));
            }
        }

        /* Paint the status-bar / top safe-area region navy so it blends with the navy
           nav (content pages) and navy hero (dashboard) — removes the light strip above
           the app on mobile. Pairs with theme-color for browsers that honour it. */
        @media (max-width: 991.98px) {
            html { background: #0D1260; }
            /* Status bar blending, two cases:
               1. Safari browser: it colours the status bar by sampling the page's
                  top-edge background — so the top band of the body is painted navy
                  (invisible in the app itself: every page opens with a navy hero).
               2. Home-screen app (black-translucent): content flows under the
                  transparent status bar — pad it out; the navy band sits behind
                  the white clock/battery. */
            body.has-bottom-nav {
                /* ⚠️ THROUGH THE VARIABLE, SO THE APP CAN CORRECT IT (24 Sep 2026). These
                   three read `env()` directly, which nothing can override — and inside
                   the iOS shell the page is ALREADY held clear of the status bar while
                   `env(safe-area-inset-top)` still reports the notch, so the body padded
                   for an allowance that had been made twice. That is the band Kay
                   photographed above the hero on Home, Trips and Inspire: not a header,
                   just the body's own background filling a gap nothing needed.
                   `_app_shell.html` measures whether the shell has already inset the
                   webview and zeroes `--safe-top` when it has. In a browser the variable
                   is `env()` and nothing changes. */
                padding-top: var(--safe-top, 0px);
                /* Fill the whole viewport so the navy html backdrop never
                   peeks out below short pages (blue bar at the bottom). */
                min-height: 100vh;
                min-height: 100dvh;
                background:
                    linear-gradient(#0D1260 0,
                                    #0D1260 calc(var(--safe-top, 0px) + 80px),
                                    var(--tp-bg) calc(var(--safe-top, 0px) + 80px));
            }
        }

        /* ══════════════════════════════════════
           TOP NAVBAR
        ══════════════════════════════════════ */
        /* ⚠️ NOTHING MAY BE SCROLLED TO UNDERNEATH THE STICKY HEADER.
           The weekly desktop click audit, 23 Aug 2026, found the sort chips on Flight
           results unclickable at every desktop width:

             desktop Flight results: button.vs-chip "Stops" —
               <div class="container"> from <header class="tp-nav"> subtree intercepts
               pointer events

           The chips were fine; the NAVY BAR WAS ON TOP OF THEM. Scrolling an element to
           the top of the window puts it under a sticky header, and the click then lands
           on the header — the class of fault that looks perfect in a screenshot.

           `scroll-padding-top` tells the browser to keep the header's height clear
           whenever IT does the scrolling: anchors, tab-to-focus, and `scrollIntoView`.
           One rule, every page, instead of a `scroll-margin` on each thing we remember.
           It is zeroed on phones, where the top bar is hidden and the bottom one is the
           navigation. */
        html { scroll-padding-top: calc(var(--tp-nav-h) + 12px); }
        @media (max-width: 767px) { html { scroll-padding-top: 0; } }

        .tp-nav {
            position: sticky;
            top: 0;
            z-index: 1040;
            height: var(--tp-nav-h);
            background: #0D1260;   /* Window Seat navy */
            box-shadow: 0 2px 12px rgba(8,11,69,0.28);
        }
        .tp-nav .container {
            height: 100%;
            display: flex;
            align-items: center;
            gap: 8px;
        }

        /* Brand */
        .tp-brand {
            display: flex;
            align-items: center;
            text-decoration: none;
            flex-shrink: 0;
        }
        .tp-brand-logo-img {
            height: 56px;    /* desktop: logo fills more of the 64px navbar */
            width: auto;
            display: block;
        }

        /* Mobile navbar: 56px tall, 10px padding top/bottom, logo 36px */
        @media (max-width: 991.98px) {
            :root { --tp-nav-h: 56px; }

            .tp-brand-logo-img {
                height: 36px;
                width: auto;
            }
        }

        /* Desktop nav links (hidden on mobile) */
        .tp-nav-links {
            display: none;
            align-items: center;
            gap: 2px;
            margin-left: auto;
        }
        @media (min-width: 992px) {
            .tp-nav-links { display: flex; }
        }
        .tp-nav-link {
            color: rgba(255,255,255,0.82);
            text-decoration: none;
            font-size: 0.875rem;
            font-weight: 500;
            padding: 7px 13px;
            border-radius: 8px;
            display: flex;
            align-items: center;
            gap: 6px;
            transition: background 0.15s, color 0.15s;
            white-space: nowrap;
        }
        .tp-nav-link:hover { color: #fff; background: rgba(255,255,255,0.10); }
        .tp-nav-link.active { color: #fff; background: rgba(255,255,255,0.14); }
        .tp-nav-link.tp-nav-cta {
            background: var(--tp-orange);
            color: #fff;
            margin-left: 6px;
            padding: 7px 16px;
        }
        .tp-nav-link.tp-nav-cta:hover { background: var(--tp-orange-dark); color: #fff; }

        /* Mobile right utility strip (bell + avatar, visible only on mobile) */
        .tp-nav-mobile-utils {
            display: flex;
            align-items: center;
            gap: 4px;
            margin-left: auto;
        }
        @media (min-width: 992px) { .tp-nav-mobile-utils { display: none; } }
        .tp-nav-icon-btn {
            width: 38px; height: 38px;
            display: flex; align-items: center; justify-content: center;
            border-radius: 50%;
            color: rgba(255,255,255,0.85);
            text-decoration: none;
            font-size: 1.2rem;
            transition: background 0.15s;
        }
        .tp-nav-icon-btn:hover { background: rgba(255,255,255,0.12); color: #fff; }

        /* ══════════════════════════════════════
           BOTTOM TAB BAR  (mobile only)
        ══════════════════════════════════════ */
        .tp-bottom-nav {
            display: none;
            position: fixed;
            bottom: 0;
            left: 0; right: 0;
            height: calc(var(--tp-bottom-nav-h) + var(--safe-bottom));
            padding-bottom: var(--safe-bottom);
            background: var(--tp-surface);
            border-top: 1px solid var(--tp-border);
            box-shadow: 0 -4px 20px rgba(43,63,191,0.08);
            z-index: 1035;
            align-items: stretch;
        }
        @media (max-width: 991.98px) { .tp-bottom-nav { display: flex; } }

        /* ⚠️ OUT OF THE WAY WHILE THE KEYBOARD IS UP. Kay, 22 Aug 2026: "when you enter
           an origin for a search, the nav bar is moving up which it should not."

           It is not moving of its own accord. iOS Safari shrinks the VISUAL viewport when
           the keyboard opens and re-anchors `position: fixed` to it, so a bar pinned to
           `bottom: 0` is placed just above the keyboard — in the middle of the page, on
           top of the field below the one being typed in. Nothing in our CSS can hold it
           at the bottom of a viewport the browser has redefined.

           So it is hidden while a text field has focus, which is what native apps do: the
           tab bar is not reachable with a keyboard covering it anyway. The body keeps its
           padding, so nothing reflows underneath and the page does not jump. */
        body.kb-open .tp-bottom-nav { display: none !important; }

        .tp-tab {
            flex: 1;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: flex-end;
            padding-bottom: 10px;
            gap: 3px;
            text-decoration: none;
            color: #A0A6BF;
            font-size: 0.68rem;
            font-weight: 500;
            transition: color 0.15s;
            position: relative;
        }
        .tp-tab i { font-size: 1.3rem; line-height: 1; transition: transform 0.15s; }
        .tp-tab.active { color: #1C21C4; }                  /* Window Seat royal */
        .tp-tab.active i { transform: scale(1.08); }

        /* Kay, 14 Aug 2026: "Clicking on the navigation bar seems to be very sluggish.
           How can we make it more seamless and responsive?"

           Three separate causes, and only one of them was the server.

           1. HOVER ON A TOUCH TARGET. `.tp-tab:hover` applied to phones, where the
              first tap is spent applying hover rather than following the link — the
              identical fault fixed on the Explore button on 13 Aug. Hover is now
              behind a capability query, so a phone never sees it. */
        @media (hover: hover) and (pointer: fine) {
            .tp-tab:hover { color: #1C21C4; }
        }

        /* 2. NO ACKNOWLEDGEMENT. A tap did nothing visible until the next page
              painted, so every millisecond of load read as the app ignoring you. The
              pressed state lands in the same frame as the finger. */
        .tp-tab:active { color: #1C21C4; }
        .tp-tab:active i { transform: scale(0.92); }
        .tp-tab.tp-tab-going { color: #1C21C4; }
        .tp-tab.tp-tab-going i { transform: scale(1.08); }

        /* ⚠️ THE TAP MUST BE UNMISTAKABLE, because the wait is not ours to remove.
           Kay, 18 Aug 2026: "you can count to 2 easily before we see the page moving to
           the new page." Server render is 7-243ms and the droplet and the Cloudflare edge
           are both in Sydney, so the time is going somewhere on the device that we cannot
           see from here. What we CAN fix is that the screen looked frozen while it
           happened: a colour change and an 8% icon nudge are easy to miss on a phone held
           at arm's length, so nothing appeared to acknowledge the tap.

           This is honest about what it does — it does not make the page arrive sooner. It
           makes the wait legible, which is the difference between "slow" and "broken". */
        .tp-tab.tp-tab-going span { font-weight: 700; }
        .tp-tab.tp-tab-going .tp-search-bubble { transform: scale(1.06); }

        /* A thin bar across the top that starts the moment a tab is tapped. It advances
           quickly at first and then crawls, never reaching the end — it cannot know how
           long the page will take, and a bar that completes before the page arrives is a
           lie the customer will notice twice. The new page replaces it by existing. */
        #tpNavProgress {
            /* ⚠️ A 3px BAR AT top: 0 IS INVISIBLE IN THE APP — the status bar covers
               it exactly. It is the only signal a tap has been heard on a slow page, so
               it sits below the clock rather than behind it. */
            position: fixed; top: var(--safe-top, 0px); left: 0; height: 3px; width: 0;
            background: linear-gradient(90deg, #2B3FBF, #F97316);
            z-index: 2147483647; opacity: 0;
            transition: width .25s ease-out, opacity .2s ease-out;
            pointer-events: none;
        }
        #tpNavProgress.on { opacity: 1; }

        /* The spinner on whatever was tapped. It is drawn INSIDE the control's own box
           and does not change its size, because a control that grows as you press it
           moves everything under your thumb. */
        .tp-going { position: relative; }
        .tp-going::after {
            content: ''; position: absolute; right: 8px; top: 50%;
            width: 14px; height: 14px; margin-top: -7px;
            border: 2px solid currentColor; border-right-color: transparent;
            border-radius: 50%; opacity: .75;
            animation: tpGoingSpin .6s linear infinite;
            pointer-events: none;
        }
        @keyframes tpGoingSpin { to { transform: rotate(360deg); } }
        /* Kay's own rule, and R6.82's: someone who asked for less motion gets the
           acknowledgement without the spin. */
        @media (prefers-reduced-motion: reduce) {
            .tp-going::after { animation: none; border-right-color: currentColor;
                               opacity: .45; }
        }

        /* Someone who has asked for less motion still gets the bar — it is information,
           not decoration — but it appears rather than animating across. */
        @media (prefers-reduced-motion: reduce) {
            #tpNavProgress { transition: opacity .2s ease-out; }
            .tp-tab.tp-tab-going i,
            .tp-tab.tp-tab-going .tp-search-bubble { transform: none; }
        }

        /* 3. THE 300ms TAP DELAY that browsers hold back in case a second tap is a
              double-tap-to-zoom. Nothing in this bar wants that gesture. */
        .tp-bottom-nav a, .tp-tab { touch-action: manipulation; -webkit-tap-highlight-color: transparent; }

        /* Search tab — raised orange bubble */
        .tp-tab-search {
            justify-content: flex-start;
            padding-top: 0;
            padding-bottom: 8px;
        }
        .tp-tab-search .tp-search-bubble {
            width: 50px; height: 50px;
            background: var(--tp-orange);
            border-radius: 50%;
            display: flex; align-items: center; justify-content: center;
            margin-top: -18px;
            box-shadow: 0 4px 16px rgba(194,65,12,0.38);
            border: 3px solid var(--tp-bg);
            transition: transform 0.15s, box-shadow 0.15s;
        }
        .tp-tab-search:hover .tp-search-bubble,
        .tp-tab-search.active .tp-search-bubble {
            transform: translateY(-2px);
            box-shadow: 0 6px 20px rgba(194,65,12,0.48);
        }
        .tp-tab-search .tp-search-bubble i {
            color: #fff;
            font-size: 1.25rem;
            transform: none !important;
        }
        .tp-tab-search:hover, .tp-tab-search.active { color: var(--tp-orange-text,#C2410C); }

        /* Notification dot */
        .tp-tab .tp-notif-dot {
            position: absolute;
            top: 4px;
            right: calc(50% - 18px);
            width: 8px; height: 8px;
            background: var(--tp-orange);
            border-radius: 50%;
            border: 2px solid var(--tp-surface);
            display: none;
        }
        .tp-tab .tp-notif-dot.show { display: block; }

        /* ══════════════════════════════════════
           MAIN CONTENT
        ══════════════════════════════════════ */
        main { padding: 20px 0 32px; }
        @media (min-width: 992px) { main { padding: 28px 0 48px; } }
        /* Window Seat mobile: no top nav bar — bottom tab bar is the navigation.
           Page heroes bleed to the very top (per Claude Design). Logged-out pages keep the nav. */
        @media (max-width: 767px) {
            body.has-bottom-nav .tp-nav { display: none; }
            body.has-bottom-nav main { padding-top: 0; }
        }

        /* ══════════════════════════════════════
           FLASH TOASTS
        ══════════════════════════════════════ */
        /* × on a dismissible notice. Sits inside the panel's own padding so it
           never overlaps the heading, and keeps a 32px touch target. */
        .tp-dismiss {
            position: absolute; top: 6px; right: 8px;
            width: 32px; height: 32px; line-height: 1;
            display: inline-flex; align-items: center; justify-content: center;
            background: transparent; border: none; cursor: pointer;
            font-size: 1.35rem; color: inherit; opacity: .55; padding: 0;
        }
        .tp-dismiss:hover { opacity: 1; }
        .tp-dismiss:focus-visible { outline: 2px solid currentColor; outline-offset: 2px; }

        .tp-toasts {
            position: fixed;
            top: calc(var(--tp-nav-h) + 12px);
            right: 16px;
            /* Above the page and the nav (1040), BELOW every dialog. At 9999 a toast
               floated over open pop-ups and could cover their controls — the desktop
               click audit caught a success toast sitting on the feedback dialog's Close
               button at 1024px, leaving no way to dismiss it. Flash messages arrive on
               page load, so a dialog opened afterwards is a deliberate act and the toast
               has already been seen; a dialog winning is the right way round. */
            z-index: 1045;
            width: min(380px, calc(100vw - 32px));
            display: flex;
            flex-direction: column;
            gap: 8px;
        }
        .tp-toasts .alert {
            border: none;
            border-radius: 12px;
            box-shadow: var(--tp-shadow-lg);
            font-size: 0.875rem;
            font-weight: 500;
            margin: 0;
            padding: 12px 16px;
        }
        .tp-toasts .alert-success { background: #ecfdf5; color: #065f46; }
        .tp-toasts .alert-danger  { background: #fef2f2; color: #991b1b; }
        .tp-toasts .alert-warning { background: #fffbeb; color: #92400e; }
        .tp-toasts .alert-info    { background: #eff6ff; color: #1e40af; }

        /* ══════════════════════════════════════
           CARDS
        ══════════════════════════════════════ */
        .tp-card {
            background: var(--tp-surface);
            border: 1px solid var(--tp-border);
            border-radius: var(--tp-radius);
            box-shadow: var(--tp-shadow-sm);
        }
        .tp-card .tp-card-header {
            padding: 16px 20px;
            border-bottom: 1px solid var(--tp-border);
            font-weight: 600;
            font-size: 0.95rem;
            color: var(--tp-text);
            display: flex;
            align-items: center;
            gap: 8px;
        }
        .tp-card .tp-card-body { padding: 20px; }

        /* Bootstrap card override */
        .card {
            border: 1px solid var(--tp-border);
            border-radius: var(--tp-radius);
            box-shadow: var(--tp-shadow-sm);
        }
        .card-header {
            border-bottom: 1px solid var(--tp-border);
            border-radius: var(--tp-radius) var(--tp-radius) 0 0 !important;
            background: var(--tp-surface);
            font-weight: 600;
        }

        /* ══════════════════════════════════════
           BUTTONS
        ══════════════════════════════════════ */
        .btn {
            font-weight: 600;
            border-radius: 10px;
            padding: 9px 20px;
            font-size: 0.9rem;
            transition: all 0.15s;
        }
        .btn-sm { padding: 5px 12px; font-size: 0.82rem; border-radius: 7px; }
        .btn-lg { padding: 13px 28px; font-size: 1rem; border-radius: 12px; }

        .btn-primary {
            background: var(--tp-blue);
            border-color: var(--tp-blue);
            color: #fff;
        }
        .btn-primary:hover, .btn-primary:focus {
            background: var(--tp-blue-dark);
            border-color: var(--tp-blue-dark);
            color: #fff;
        }
        .btn-orange {
            background: var(--tp-orange);
            border-color: var(--tp-orange-text,#C2410C);
            color: #fff;
        }
        .btn-orange:hover, .btn-orange:focus {
            background: var(--tp-orange-dark);
            border-color: var(--tp-orange-dark);
            color: #fff;
        }
        .btn-outline-primary {
            color: var(--tp-blue);
            border-color: var(--tp-blue);
            border-width: 1.5px;
        }
        .btn-outline-primary:hover {
            background: var(--tp-blue);
            border-color: var(--tp-blue);
            color: #fff;
        }

        /* ══════════════════════════════════════
           FORM CONTROLS
        ══════════════════════════════════════ */
        .form-label {
            font-weight: 600;
            font-size: 0.83rem;
            color: var(--tp-text-muted);
            text-transform: uppercase;
            letter-spacing: 0.4px;
            margin-bottom: 6px;
        }
        .form-control, .form-select {
            border: 1.5px solid var(--tp-border);
            border-radius: 10px;
            padding: 10px 14px;
            font-size: 0.95rem;
            /* ⚠️ background-COLOR, not the `background` shorthand: the shorthand also wiped
               Bootstrap's arrow from every .form-select on the site, so dropdowns looked like
               text boxes (Kay, 1 Oct 2026, "Assign to trip"). */
            background-color: var(--tp-surface);
            color: var(--tp-text);
            transition: border-color 0.15s, box-shadow 0.15s;
        }
        /* room for the arrow the rule above stopped wiping: base.css's padding out-ranked
           Bootstrap's 2.25rem, so long text ran under it (critic, 1 Oct 2026) */
        .form-select { padding-right: 2.25rem; }
        .form-control::placeholder { color: #6F7592; }
        /* Prevent iOS Safari zoom-on-focus: any text field <16px makes the page zoom in
           (and not back out). Force 16px on phones/tablets, overriding inline sizes. */
        @media (max-width: 991.98px) {
            input:not([type="checkbox"]):not([type="radio"]):not([type="range"]),
            select, textarea { font-size: 1rem !important; }
        }
        .form-control:focus, .form-select:focus {
            border-color: var(--tp-blue);
            box-shadow: 0 0 0 3px rgba(43,63,191,0.12);
            background: var(--tp-surface);
        }
        .input-group-text {
            background: var(--tp-bg);
            border: 1.5px solid var(--tp-border);
            color: var(--tp-blue);
            font-size: 1rem;
        }
        .input-group > .input-group-text { border-radius: 10px 0 0 10px; }
        .input-group > .form-control,
        .input-group > .form-select { border-radius: 0 10px 10px 0; border-left: none; }
        .input-group > .input-group-text + .form-control:focus { border-left: 1.5px solid var(--tp-blue); margin-left: -1.5px; }

        .form-text { font-size: 0.78rem; color: var(--tp-text-muted); margin-top: 4px; }

        /* ══════════════════════════════════════
           SECTION HEADERS
        ══════════════════════════════════════ */
        .tp-page-title {
            font-size: 1.4rem;
            font-weight: 700;
            color: var(--tp-text);
            margin: 0;
        }
        @media (min-width: 992px) { .tp-page-title { font-size: 1.65rem; } }
        .tp-page-subtitle {
            color: var(--tp-text-muted);
            font-size: 0.9rem;
            margin: 4px 0 0;
        }

        /* ══════════════════════════════════════
           BADGES & STATUS
        ══════════════════════════════════════ */
        .tp-badge {
            display: inline-flex;
            align-items: center;
            gap: 4px;
            padding: 3px 10px;
            border-radius: 20px;
            font-size: 0.75rem;
            font-weight: 600;
        }
        .tp-badge-blue   { background: var(--tp-blue-muted);   color: var(--tp-blue); }
        .tp-badge-orange { background: var(--tp-orange-muted); color: var(--tp-orange-dark); }
        .tp-badge-green  { background: #ecfdf5; color: #16a34a; }
        .tp-badge-red    { background: #fef2f2; color: #dc2626; }
        .tp-badge-gray   { background: #f3f4f6; color: #6b7280; }

        /* ══════════════════════════════════════
           TABLES
        ══════════════════════════════════════ */
        .tp-table { width: 100%; border-collapse: collapse; }
        .tp-table th {
            font-size: 0.75rem;
            font-weight: 600;
            text-transform: uppercase;
            letter-spacing: 0.5px;
            color: var(--tp-text-muted);
            padding: 10px 16px;
            background: var(--tp-bg);
            border-bottom: 1px solid var(--tp-border);
        }
        .tp-table td {
            padding: 12px 16px;
            border-bottom: 1px solid var(--tp-border);
            font-size: 0.9rem;
            vertical-align: middle;
        }
        .tp-table tr:last-child td { border-bottom: none; }
        .tp-table tbody tr:hover { background: var(--tp-bg); }

        /* ══════════════════════════════════════
           DIVIDER
        ══════════════════════════════════════ */
        .tp-divider {
            border: none;
            border-top: 1px solid var(--tp-border);
            margin: 20px 0;
        }
        .tp-divider-text {
            display: flex;
            align-items: center;
            gap: 12px;
            color: var(--tp-text-muted);
            font-size: 0.8rem;
            font-weight: 500;
            margin: 20px 0;
        }
        .tp-divider-text::before,
        .tp-divider-text::after {
            content: '';
            flex: 1;
            border-top: 1px solid var(--tp-border);
        }

        /* ══════════════════════════════════════
           FOOTER — REMOVED 25 Jul 2026
           Kay: "The old footer in desktop view can be removed on the app. It does not
           serve any purpose." It was a copyright line shown on desktop only (hidden
           below 992px), and it had been pinned with an inline position:fixed, laying an
           opaque band across the bottom of every desktop page. Markup and styles both
           gone rather than merely hidden — nothing links to it and nothing else uses
           the class.
        ══════════════════════════════════════ */

        /* ══════════════════════════════════════
           UTILITY
        ══════════════════════════════════════ */
        .text-blue   { color: var(--tp-blue)   !important; }
        .text-orange { color: var(--tp-orange-text,#C2410C) !important; }
        .bg-blue     { background: var(--tp-blue)   !important; }
        .bg-orange   { background: var(--tp-orange) !important; }
        .text-muted  { color: var(--tp-text-muted) !important; }

        /* Smooth link transitions */
        a { transition: color 0.12s; }

        /* Loading shimmer (for future async content) */
        @keyframes shimmer {
            0%   { background-position: -600px 0; }
            100% { background-position: 600px 0; }
        }
        .tp-shimmer {
            background: linear-gradient(90deg, #f0f2fb 25%, #e4e7f4 50%, #f0f2fb 75%);
            background-size: 600px 100%;
            animation: shimmer 1.4s infinite;
            border-radius: 6px;
        }

/* ── THE HERO REACHES UP BEHIND THE CLOCK (Kay, 26 Sep 2026, from the app) ────────────────
   *"on the home page, search and the trip page, we still have a dark blue banner at the top.
   You can see this as there is no highlight in the blue. Can this be the same background as
   the top banner so the shading continues seamlessly."*
   The strip behind the clock was the body's own flat navy (its padding for the inset); each
   page's hero, with its gradient and glow, started below it — a hard line. On phones the first
   hero now takes the inset back: pulled up by it and padded by it, so its own background runs
   to the top of the screen. `--safe-top` is 0 in a browser, so there this is a no-op.
   Higher specificity than the page-local copies of these rules, which load after this file. */
@media (max-width: 767px) {
    /* ⚠️ THE INSET IS ADDED TO THE HERO'S OWN TOP PADDING, NOT PUT IN PLACE OF IT. Five heroes
       set their padding inline (Plan, plan choose, notifications, upgrade, upgrade success);
       an inline style beats any stylesheet rule without !important, so the first version pulled
       Plan's hero up by the inset but padded it by only 26px, and "PLAN A TRIP" sat under the
       clock (critic, 26 Sep 2026). Those heroes now also carry their padding as `--hero-pt`. */
    html body .tp-ws .ws-hero {
        margin-top: calc(-1 * var(--safe-top, 0px));
        padding-top: calc(var(--safe-top, 0px) + var(--hero-pt, 0px)) !important;
    }
    /* Home with a search running wraps the hero and the strip in ONE navy surface
       (`.ws-navy`, dashboard state B): that wrapper is what reaches up, not the hero inside. */
    html body .tp-ws .ws-navy {
        margin-top: calc(-1 * var(--safe-top, 0px));
        padding-top: var(--safe-top, 0px);
    }
    html body .tp-ws .ws-navy .ws-hero {
        margin-top: 0 !important;
        padding-top: var(--hero-pt, 0px) !important;
    }
    /* Anything PINNED inside a hero (Trips' profile picture sits at top:16px) is placed from the
       hero's edge, not its padding, so it would ride up under the clock with the edge. The glow
       is the one thing meant to go up there. */
    html body .tp-ws .ws-hero > [style*="position:absolute"]:not(.ws-hero-glow) {
        margin-top: var(--safe-top, 0px);
    }
    html body .td-header {
        margin-top: calc(-1 * var(--safe-top, 0px));
        padding-top: calc(12px + var(--safe-top, 0px));
    }
    /* the flight and hotel search pages' photo banner too (Kay, 1 Oct 2026: "you can still see
       a solid blue banner on top. The background image should be extended like in the other
       pages") — their own phone rules set margin with !important, so these must as well */
    html body .vs-page-head, html body .vhs-page-head {
        margin-top: calc(-1 * var(--safe-top, 0px)) !important;
        padding-top: calc(16px + var(--safe-top, 0px)) !important;
    }
}

/* ══ AN OPEN SHEET STAYS ABOVE THE KEYBOARD (base.js) ═══════════════════════════════
   While the keyboard is up, base.js fits the backdrop of the sheet being typed in to the visible
   area (`tp-kb-fit`); these then fit the sheet inside it, and the sheet scrolls inside itself.
   With no keyboard nothing here applies. Kept here, as these sheets are on eight pages (Kay,
   10 Oct 2026).
   ⚠️ THE SCROLL IS SET HERE TOO, not trusted to the sheet (reviewer, 10 Oct 2026): the
   "Add to my timeline" sheet's own styles (_insight_lockin.html) say `overflow: hidden`, and on
   the activity pages they are the only ones — shortened without this, its buttons would be cut
   off with no way to reach them until the keyboard went down. */
.td-modal-overlay.tp-kb-fit .td-modal-box,
.td-sheet-bg.tp-kb-fit .td-sheet,
#fqmOverlay.tp-kb-fit #fqmModal,
#hqmOverlay.tp-kb-fit #hqmModal { max-height: calc(100% - 8px); overflow-y: auto; }
/* Add a place keeps a FIXED height so its results never push it upward (Kay, 5 Sep 2026); fitted,
   that height is the visible area */
.ap-sheet-bg.tp-kb-fit .ap-sheet { height: calc(100% - 8px); max-height: calc(100% - 8px); }
