/* ============================================================================
   Ticket surface — shared, sitewide CSS
   ----------------------------------------------------------------------------
   TWO blocks, both moved verbatim out of themes/te.3/lib/styles/src/modal.css:

     PART 1 (below)  the `page-te` ticket-modal restyling, PLUS the two
                     components the sheet depends on and that were never
                     sheet-scoped: `.te-checkout-bar` (the sticky checkout bar)
                     and `#tm-fullscreen` (the fullscreen gallery).

     PART 2          the [data-variant='sheet'] block itself.

   Part 1 has to travel with Part 2. te.3's sheet is built ON TOP of the
   `page-te` modal — the sheet block only adds its diffs — so a host that opts
   into the sheet without these rules gets the right structure with the wrong
   layout. That is why the Google/trust block, the header chrome and the sticky
   checkout bar did not match on seating-areas.

   A host opts in by passing `pageId: "te"` to TicketSheet, exactly as te.3
   does. Everything here is scoped to `#modal-ticket` / `.page-te` /
   `.te-checkout-bar` / `#tm-fullscreen`, so it is inert on any page that does
   not open one of these surfaces — including the plain ticket modal in the
   control arm, which never gets the `page-te` class.
   ========================================================================= */

/* ============================================================
   te.3 — TICKET MODAL (pageId "te") overrides
   ------------------------------------------------------------
   The shared TicketModal (themes/assets/lib/scripts/ticketsManager.js)
   tags its content <main> with `page-te` when opened with pageId:"te",
   so these rules only touch te's modal — the shared modal on other
   pages (te.1, ti.2, …) is untouched. The te-only checkout bars are
   scoped by their own classes / the fullscreen dialog id.
   ============================================================ */

/* #11 — Heroize: hero goes edge-to-edge (breaks out of <main>'s horizontal
   padding via the shared inline-bleed trick) with no image/hero radius.
   The negative margin MUST equal main's actual padding-inline (ticket-modal.css:
   `#modal-ticket.cmodal main { padding-inline: clamp(16px,3vw,24px) }`), which
   wins over the --modal-main-gap rule — so match that literal, not the var, or
   the two won't cancel and you get a side gap. Applied at all widths. */
#modal-ticket main.page-te > section[data-role='hero'] {
   width: auto;
   /* !important is required: the base `#modal-ticket section { margin:0 !important }`
      would otherwise zero this out and leave the side gap. */
   margin-inline: calc(-1 * clamp(16px, 3vw, 24px)) !important;
   border-radius: 0;
}
#modal-ticket main.page-te section[data-role='hero'] img,
#modal-ticket main.page-te section[data-role='hero'] figure,
#modal-ticket main.page-te section[data-role='hero'] figure.caption-overlay {
   border-radius: 0;
}

/* Pagination dots: keep the ~5-dot window, but SHRINK the edge dots (via [data-dist],
   set per-dot in zero.js) instead of hard-clipping them — nothing gets chopped on the
   sides. dist 0-1 full · 2 smaller · 3 tiny · 4+ gone. */
#modal-ticket.page-te [data-role='gallery-dots-viewport'] { overflow: visible; }
#modal-ticket.page-te [data-role='gallery-dots'] span[data-dist='2'] { transform: scale(.6); }
#modal-ticket.page-te [data-role='gallery-dots'] span[data-dist='3'] { transform: scale(.35); }
#modal-ticket.page-te [data-role='gallery-dots'] span[data-dist='4'] { transform: scale(0); }

/* #5 — Google/trust block relocated into Details (pageId te): its edge-to-edge
   bleed is now handled by the shared `.modal-bleed` class (ticket-modal.css),
   applied on the trust <section> in ticketsManager.js — no bespoke math here. */

/* ------------------------------------------------------------
   te checkout bar — shared by the sticky pop-up (#7) and the photo
   gallery (#8). Mirrors the modal's checkout section: a quantity
   select + a large Checkout button, side by side.
   ------------------------------------------------------------ */
.te-checkout-bar {
   position: fixed;
   left: 0;
   right: 0;
   bottom: 0;
   z-index: 30;
   background: #fff;
   box-shadow: 0 -6px 20px rgba(0, 0, 0, .18);
}
.te-checkout-inner {
   display: flex;
   align-items: center;
   gap: 16px;                          /* = modal checkout section */
   margin: 0 auto;
   padding: 12px 16px calc(12px + env(safe-area-inset-bottom));
}
/* Quantity select + fixed-qty span — EXACTLY the modal's checkout controls
   (ticket-modal.css §checkout): light #f7fafd fill, native caret, 7px radius,
   subtle border + shadow. No bespoke restyle. */
.te-checkout-bar select[name='quantity-selection'],
.te-checkout-bar .te-checkout-inner > span {
   flex: 1 1 0;
   min-width: 0;
   box-sizing: border-box;
   padding: 12px 16px;
   border: 1px solid #E5E8EF;
   border-radius: 7px;
   background: #f7fafd;
   color: #232328;
   font-size: 16px;
   font-weight: 500;
   box-shadow: 0 2px 8px rgba(0, 0, 0, .03);
}
.te-checkout-bar select[name='quantity-selection'] { appearance: auto; -webkit-appearance: auto; }
.te-checkout-bar .te-checkout-inner > span { display: inline-flex; align-items: center; justify-content: center; }
/* Checkout button — EXACTLY the modal's [data-role='go-to-checkout']. */
.te-checkout-bar .te-checkout-btn {
   flex: 1 1 0;
   min-width: 0;
   display: inline-flex;
   align-items: center;
   justify-content: center;
   padding: 12px 0;
   background: #0067ce;
   color: #fff;
   font-weight: 500;
   font-size: 17px;
   text-decoration: none;
   border-radius: 7px;
   box-shadow: 0 2px 8px rgba(0, 0, 0, .03);
   cursor: pointer;
   white-space: nowrap;
}

/* #7 sticky variant — hidden until the real checkout CTA scrolls out of view
   (IntersectionObserver toggles .is-visible). */
.te-checkout-bar--sticky {
   transform: translateY(100%);
   transition: transform .28s cubic-bezier(.32, .72, 0, 1);
   pointer-events: none;
}
.te-checkout-bar--sticky.is-visible {
   transform: translateY(0);
   pointer-events: auto;
}

/* #8 gallery variant — always visible, above the fullscreen <dialog>. */
.te-checkout-bar--gallery {
   z-index: 2147483000;
}

/* Reserve space at the very bottom of the modal content so the fixed sticky checkout
   bar (which pops in on scroll) never covers the last content. Always present — even
   when the bar is hidden — so nothing gets cut off either way. */
#modal-ticket.page-te main {
   padding-bottom: calc(124px + env(safe-area-inset-bottom));
}

/* ============================================================
   #tm-fullscreen — full-screen photo viewer (pageId te).
   Built by _openFullscreenGallery. Flex column: top bar / photo
   stage (the "safe space") / bottom ticket card. Canvas = #111.
   ============================================================ */
dialog#tm-fullscreen.tmfs {
   width: 100vw; max-width: 100vw;
   height: 100vh; max-height: 100vh;      /* fallback for old browsers */
   height: 100dvh; max-height: 100dvh;    /* actual VISIBLE viewport — keeps the action strip
                                             above the browser UI / Android nav controls */
   margin: 0; padding: 0; border: none;
   background: #111111;                 /* #6 photo canvas color */
   overflow: hidden;
   display: flex; flex-direction: column;
}
dialog#tm-fullscreen.tmfs::backdrop { background: #111111; }

/* top bar: back arrow (left) + photo count (right) */
.tmfs-top {
   flex: 0 0 auto; position: relative; z-index: 3;
   display: flex; align-items: center; justify-content: space-between;
   gap: 12px; padding: 10px 12px;
}
.tmfs-back {
   width: 40px; height: 40px; display: grid; place-items: center;
   border: none; border-radius: 50%;
   color: #fff;
   cursor: pointer; -webkit-tap-highlight-color: transparent;
}
.tmfs-back:active { transform: scale(.94); }
.tmfs-count { color: #fff; font-size: 14px; font-weight: 600; font-variant-numeric: tabular-nums; }

/* photo stage = the SAFE SPACE between the top bar and the bottom card */
.tmfs-stage { flex: 1 1 auto; min-height: 0; position: relative; }
.tmfs-track {
   width: 100%; height: 100%; display: flex;
   overflow-x: auto; scroll-snap-type: x mandatory;
   -webkit-overflow-scrolling: touch; overscroll-behavior: contain;
}
.tmfs-track::-webkit-scrollbar { display: none; }
.tmfs-slide {
   position: relative; flex: 0 0 100%; height: 100%;
   scroll-snap-align: center;
   display: flex; flex-direction: column; align-items: center; justify-content: center;
   overflow: hidden;
}
/* photo: vertically centered, edge-to-edge width, cover-crop when taller than the safe space */
.tmfs-img {
   width: 100%; max-width: 100%;
   max-height: calc(100% - 40px); /* guaranteed room for caption*/
   object-fit: cover;
   touch-action: pan-x;                 /* horizontal swipe flips photos when not zoomed */
   transform-origin: center center;
   will-change: transform;
   -webkit-user-select: none; user-select: none;
}
.tmfs-slide.is-zoomed .tmfs-img { touch-action: none; cursor: grab; }

/* caption: sits directly below the image; sticky:bottom keeps it inside the safe space,
   so a photo tall enough to reach the bottom gets the caption OVERLAPPING its lower edge
   (the 80% bg keeps the photo readable through it). CSS-only (no JS). */
.tmfs-cap {
   position: sticky; bottom: 0;
   z-index: 1;
   max-width: 720px; width: max-content;
   margin: 0; padding: 8px 16px; box-sizing: border-box;
   text-align: center; text-wrap: balance;
   color: #fff; font-size: 13px; line-height: 1.35;
   background: rgba(17, 17, 17, .8);
}

/* bottom card: ticket sec/row + price and the checkout actions */
.tmfs-card {
   flex: 0 0 auto;
   display: flex; flex-wrap: wrap; align-items: center; gap: 12px;
   margin: 12px;                        /* 12px on all sides */
   margin-bottom: calc(12px + env(safe-area-inset-bottom, 0px));   /* clear gesture-nav bars */
   padding: 12px;
   background: #fff; border-radius: 4px; /* 4px rounded corners */
}
/* seat details + price, next to each other */
.tmfs-card-info { flex: 1 1 100%; display: flex; align-items: baseline; gap: 6px; min-width: 0; font-size: 13px; color: var(--color-90);}
.tmfs-seats {min-width: 0; }
.tmfs-price { white-space: nowrap; }
.tmfs-price span { font-weight: 500; font-size: 12px; color: var(--color-60, #666); margin-left: 1px; }

.tmfs-qty, .tmfs-checkout { flex: 1 1 0; height: 40px; }   /* mobile: 50% each; (6a) 8px shorter */
.tmfs-qty { min-width: 0; }
/* Quantity control — matches the ticket-modal checkout: native caret on the <select>,
   and the same light #f7fafd fill on BOTH the select and the fixed-qty <span> (so a
   single-split ticket reads as a disabled selector). */
.tmfs-qty select[name='quantity-selection'],
.tmfs-qty > span {
   box-sizing: border-box;
   width: 100%; height: 40px; padding: 0 14px;
   border: 1px solid #E5E8EF; border-radius: 7px;
   background: #f7fafd; color: #232328;
   font-size: 16px; font-weight: 500;
   box-shadow: 0 2px 8px rgba(0, 0, 0, .03);
}
.tmfs-qty select[name='quantity-selection'] { appearance: auto; -webkit-appearance: auto; }
.tmfs-qty > span { display: inline-flex; align-items: center; justify-content: center; }
.tmfs-checkout {
   display: inline-flex; align-items: center; justify-content: center;
   background: var(--color-brand, #0067CE); color: #fff; font-weight: 700; font-size: 16px;
   text-decoration: none; border-radius: 8px; cursor: pointer;
}
.tmfs-checkout:active { transform: scale(.98); }

/* >= 600px: details inline with the buttons (up to 2 lines); buttons fixed 160px;
   card shrinks to its contents and right-aligns. */
@media (min-width: 600px) {
   .tmfs-card {
      flex-wrap: nowrap;
      align-self: flex-end;             /* right-align; width = contents */
      width: auto; max-width: calc(100% - 24px);
   }
   .tmfs-card-info { flex: 0 1 auto; max-width: 360px; margin-inline: 12px; }
   .tmfs-seats {                        /* allow up to two lines */
      white-space: normal;
      display: -webkit-box; -webkit-line-clamp: 2; line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
   }
   .tmfs-price { font-size: 16px; }
   .tmfs-qty, .tmfs-checkout { flex: 0 0 160px; width: 160px; }
}

/* ============================================================
   Ticket modal hero header — pageId "te", fullscreen-modal widths only
   (≤ 767px, matching the modal's fullscreen cutoff — extras.css /
   --bp-modal-fullscreen / TicketModal.FULLSCREEN_MAX_WIDTH).
   Onload: full-bleed 260px hero + an overlay circle back button,
   bottom-left condensed caption, wider location preview, and NO header
   strip / × close. On scroll a fixed centered bar (back arrow + seat/price)
   parallaxes in, cross-fading with the circle. Driven by --te-hdr (0..1),
   set on #modal-ticket by _setupTeHeroHeader. ≥768px (windowed modal) is
   untouched (header keeps its centered title + × close; back arrows hidden).
   ============================================================ */

/* the two custom back arrows only exist in the fullscreen (≤767px) chrome */
#modal-ticket.page-te .te-hdr-back,
#modal-ticket.page-te .te-hero-back { display: none; }

@media (max-width: 767px) {   /* = --bp-modal-fullscreen (tickets.css) */
   /* hero flush at the very top — the header strip is now a fixed overlay */
   /* flush to the very top: kill main's padding-top AND the .cmodal main margin-block-start:8px */
   #modal-ticket.page-te main { padding-top: 0; margin-block-start: 0; }

   /* fixed parallax header bar — fades in as we scroll past the hero */
   #modal-ticket.page-te hgroup {
      position: fixed; top: 0; left: 0; right: 0; z-index: 20;
      display: flex; align-items: center;
      height: 54px;
      background: #fff; border-bottom: 1px solid #E5E8EF; border-radius: 0;
      opacity: var(--te-hdr, 0);
      pointer-events: none;                     /* JS enables once faded in */
   }
   #modal-ticket.page-te .is-close { display: none; }     /* no × on mobile */

   #modal-ticket.page-te hgroup .is-header {
      position: relative; width: 100%; padding: 0 44px;
      line-height: normal; text-align: center;
      font-size: 15px; font-weight: 600; color: var(--color-primary, #002448);
   }
   #modal-ticket.page-te .te-hdr-title {
      display: inline-block; max-width: 100%;
      white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
   }
   #modal-ticket.page-te .te-hdr-back {
      display: grid; place-items: center;
      position: absolute; left: 6px; top: 50%; transform: translateY(-50%);
      width: 40px; height: 40px; border: none; background: none;
      color: var(--color-primary, #002448);
      cursor: pointer; -webkit-tap-highlight-color: transparent;
   }

   /* overlay circle back button on the hero (onload) — cross-fades out on scroll */
   #modal-ticket.page-te .te-hero-back {
      display: grid; place-items: center;
      position: fixed; top: 12px; left: 12px; z-index: 21;
      width: 40px; height: 40px; border: none; border-radius: 50%;
      background: rgba(0, 0, 0, .65); color: #fff;
      cursor: pointer; -webkit-tap-highlight-color: transparent;
      opacity: calc(1 - var(--te-hdr, 0));
   }

   /* hero image: full-width, fixed 260px, object-fit cover */
   #modal-ticket.page-te section[data-role='hero'] figure.caption-overlay,
   #modal-ticket.page-te section[data-role='hero'] .skeleton {
      aspect-ratio: auto; height: 260px;
   }
   #modal-ticket.page-te section[data-role='hero'] img { border-radius: 0; }

   /* caption: condensed 11px, bottom-left — capped at 180px wide, max 2 lines */
   #modal-ticket.page-te [data-role='hero'] figcaption {
      top: unset; right: unset; bottom: 8px; left: 8px;
      max-width: 160px;
      font-size: 11px; line-height: 1.2; padding: 3px 8px; border-radius: 6px;
      background: rgba(0, 0, 0, .6); color: #fff;
      white-space: normal;
      display: -webkit-box; -webkit-line-clamp: 2; line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
   }

   /* location preview: fixed 96 x 64 */
   #modal-ticket.page-te [data-role='gallery-overlay'] {
      width: 96px; min-width: 0; max-width: none;
   }
   #modal-ticket.page-te [data-role='gallery-overlay'] [data-overlay-target] {
      aspect-ratio: auto; height: 64px;
   }
}


/* ============================================================================
   TicketSheet — shared, sitewide sheet CSS
   ----------------------------------------------------------------------------
   Moved here verbatim from themes/te.3/lib/styles/src/modal.css (the
   [data-variant='sheet'] block) so te.3 and seating-areas share one stylesheet
   and the sheet cannot look different in the two places.

   Everything is scoped to #modal-ticket[data-variant='sheet'], which the shared
   Modal stamps only when TicketSheet opens — so this file is inert on every
   other page and for the plain ticket modal.

   TWO FRAMES, ONE SET OF CONTENT RULES
   ------------------------------------
   The FRAME (how the dialog is positioned and what scrolls) differs per host and
   is scoped with [data-sheet-mode]:

     route-a     te.3 — flow content inside the app's own #evBody scroller
     app-scroll  seating-areas — flow content after #app-scroll, body scrolls

   Everything below the frame — header bar, headline, stub, deal gauge, hero,
   MLB list, reviews — is UNSCOPED and shared by both. That is the point: the
   content is identical because it is literally the same rules.
   ========================================================================= */


/* ============================================================
   TicketSheet — sheet variant (A/B arm)
   ------------------------------------------------------------
   All scoped to #modal-ticket[data-variant='sheet'][data-sheet-mode='route-a'], stamped by the
   shared Modal (variant option) when TicketSheet opens. Reuses every
   #modal-ticket / .cmodal / .page-te rule and only ADDS the sheet diffs.
   The dollar-tag + expand icons live in lib/base/styles/src/icons.css.
   ============================================================ */
/* ---- Route A frame (te.1 "scroll-over" model) ----------------------
   The sheet is FLOW content inside .ev-body (the app's own scroller), over the
   PINNED .ev-map (pinned in JS). .ev-body is a plain pointer-events:auto scroller
   → iOS scrolls it natively, and the map stays live in the gap above the sheet.
   NO pointer-events:none anywhere. Validated in _lab/KH/sheet-scroll-proto. */

/* .ev-body becomes the scroller while a sheet is open; hide the list + its chrome. */
#evBody.ts-sheet-open {
   overflow-y: auto !important;
   -webkit-overflow-scrolling: touch;
   overscroll-behavior: contain;
}
/* Filters (.ev-strip) stay in view — hiding them flickered on ticket→ticket swaps. */
#evBody.ts-sheet-open .ev-list,
#evBody.ts-sheet-open .ev-tomap,
#evBody.ts-sheet-open .ev-scrollpill,
#evBody.ts-sheet-open .ev-totop { display: none !important; }

/* The sheet dialog: normal flow content; margin-top (the peek gap) is set inline. */
#modal-ticket[data-variant='sheet'][data-sheet-mode='route-a'].cmodal,
#modal-ticket[data-variant='sheet'][data-sheet-mode='route-a'] {
   position: relative !important;
   inset: auto !important;
   margin: 0;                            /* margin-top set inline = the peek gap */
   width: 100% !important;
   max-width: 100% !important;
   height: auto !important;
   max-height: none !important;
   overflow: visible !important;
   background: transparent !important;
   border: 0 !important;
   padding: 0 !important;
   box-shadow: none !important;
   z-index: 2 !important;                /* above the pinned map (z:1) */
   pointer-events: auto;
   --ts-shadow: 0px 6px 10px -8px rgba(0, 0, 0, .3);
}
#modal-ticket[data-variant='sheet'][data-sheet-mode='route-a']::backdrop { background: transparent; }

/* Calm entrance — a short eased rise + fade on the sheet body. */
@keyframes ts-sheet-in {
   from { transform: translateY(24px); opacity: 0; }
   to   { transform: translateY(0);    opacity: 1; }
}
#modal-ticket[data-variant='sheet'][data-sheet-mode='route-a'] .modal-container { animation: ts-sheet-in .3s cubic-bezier(.22, .61, .36, 1); }
@media (prefers-reduced-motion: reduce) {
   #modal-ticket[data-variant='sheet'][data-sheet-mode='route-a'] .modal-container { animation: none; }
}

/* The white sheet body — scrolls (with .ev-body) up over the pinned map. */
#modal-ticket[data-variant='sheet'][data-sheet-mode='route-a'] .modal-container {
   position: static !important;
   left: auto !important;
   top: auto !important;
   transform: none !important;
   width: 100% !important;
   max-width: 100% !important;
   border-radius: 16px 16px 0 0 !important;
   background: #fff !important;
   height: auto !important;
   min-height: 100dvh;                   /* enough to fully cover the map when scrolled up */
   max-height: none !important;
   overflow: visible !important;
   box-shadow: 0 -10px 26px -4px rgba(0, 0, 0, .3);   /* lift off the map */
   pointer-events: auto;
}
#modal-ticket[data-variant='sheet'][data-sheet-mode='route-a'] hgroup,
#modal-ticket[data-variant='sheet'][data-sheet-mode='route-a'] main { pointer-events: auto; background: #fff; }

/* Neutralize the body scroll-lock (the iOS freeze) while a sheet is open. Panel
   mode is included: it scrolls inside the left column, so a body-level lock has
   nothing to protect and only risks freezing the shell around it. */
body.scroll-lock:has(#modal-ticket[data-variant='sheet'][data-sheet-mode='panel']),
body.scroll-lock:has(#modal-ticket[data-variant='sheet'][data-sheet-mode='route-a']) {
   height: auto !important;
   overflow-y: visible !important;
}



/* ═══════════════════════════════════════════════════════════════════════════
   FRAME — app-scroll (seating-areas)
   ---------------------------------------------------------------------------
   The page scrolls on <body>. While a sheet is open, #app-scroll is locked to a
   viewport-sized sticky box (so it stays in view and stops competing for the
   gesture) and the sheet becomes flow content after it, pulled up by --ts-peek.
   The sheet's own height is what makes the document scrollable, so scrolling
   the body raises the sheet.

   `.ts-sheet-lock` is set on <html> by TicketSheet._mountAppScroll and removed
   on close, so NOTHING here applies to a normal page load.
   ═══════════════════════════════════════════════════════════════════════════ */

html.ts-sheet-lock #app-scroll {
   position: sticky;
   top: 0;
   height: 100dvh;
   overflow: hidden;              /* locked: the body is the only scroller now */
   z-index: 1;
}

/* Only ONE snap point, on the sheet. Adding a second at the top makes CSS
   scroll-snap pick whichever is NEARER on release — which is distance-based and
   ignores velocity, so every scroll under half the gap springs back and the
   sheet feels like it's fighting you. Measured, then removed. */
html.ts-sheet-lock { scroll-snap-type: y proximity; }

#modal-ticket[data-variant='sheet'][data-sheet-mode='app-scroll'].cmodal,
#modal-ticket[data-variant='sheet'][data-sheet-mode='app-scroll'] {
   --ts-peek: 400px;

   position: relative !important;
   inset: auto !important;
   z-index: 2 !important;                    /* above the locked #app-scroll */

   /* Normal-flow top is right after #app-scroll (i.e. 100dvh), and we want it at
      100dvh - peek, so the pull-up is exactly -peek. */
   margin: calc(0px - var(--ts-peek)) 0 0 0 !important;
   width: 100% !important;
   max-width: 100% !important;
   height: auto !important;
   min-height: 100dvh;                       /* enough to cover the app when fully up */
   max-height: none !important;

   overflow: visible !important;
   background: transparent !important;
   border: 0 !important;
   padding: 0 !important;
   box-shadow: none !important;
   pointer-events: auto;

   scroll-snap-align: start;
   --ts-shadow: 0px 6px 10px -8px rgba(0, 0, 0, .3);
}

#modal-ticket[data-variant='sheet'][data-sheet-mode='app-scroll']::backdrop {
   background: transparent;
}

/* The shared Modal focuses the dialog programmatically (so Esc / CloseWatcher
   reach it). In this mode the dialog's box is the whole flow area, so the UA
   focus ring paints a black rectangle around the sheet AND the exposed page.
   Suppressed on the CONTAINER only — every interactive child keeps its own
   focus style, so keyboard navigation is unaffected. Scoped to app-scroll so
   te.3's Route A frame is untouched. */
#modal-ticket[data-variant='sheet'][data-sheet-mode='app-scroll']:focus,
#modal-ticket[data-variant='sheet'][data-sheet-mode='app-scroll']:focus-visible {
   outline: none;
}

/* Swipe-down-to-close writes an inline transform; this is the release easing. */
#modal-ticket[data-variant='sheet'][data-sheet-mode='app-scroll'].ts-snapping {
   transition: transform .28s cubic-bezier(.32, .72, 0, 1);
}

#modal-ticket[data-variant='sheet'][data-sheet-mode='app-scroll'] .modal-container {
   position: static !important;
   left: auto !important;
   top: auto !important;
   transform: none !important;
   width: 100% !important;
   max-width: 100% !important;
   height: auto !important;
   min-height: 100dvh;
   max-height: none !important;
   overflow: visible !important;
   border-radius: 16px 16px 0 0 !important;
   background: #fff !important;
   box-shadow: 0 -10px 26px -4px rgba(0, 0, 0, .3);
   pointer-events: auto;
   animation: ts-sheet-in .3s cubic-bezier(.22, .61, .36, 1);
}
@media (prefers-reduced-motion: reduce) {
   #modal-ticket[data-variant='sheet'][data-sheet-mode='app-scroll'] .modal-container {
      animation: none;
   }
}

/* ============================================================================
   PANEL frame — te.3 WIDESCREEN (>= 880px), te.1's slide-in model
   ----------------------------------------------------------------------------
   The dialog fills the LEFT TICKET COLUMN and slides in from the left, over the
   list, exactly like te.1's `.slide-in-element` (te.1 styles-desktop.css:214).
   The map column is untouched — full width, live, and beside the panel rather
   than behind a scrim.

   Structure the JS builds (_mountPanel):
      dialog#modal-ticket        absolute, fills #evList, flex column, clips
        .ts-panel-nav            fixed top bar — "Back to all tickets"
        .modal-container         THE SCROLLER (hgroup sticks to its top)

   Only the frame is scoped here. Every content rule below this block is shared
   with the two sheet modes verbatim — same sections, same order.
   ========================================================================= */

/* The host column must be a positioning context for the absolute panel. te.3's
   wide block sets `.ev-list { position: static }`, which is what this overrides;
   it is stamped by the JS on open so nothing changes when no panel is open. */
.ts-panel-open { position: relative !important; }

#modal-ticket[data-variant='sheet'][data-sheet-mode='panel'].cmodal,
#modal-ticket[data-variant='sheet'][data-sheet-mode='panel'] {
   position: absolute !important;
   inset: 0 !important;
   margin: 0 !important;
   width: 100% !important;
   max-width: 100% !important;
   height: 100% !important;
   max-height: 100% !important;
   display: flex !important;
   flex-direction: column;
   overflow: hidden !important;          /* the container scrolls, not the dialog */
   background: #fff !important;
   border: 0 !important;
   padding: 0 !important;
   /* Above the list (z:2) and its drag zone (z:3); below the peek card and the
      quantity overlay, which are app-level surfaces. */
   z-index: 20 !important;
   box-shadow: 6px 0 24px rgba(16, 24, 40, .14) !important;
   pointer-events: auto;
   --ts-shadow: 0px 6px 10px -8px rgba(0, 0, 0, .3);
}
#modal-ticket[data-variant='sheet'][data-sheet-mode='panel']::backdrop { background: transparent; }

/* Slide in from the left edge of the column — te.1's margin-left animation,
   as a transform so it composites. */
/* Transform ONLY — no opacity fade. Fading the panel in means the content is
   invisible for the whole animation, which reads as "the ticket is still
   loading" even when it is already built. te.1's slide-in animates position and
   its content is legible from the first frame; same here. */
@keyframes ts-panel-in {
   from { transform: translateX(-18px); }
   to   { transform: translateX(0); }
}
#modal-ticket[data-variant='sheet'][data-sheet-mode='panel'] {
   animation: ts-panel-in .13s cubic-bezier(.22, .61, .36, 1);
}
@media (prefers-reduced-motion: reduce) {
   #modal-ticket[data-variant='sheet'][data-sheet-mode='panel'] { animation: none; }
}

/* te.1's `.dialog-navigation` (styles-desktop.css:221) — outside the scroller,
   so it holds while the content moves under it. */
/* te.1's `.dialog-navigation` (styles-desktop.css:221-223): a 23px line in a
   39px bar, 16px text, and the back arrow is the BLUE `arrow-open-blue-large`
   icon — the arrow carries the colour, the label stays dark until hover. Mine
   was 14px grey with a grey chevron, which read as fine print. */
/* (a) THE PANEL IS A CARD TOO. It fills the list column, which is now a floating card
   with a 16px radius — an overlay with square corners inside it reads as a different
   object sitting on top rather than the same card showing different content. Top corners
   only: the panel runs to the column's floor, where the card's own radius does the work.
   `overflow: hidden` is what actually clips the content to the curve. */
#modal-ticket[data-variant='sheet'][data-sheet-mode='panel'] {
   /* MINUS the host card's 1px border: this sits at `inset: 0` INSIDE that border, where
      the card's inner curve is 1px tighter, so matching 16 to 16 leaves the corner poking
      a hairline past it. */
   border-top-left-radius: calc(var(--te3-card-radius, 16px) - 1px);
   border-top-right-radius: calc(var(--te3-card-radius, 16px) - 1px);
}
#modal-ticket[data-variant='sheet'][data-sheet-mode='panel'] .ts-panel-nav {
   flex: 0 0 auto;
   display: flex;
   align-items: center;
   min-height: 39px;
   padding: 8px 15px;
   background: #fff;
   border-bottom: 1px solid #CCC;
}
#modal-ticket[data-variant='sheet'][data-sheet-mode='panel'] .ts-panel-back {
   display: inline-flex;
   align-items: center;
   gap: 0;   /* R19 — chevron sits flush against the label */
   padding: 0;
   border: 0;
   background: none;
   font: inherit;
   font-size: 16px;
   font-weight: 700;
   line-height: 23px;
   color: #444;
   cursor: pointer;
}
#modal-ticket[data-variant='sheet'][data-sheet-mode='panel'] .ts-panel-back i {
   color: #0067CE;              /* te.1's blue arrow */
   /* R19 — size comes from data-size='xl' (32px) on the element, double the 's' (16px)
      it carried. The font-size that used to sit here did nothing: these icons are masks
      with explicit height/width, so only the size attribute moves them. */
}
#modal-ticket[data-variant='sheet'][data-sheet-mode='panel'] .ts-panel-back:hover { color: #0067CE; }

/* Save toggle — right end of the nav bar, opposite the back link.
   `margin-left: auto` rather than `justify-content: space-between` on the nav:
   the nav is shared with the back-link-only case (no saveToggle option passed),
   and space-between would leave a lone back link centred in nothing. */
#modal-ticket[data-variant='sheet'][data-sheet-mode='panel'] .ts-panel-save {
   margin-left: auto;
   display: inline-flex;
   align-items: center;
   gap: 6px;
   padding: 4px 10px;
   border: 1px solid var(--outline-color);
   border-radius: 999px;
   background: #fff;
   font: inherit;
   font-size: 13px;
   font-weight: 700;
   line-height: 21px;
   color: #444;
   cursor: pointer;
}
#modal-ticket[data-variant='sheet'][data-sheet-mode='panel'] .ts-panel-save i { color: #767676; }
#modal-ticket[data-variant='sheet'][data-sheet-mode='panel'] .ts-panel-save:hover {
   border-color: #0067CE;
   color: #0067CE;
}
#modal-ticket[data-variant='sheet'][data-sheet-mode='panel'] .ts-panel-save:hover i { color: #0067CE; }
/* Saved state carries the colour on the ICON, matching the back link's blue-arrow
   pattern one row up — the label stays dark so the two controls read as a pair. */
#modal-ticket[data-variant='sheet'][data-sheet-mode='panel'] .ts-panel-save.is-saved {
   border-color: #0067CE;
   background: rgba(0, 103, 206, .06);
}
#modal-ticket[data-variant='sheet'][data-sheet-mode='panel'] .ts-panel-save.is-saved i { color: #0067CE; }

/* (8b) NO sticky close button on the panel. The x is a SHEET affordance — on a
   phone the sheet covers the screen and needs a dismiss that follows you down.
   The panel has a permanent "Back to all tickets" bar above it and a live map
   beside it, so a second dismiss riding the headline is redundant chrome that
   also eats width from the headline itself. */
#modal-ticket[data-variant='sheet'][data-sheet-mode='panel'] .ts-close { display: none; }
/* With no x to clear, the headline bar no longer needs to stick. Letting it
   scroll away is what frees the top edge for the STUB to dock to (8d). */
/* NO HEADLINE BAR on the panel. The hgroup carries a computed headline AND a
   close x — both SHEET affordances. On a phone the sheet owns the screen and
   needs its own title and a dismiss that follows you down. The panel has a
   permanent "Back to all tickets" bar directly above it and a live map beside
   it, so the strip is a second title and a second dismiss for a surface that
   already has both — and it stands between the nav bar and the hero, which is
   where the photo should start. */
#modal-ticket[data-variant='sheet'][data-sheet-mode='panel'] hgroup { display: none !important; }

/* THE scroller. Squared off (it fills a column, not a rounded sheet) and it
   owns the scroll — the dialog above it is overflow:hidden. */
#modal-ticket[data-variant='sheet'][data-sheet-mode='panel'] .modal-container {
   /* RELATIVE, not static. This element has to be a positioned box so its
      `z-index: 1` counts — that is what puts the scrolling listing ABOVE the
      pinned hero behind it. `static` (which is what undid the base sheet's
      positioning) makes z-index a no-op, and the photo would paint over the
      content instead of under it. `relative` undoes the base positioning just
      as well, because left/top are reset to auto immediately below. */
   position: relative !important;
   z-index: 1;
   left: auto !important;
   top: auto !important;
   transform: none !important;
   flex: 1 1 auto;
   width: 100% !important;
   max-width: 100% !important;
   height: auto !important;
   min-height: 0 !important;             /* a flex child must be allowed to shrink to scroll */
   max-height: none !important;
   border-radius: 0 !important;
   /* NO background here. The hero pins INSIDE this scroller and the content
      after it carries the white (see the hero block below); a background on the
      scroller itself is harmless but redundant, and was previously #fff. */
   overflow-y: auto !important;
   overscroll-behavior: contain;
   box-shadow: none !important;
}
/* The sticky headline bar sticks to the SCROLLER's top, so square its corners —
   there is no rounded sheet edge for them to follow here. */
#modal-ticket[data-variant='sheet'][data-sheet-mode='panel'] hgroup {
   border-radius: 0 !important;
}

/* ---- te.1 chrome (opt-in: `te1Chrome`, stamps .ts-te1) ------------------
   TWO pieces only, both of them things te.1's desktop listing does better than
   the sheet. Which sections exist, and their order, stay te.3's. */

/* (a) te.1's PINNED hero — the listing scrolls OVER the photo
   (styles-desktop.css:458-461 + section-seat-views.php:35).

   ONE height, used by the photo itself. te.1 steps it DOWN on short screens
   (245 -> 220 -> 160/195 at its 900px and 768px height breakpoints) because a
   pinned hero costs vertical room a laptop does not have. Same three tiers. */
/* 245px FLAT — te.1's `.seat-view-spacer{height:245px}` (styles-desktop.css:461).
   This is the OPEN FRAME: it dictates the photo's size, so it is one number.
   te.1 does step down on short viewports (220 at ≤900px, 195 at ≤768px); those
   tiers were here and are removed per instruction. Two lines to restore. */
#modal-ticket[data-variant='sheet'][data-sheet-mode='panel'] { --ts-hero-h: 245px; }

/* STICKY, not absolute, and NOT reparented — it stays exactly where the shared
   modal built it, inside <main>, so every base rule scoped
   `main.page-te section[data-role='hero'] …` still applies to it.

   z-index 0 here + z-index 1 on everything after it is what makes the content
   travel OVER the photo. Without that layering, sticky gives the wrong effect:
   the listing butts up against the photo and shoves it out of the way. */
#modal-ticket[data-variant='sheet'][data-sheet-mode='panel'] .ts-hero-pinned {
   position: sticky;
   top: 0;
   z-index: 0;
   height: var(--ts-hero-h);
   overflow: hidden;
   /* NO vertical margin. The shared sheet rule gives the hero
      `margin-block: 16px !important` so it can breathe mid-content; here it is
      the FIRST thing under the nav bar and any gap reads as a broken edge.
      Panel scope only — the sheet keeps its 16px. */
   margin-block: 0 !important;
}
/* Same reason one level up: `.cmodal main` carries `margin-block-start: 8px`
   and main its own padding-top, both pushing the hero off the nav bar. */
#modal-ticket[data-variant='sheet'][data-sheet-mode='panel'] main {
   margin-block-start: 0 !important;
   padding-top: 0 !important;

   /* FULL-WIDTH, OPAQUE. `#modal-ticket.cmodal main` carries
      `padding-inline: clamp(16px,3vw,24px)`, and R26 made main transparent so
      the sticky hero could show through it. Together those meant the listing
      slid over the photo with a see-through gutter down both sides and above —
      the photo showing in the gaps between sections.

      So main becomes edge-to-edge and WHITE, and each section takes the padding
      back individually (below). main's background sits behind all its children,
      including the sticky hero at z-index 0, so the hero still paints above it
      and the reveal is unaffected. */
   padding-inline: 0 !important;
   background: #fff;

   /* T1, THE ACTUAL CAUSE — main was not sized by its content at all.
      The debug table showed `main` at 1098px and `.modal-container` at 1098px: the
      SAME height. `.cmodal .modal-container` is `display: flex; flex-direction:
      column` (themes/assets/lib/styles/modal.css:88), so main is a column flex item
      with the default `flex-shrink: 1` — when the content ran past the scroller's
      height, main SHRANK to the scroller's height and let its own content overflow.

      A sticky box is constrained by its containing block, which for main's children
      is main's content box. That box stopped at 1098px no matter how much content
      sat below it, which is why every measurement lines up exactly:
         hero releases at 1098 - 245 = 853   (observed ~830-853)
         stub releases at 1098 - 161 = 937   (observed ~913-937)
      and why the ::after spacer below did nothing — you cannot grow a box by adding
      content to it when flex is dictating its height.

      `flex: 0 0 auto` lets main take its content height again. */
   flex: 0 0 auto;
}
/* T1 — SPACER AT THE END OF main, and it has to be a BOX, not padding.
   The trace settled the mechanism: the stub (161px) releases exactly 84px after the
   hero (245px), and 245 - 161 = 84. Both are `sticky; top: 0` siblings in <main>,
   both constrained by the same block, each releasing one of its OWN heights above
   that block's bottom edge. Textbook behaviour — nothing exotic going on.

   So why did `padding-bottom: <stub height>` on main change nothing? Because a sticky
   box is a RELATIVELY positioned box, and the containing block of a relatively
   positioned box is its parent's CONTENT box, not its padding box. Padding lies
   outside the content box, so the constraint rectangle never saw it. That is the
   detail I missed twice.

   A pseudo-element IS content, so it grows the content box and carries the release
   point down with it. Same reasoning behind te.1's `.fixed-ticket-stub-spacer` being
   a real div. The reserve is not dead space, either: while pinned the stub covers the
   top 161px of whatever is beneath it, so without this the last screenful of the
   listing could never be read out from under it.

   THIS ONLY WORKS ALONGSIDE `flex: 0 0 auto` on main above — on its own it was inert,
   because main's height was coming from flex, not from its content. Both are needed:
   the flex fix moves the release to the true end of the content, and this moves it
   past the content so the last section clears the stub. */

   --ts-stub-h is measured in ticket-sheet.js; the fallback covers the first frame. */
#modal-ticket[data-variant='sheet'][data-sheet-mode='panel'] main::after {
   content: "";
   display: block;
   height: calc(var(--ts-stub-h, 161px) + 24px);
}
/* The padding moves onto the sections themselves. */
#modal-ticket[data-variant='sheet'][data-sheet-mode='panel'] main > section {
   padding-inline: clamp(16px, 3vw, 24px);
}
/* …except the ones that are deliberately EDGE-TO-EDGE. These carry a negative
   margin-inline to break OUT of main's padding — with that padding now gone,
   the negative margin would push them past the panel's edges instead. Both the
   margin and the padding are zeroed so they sit flush, which is what they were
   always after. */
#modal-ticket[data-variant='sheet'][data-sheet-mode='panel'] main > section[data-role='hero'],
#modal-ticket[data-variant='sheet'][data-sheet-mode='panel'] main > section.modal-bleed,
#modal-ticket[data-variant='sheet'][data-sheet-mode='panel'] main > .ts-stub {
   margin-inline: 0 !important;
   padding-inline: 0 !important;
}
/* The section AFTER the stub takes the breathing room the stub's margin used to
   provide — as padding, so it is inside that section's white box and no gap can
   show the hero through. */
#modal-ticket[data-variant='sheet'][data-sheet-mode='panel'] .ts-stub + section,
#modal-ticket[data-variant='sheet'][data-sheet-mode='panel'] .ts-stub + * {
   padding-top: 16px;
}
/* T2 — NO SEAT VIEW. The hero keeps its 245px frame and fills it the way te.1 does:
   the performer photo on a concert, else the venue image, with a caption over it
   (section-seat-views.php:61-77). Painted as the hero's own background from a custom
   property rather than as an injected <figure>, because the gallery replaces the
   hero's children asynchronously — see the note in ticket-sheet.js. */
#modal-ticket[data-variant='sheet'][data-sheet-mode='panel'] section[data-role='hero'][data-hero-empty] {
   background-image: var(--ts-hero-fallback, none);
   background-size: cover;
   background-position: center;
   background-color: var(--color-10);
}
/* te.1's `.section-seat-views figure .no-photo` (styles-desktop.css:536): NOT a bottom
   gradient — a full-cover grey wash over the whole frame that DULLS the image behind it,
   with dark text centred on top. The image is context, not the subject. */
#modal-ticket[data-variant='sheet'][data-sheet-mode='panel'] section[data-role='hero'][data-hero-empty]::before {
   content: attr(data-hero-note);
   position: absolute;
   inset: 0;
   z-index: 2;
   display: grid;
   place-items: center;
   padding: 0 40px;
   background: rgba(150, 150, 150, .90);
   color: #444;
   font-size: 16px;
   line-height: 1.4;
   text-align: center;
   /* the note carries a newline between performer and venue (te.1 uses a <br>). */
   white-space: pre-line;
   pointer-events: none;
}
/* nothing to expand or count when there is no photo. */
#modal-ticket[data-variant='sheet'][data-sheet-mode='panel'] section[data-role='hero'][data-hero-empty] .ts-hero-expand {
   display: none !important;
}
/* COMPARE TICKETS takes the gutter back. renderOtherTickets() stamps `modal-bleed`
   onto this section (ticketsManager.js:4451) because on the mobile sheet the compare
   table is a full-bleed horizontal slider — but in the panel it is the last block in
   a narrow column and was the only one running edge-to-edge. Written without the
   `main >` child combinator on purpose: when tabs are on, this section is nested
   inside the details panel div and a child selector would miss it.

   THE `.cmp-inline` CLASS IS LOAD-BEARING. Without it this selector scores (1,3,1) and the
   full-bleed rule above — `… main > section.modal-bleed` — scores (1,3,2), so it won and the
   section stayed edge-to-edge. Both carry !important, and !important ties are broken by
   specificity before source order, so being written later changed nothing. The class takes
   this to (1,4,1). */
#modal-ticket[data-variant='sheet'][data-sheet-mode='panel'] section[data-role='otherTickets'].cmp-inline {
   margin-inline: 0 !important;
   padding-inline: clamp(16px, 3vw, 24px) !important;
}
/* The stub's own contents still need the gutter — only its BOX is full-bleed. */
#modal-ticket[data-variant='sheet'][data-sheet-mode='panel'] .ts-stub-top,
#modal-ticket[data-variant='sheet'][data-sheet-mode='panel'] .ts-stub-bottom {
   padding-inline: clamp(16px, 3vw, 24px);
}
/* THE HERO IS ONE PHOTO. Three pieces of gallery chrome come off:

     [data-role='gallery-overlay']  the seating-chart thumbnail (zero.js:220).
       THIS is the "location map" — not `figure[data-media='map']`, which is why
       hiding that selector did nothing. It is a corner thumbnail of the venue
       chart, and the panel sits beside a live seating map already.

     button[data-action='prev'|'next']  the hover arrows (zero.js:234-235).
       In the panel they do not page the hero — a click anywhere on the hero is
       captured by the photo viewer, so the arrows open the full-screen gallery
       instead of navigating. An arrow that does not do the one thing arrows
       mean is worse than no arrow.

     figure[data-media='map']  kept hidden too; harmless if absent. */
#modal-ticket[data-variant='sheet'][data-sheet-mode='panel'] .ts-hero-pinned [data-role='gallery-overlay'],
#modal-ticket[data-variant='sheet'][data-sheet-mode='panel'] .ts-hero-pinned button[data-action='prev'],
#modal-ticket[data-variant='sheet'][data-sheet-mode='panel'] .ts-hero-pinned button[data-action='next'],
#modal-ticket[data-variant='sheet'][data-sheet-mode='panel'] .ts-hero-pinned figure[data-media='map'] {
   display: none !important;
}
#modal-ticket[data-variant='sheet'][data-sheet-mode='panel'] .ts-hero-pinned figure:not([data-media='map']),
#modal-ticket[data-variant='sheet'][data-sheet-mode='panel'] .ts-hero-pinned img {
   width: 100%;
   height: var(--ts-hero-h) !important;
   max-height: none !important;
   min-height: 0 !important;
   aspect-ratio: auto !important;    /* the fixed height owns the box now */
   object-fit: cover;
   border-radius: 0;
}

/* Everything AFTER the hero rides over it: raised one layer and opaque, so the
   photo is covered as they pass rather than showing through. `~` (not `>`)
   because these are the hero's later siblings inside <main>. */
#modal-ticket[data-variant='sheet'][data-sheet-mode='panel'] .ts-hero-pinned ~ * {
   position: relative;
   z-index: 1;
   background: #fff;
}
/* The listing's leading edge gets a soft lift so it reads as a sheet of content
   riding over the photo rather than a hard cut. */
#modal-ticket[data-variant='sheet'][data-sheet-mode='panel'] .ts-hero-pinned + * {
   box-shadow: 0 -6px 16px -4px rgba(0, 0, 0, .28);
}
/* The STUB is a later sibling too, so the blanket rule above would have reset it
   to position:relative — same specificity, and this block now comes after the
   stub's own rule, so it would have won and silently killed the docking from
   R22. Restated with one extra class so it outranks the `*`. */
#modal-ticket[data-variant='sheet'][data-sheet-mode='panel'] .ts-hero-pinned ~ .ts-stub {
   position: sticky;
   top: 0;
   z-index: 40;
}
/* <main> must NOT paint its own background, or it would cover the sticky hero
   inside it. The children above carry the white instead.

   The other three declarations keep STICKY WORKING. `position: sticky` is
   silently cancelled by ANY ancestor between the element and its scroll
   container that has `overflow` other than visible, a `transform`, a `filter`,
   or `contain: paint` — the element just stops pinning partway down, which is
   exactly the symptom (the stub docks, then gives up once you scroll far
   enough and the checkout button goes with it). <main> is the only ancestor
   between the stub and the scroller, so it is pinned down here explicitly
   rather than left to whatever the base modal sets. */
#modal-ticket[data-variant='sheet'][data-sheet-mode='panel'] main {
   background: transparent;
   overflow: visible;
   transform: none;
   contain: none;
}

/* NO CAPTION in the panel. te.1's desktop listing shows a photo COUNT over the
   hero and nothing else — the caption belongs to the full-screen viewer, where
   there is room to read it. Over a 245px frame it covered the picture it was
   describing. */
#modal-ticket[data-variant='sheet'][data-sheet-mode='panel'] section[data-role='hero'] figcaption {
   display: none !important;
}
#modal-ticket[data-variant='sheet'].ts-te1 .ts-hero-expand { z-index: 3; }
/* PHOTO COUNT sits UNDER the expand button, top right — te.1's
   `.ticket-listing .section-seat-views .photo-count{right:20px;top:38px;bottom:auto;left:auto}`
   (styles-desktop.css:499): tucked below the button, not in the opposite corner.
   The base (sheet) placement stays bottom-left; only the panel moves it. */
/* Rendered from `data-photo-count` on the HERO, not an appended child — the
   gallery replaces the hero's innerHTML when it builds (zero.js:241), which
   wiped the badge. An attribute on the hero survives that. */
#modal-ticket[data-variant='sheet'].ts-te1 section[data-role='hero'][data-photo-count]::after {
   content: attr(data-photo-count) " Photos";
   position: absolute;
   left: 12px;
   bottom: 10px;
   z-index: 4;
   font-size: 13px;
   font-weight: 600;
   color: #fff;
   text-shadow: 1px 1px 2px rgba(0, 0, 0, .75);
   pointer-events: none;
}
/* Panel: tucked UNDER the expand button, top right — te.1's
   `.photo-count{right:20px;top:38px;bottom:auto;left:auto}` (styles-desktop.css:499). */
#modal-ticket[data-variant='sheet'][data-sheet-mode='panel'] section[data-role='hero'][data-photo-count]::after {
   /* clears the 30px expand button that starts at top:10px, with a 6px gap. */
   top: 46px;
   right: 10px;
   left: auto;
   bottom: auto;
   /* te.1 plates this on translucent black rather than relying on a text-shadow
      (styles-default.css:283) — over a bright seat view a shadowed white label
      disappears. Panel-scoped: the mobile sheet keeps its bottom-left shadow. */
   background: rgba(0, 0, 0, .30);
   line-height: 20px;
   padding: 0 5px;
   border-radius: 4px;
   text-shadow: none;
}
/* EXPAND BUTTON matches the section overlay's (.sp-hero-expand, overlays.css:2053)
   — the two overlays sit in the same column and open the same kind of viewer, so a
   dark square in one and a light square in the other read as different controls.
   Panel-scoped; the mobile sheet keeps the dark button that suits a full-bleed photo. */
#modal-ticket[data-variant='sheet'][data-sheet-mode='panel'] .ts-hero-expand {
   top: 10px;
   right: 10px;
   width: 30px;
   height: 30px;
   background: rgba(255, 255, 255, .92);
   color: var(--color-primary);
   box-shadow: 0 1px 3px rgba(0, 0, 0, .25);
}
#modal-ticket[data-variant='sheet'][data-sheet-mode='panel'] .ts-hero-expand:hover { background: #fff; }
/* PAGINATION DOTS off in the panel. Removed in JS too, but the gallery can
   rebuild after that runs — same race as the badge and the location chart. With
   one photo on show and the viewer one click away, dots suggest a swipe the
   hero does not do. */
#modal-ticket[data-variant='sheet'][data-sheet-mode='panel'] .ts-hero-pinned [data-role='gallery-dots-viewport'],
#modal-ticket[data-variant='sheet'][data-sheet-mode='panel'] .ts-hero-pinned [data-role='gallery-dots'] {
   display: none !important;
}

/* (8d) NO FIXED BOTTOM BAR in panel mode; the STUB docks to the top instead.

   The sticky bar is a phone pattern: on a sheet the checkout CTA scrolls away
   and a bar has to follow it back. In a panel the stub is already a compact
   PRICE / SEC / ROW + Buy row, so sticking THAT to the top of the scroller keeps
   the price, the seat and the button on screen together — one control instead of
   a duplicate of it pinned to the far edge of the window, where it also sat over
   the map's pricing disclosure.

   The bar is a FIXED element appended to the dialog, so hiding it also reclaims
   the 124px of bottom padding the modal reserves for it. */
#modal-ticket[data-variant='sheet'][data-sheet-mode='panel'] .te-checkout-bar--sticky { display: none !important; }
#modal-ticket[data-variant='sheet'][data-sheet-mode='panel'] main.page-te { padding-bottom: 24px; }

#modal-ticket[data-variant='sheet'][data-sheet-mode='panel'] .ts-stub {
   /* NO bottom margin. The gap it left showed the hero through, because the
      space BETWEEN two sections belongs to neither and so carries no background.
      The breathing room moves onto the next section as padding-top instead,
      where it sits inside that section's own white box. */
   margin-bottom: 0 !important;
   position: sticky;
   top: 0;
   z-index: 40;
   /* te.1's stub blue, verbatim (`.ticket-listing .section-ticket-stub`,
      styles-default.css:159). PANEL ONLY — the mobile sheet keeps the white stub,
      where it reads as part of the listing rather than as a docked bar. */
   background: rgba(0, 103, 206, .90);
   border-bottom: 0 !important;   /* the #e5e5e5 hairline is invisible on blue */
   /* Only shows once it has actually docked, so it is a shadow that appears when
      the stub lifts off the content rather than a permanent line under it. */
   box-shadow: 0 4px 10px -6px rgba(0, 0, 0, .35);
}
/* VALUES white, LABELS dimmed and unbolded — te.1 splits the two the same way
   (`.ticket-stub div{color:white}` + `.ticket-stub .title{color:#AAA}`,
   styles-desktop.css:235-236). Using a translucent white rather than te.1's flat
   #AAA: over this blue, #AAA lands at roughly 2:1 contrast, which is too thin for
   a 10px uppercase label, while white at 72% keeps the same "stepped back" reading
   at about 3.4:1. */
#modal-ticket[data-variant='sheet'][data-sheet-mode='panel'] .ts-stub-val {
   color: #fff;
}
#modal-ticket[data-variant='sheet'][data-sheet-mode='panel'] .ts-stub-label {
   color: rgba(255, 255, 255, .72);
   font-weight: 400;
}
/* the dashed rule between the two rows was #d5d9e0 — invisible on blue. */
#modal-ticket[data-variant='sheet'][data-sheet-mode='panel'] .ts-stub-bottom {
   border-top-color: rgba(255, 255, 255, .35);
}
/* MLB strip at the FOOT of the stub (te.1's placement). Inside the stub so it pins
   with it; a slightly deeper blue than the stub's own so it reads as a footer band
   rather than more stub. */
#modal-ticket[data-variant='sheet'][data-sheet-mode='panel'] .ts-stub-mlb {
   display: flex;
   align-items: center;
   gap: var(--space-s);
   padding: var(--space-xs) clamp(16px, 3vw, 24px);
   /* te.1's league-banner navy, verbatim (`.league-banner[data-league='mlb']`,
      styles-default.css:560) — te.1 uses the SAME band in the ticket stub and the
      section dialog, so both of ours use it too and they read as one component. */
   background: #041E43;
   color: #fff;
   font-size: var(--font-size-s);
   line-height: 24px;
}
#modal-ticket[data-variant='sheet'][data-sheet-mode='panel'] .ts-stub-mlb img {
   flex: 0 0 auto;
   width: auto;
   height: 16px;
   display: block;
}
/* GREEN checkout button, because a blue face vanishes on a blue stub. Done by
   RE-POINTING the checkout tokens for this subtree, not by overriding the button:
   `.btn-checkout` and the `[data-role='go-to-checkout']` rule below both already
   paint from these three, so they follow automatically — and so does the hover
   `filter: brightness()`, which a background override would have fought. */
#modal-ticket[data-variant='sheet'][data-sheet-mode='panel'] .ts-stub-buy {
   --checkout-btn-bg: var(--checkout-btn-bg-green);
   --checkout-btn-color: var(--checkout-btn-color-green);
   --checkout-btn-shadow: var(--checkout-btn-shadow-green);
}

/* (b) CHECKOUT button — the DESIGN SYSTEM's treatment.
   `--checkout-btn-bg` (defs.css:18) is the site's checkout gradient and is what
   `.btn-checkout` in buttons.css already paints with. This previously hard-coded
   te.1's #60A00A green, which was the one place te.3 overrode a token with a
   literal colour; the token is the correct source and keeps this button matching
   every other checkout button on the site. */
#modal-ticket[data-variant='sheet'].ts-te1 [data-role='go-to-checkout'],
#modal-ticket[data-variant='sheet'].ts-te1 [data-role='sticky-go-to-checkout'] {
   background: var(--checkout-btn-bg) !important;
   border: 0 !important;
   color: #fff !important;
   font-weight: 700 !important;
}

/* ---- (1) rounded top + (2/2a) sticky WHITE header bar, no parallax --- */
#modal-ticket[data-variant='sheet'] hgroup {
   position: sticky !important;
   top: 0 !important;
   left: auto !important;
   right: auto !important;
   opacity: 1 !important;                /* te.3 keeps this at 0 for parallax — force on */
   pointer-events: auto !important;
   z-index: 60 !important;
   display: flex !important;
   align-items: center;
   gap: 8px;
   min-height: 44px;                          /* (6) 8px shorter */
   height: 44px !important;                    /* (2b) beat te.3's 54px parallax hgroup */
   padding: 6px 12px !important;
   background: #fff !important;
   border-bottom: 1px solid #E5E8EF;
   border-radius: 16px 16px 0 0 !important;   /* (1) rounded top */
   box-shadow: none;   /* the sheet body (.modal-container) carries the lift shadow */
}
/* te.3 parallax chrome is redundant under the solid bar. */
#modal-ticket[data-variant='sheet'] .te-hero-back,
#modal-ticket[data-variant='sheet'] .te-hdr-back,
#modal-ticket[data-variant='sheet'] .is-close { display: none !important; }

/* (4) Docked: once the header pins to the top, square it off + drop a bottom shadow. */
#modal-ticket[data-variant='sheet'].ts-docked hgroup {
   border-radius: 0 !important;
   box-shadow: var(--ts-shadow) !important;
}

#modal-ticket[data-variant='sheet'] .is-header {
   flex: 1 1 auto;
   min-width: 0;
   position: static !important;
   padding: 0 !important;
   text-align: left !important;
}
/* (1) Headline: 14px / 600, icon + text, per-rule colour. */
#modal-ticket[data-variant='sheet'] .ts-hdr {
   display: inline-flex;
   align-items: center;
   gap: 6px;
   min-width: 0;
   max-width: 100%;
   font-size: 14px;
   font-weight: 600;
   color: var(--color-primary, #002448);
}
#modal-ticket[data-variant='sheet'] .ts-hdr-text {
   min-width: 0;
   white-space: nowrap;
   overflow: hidden;
   text-overflow: ellipsis;
}
#modal-ticket[data-variant='sheet'] .ts-hl-ic { flex: 0 0 auto; width: 1em; height: 1em; font-size: 1.05em; }
#modal-ticket[data-variant='sheet'] .ts-hl-star-ic { line-height: 1; }

/* Per-rule tones */
#modal-ticket[data-variant='sheet'] .ts-hl-deal   { color: #006300; }                 /* deal — dark green */
#modal-ticket[data-variant='sheet'] .ts-hl-star,
#modal-ticket[data-variant='sheet'] .ts-hl-star .ts-hl-star-ic { color: #E35C02; }     /* seatscore — orange */
#modal-ticket[data-variant='sheet'] .ts-hl-vip    { color: #7c3aed; }                  /* premium zone — purple */
#modal-ticket[data-variant='sheet'] .ts-hl-lowest { color: #006300; }                 /* lowest price — green */
#modal-ticket[data-variant='sheet'] .ts-hl-default { color: var(--color-primary, #002448); opacity: .7; }

/* Marquee — brief painted in the magenta marquee gradient (no badge). */
#modal-ticket[data-variant='sheet'] .ts-hl-marquee .ts-hl-ic { color: #d60eb2; }
#modal-ticket[data-variant='sheet'] .ts-hl-marquee .ts-hdr-text {
   background: linear-gradient(90deg, #840ac9 0%, #d60eb2 30%, #fa0b81 50%, #fa0b81 100%);
   -webkit-background-clip: text;
   background-clip: text;
   color: transparent;
}

#modal-ticket[data-variant='sheet'] .ts-close {
   flex: 0 0 auto;
   width: 36px;
   height: 36px;
   display: grid;
   place-items: center;
   border: 0;
   border-radius: 50%;
   background: transparent;
   color: #333;
   font-size: 26px;
   line-height: 1;
   cursor: pointer;
   pointer-events: auto;
}
#modal-ticket[data-variant='sheet'] .ts-close:active { background: rgba(0, 0, 0, .06); }

/* ---- hero: now nested INSIDE details (after its data lists) --------
   te.3's heroize targets `main.page-te > section[data-role='hero']` (DIRECT
   child), which no longer matches once nested — so re-establish the edge-to-edge
   bleed here. margin-inline !important beats the base `#modal-ticket section
   { margin: 0 !important }`; margin-block (also !important) gives it 16px to breathe. */
#modal-ticket[data-variant='sheet'] section[data-role='hero'] {
   width: auto;
   position: relative;                                            /* anchors the expand btn */
   margin-inline: calc(-1 * clamp(16px, 3vw, 24px)) !important;   /* full bleed */
   margin-block: 16px !important;                                 /* breathe */
   border-radius: 0 !important;
}
/* Expand button — only added (by JS) when there's a real seat view. */
#modal-ticket[data-variant='sheet'] .ts-hero-expand {
   position: absolute;
   top: 8px;
   right: 8px;
   z-index: 3;
   width: 32px;
   height: 32px;
   display: grid;
   place-items: center;
   border: 0;
   border-radius: 8px;
   background: rgba(0, 0, 0, .55);
   color: #fff;
   cursor: pointer;
}

/* MLB banner rebuilt as a detail list (logo icon + note headline);
   no margin override — it inherits the default [data-list] spacing. */
#modal-ticket[data-variant='sheet'] .ts-mlb-lh { align-items: center; }
/* match the 1.05em list-header icon box (has-icon::before); contain the wide logo. */
#modal-ticket[data-variant='sheet'] .ts-mlb-icon { width: 1.05em; height: 1.05em; object-fit: contain; flex: 0 0 auto; }
@media (max-width: 767px) {
   #modal-ticket[data-variant='sheet'] section[data-role='hero'] figure.caption-overlay,
   #modal-ticket[data-variant='sheet'] section[data-role='hero'] .skeleton,
   #modal-ticket[data-variant='sheet'] section[data-role='hero'] figure.caption-overlay img {
      height: 220px !important;                /* (9) main hero photo only — not the location thumbnail */
      aspect-ratio: auto !important;
   }
}

/* ---- (5/5a) checkout row layout inside the stub --------------------- */
/* drop the modal's 320px row cap + auto-centering so the row fills the stub. */
#modal-ticket[data-variant='sheet'] .ts-stub-buy section[data-role='checkout'] {
   max-width: none !important;
   margin: 0 !important;
   display: flex;
   align-items: center;
   gap: 24px;                         /* doubled from 12px */
}
/* # tickets picker / fixed-qty span: hug the content (native <select> pins its
   arrow to the box's right edge, so a fixed 40% width left a big gap), capped at 160px. */
#modal-ticket[data-variant='sheet'] .ts-stub-buy section[data-role='checkout'] select,
#modal-ticket[data-variant='sheet'] .ts-stub-buy section[data-role='checkout'] span {
   flex: 0 0 auto !important;
   width: auto !important;
   min-width: 0 !important;
   max-width: 160px !important;
}
/* checkout button: takes the rest, capped at 320px; fixed 44px height, no v-padding. */
#modal-ticket[data-variant='sheet'] .ts-stub-buy section[data-role='checkout'] [data-role='go-to-checkout'] {
   flex: 1 1 auto !important;
   min-width: 0 !important;
   max-width: 320px !important;
   padding-block: 0 !important;
   height: 44px !important;
   line-height: 44px !important;
}
/* shadow on both buy buttons (main + sticky) — !important beats the base
   go-to-checkout / .te-checkout-btn box-shadows (specificity (1,2,2)). */
#modal-ticket[data-variant='sheet'] [data-role='go-to-checkout'],
#modal-ticket[data-variant='sheet'] [data-role='sticky-go-to-checkout'] {
   box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2) !important;
}
#modal-ticket[data-variant='sheet'] [data-role='go-to-checkout']::after,
#modal-ticket[data-variant='sheet'] [data-role='sticky-go-to-checkout']::after {
   content: '\203A';                              /* (5) forward chevron, both CTAs */
   margin-left: 8px;
   font-size: 1.25em;
   font-weight: 700;
   line-height: 1;
}

/* ---- (3) ticket stub: PRICE/SEC/ROW · dashed perforation · BUY row --- */
/* .ts-stub carries .modal-bleed (ticket-modal.css) for the full-bleed + inline
   padding — don't re-implement it here. */
#modal-ticket[data-variant='sheet'] .ts-stub {
   position: relative;
   z-index: 1;
   background: #fff;
   /* both !important beat the `#modal-ticket section` resets (ticket-modal.css). */
   border-bottom: 1px solid #e5e5e5 !important;   /* separates + gives the stub dominance */
   box-shadow: var(--ts-shadow) !important;       /* (3c) shadow under the stub */
}
/* (3a) top row — 3 EQUAL columns (Price · Sec · Row), 20px breathing room.
   Inline padding comes from .modal-bleed on the section; only pad the block here. */
#modal-ticket[data-variant='sheet'] .ts-stub-top {
   display: flex;
   align-items: center;
   padding-block: 20px;
}
#modal-ticket[data-variant='sheet'] .ts-stub-cell {
   flex: 1 1 0;
   min-width: 0;
   display: flex;
   align-items: center;
   gap: 8px;
}
/* GA / reserved / zone: no Row col → Price 1 · Sec 2 */
#modal-ticket[data-variant='sheet'] .ts-stub-top.is-2col .ts-stub-cell:last-child { flex: 2 1 0; }
/* turned, capitalised, subtle labels (te.1 pattern) */
#modal-ticket[data-variant='sheet'] .ts-stub-label {
   writing-mode: vertical-rl;
   transform: rotate(180deg);
   text-transform: uppercase;
   font-size: 10px;
   font-weight: 700;
   letter-spacing: .08em;
   color: var(--color-60, #8a94a6);
   flex: 0 0 auto;
}
/* te.1 prints this at 12px on its own line under the price (scripts-default.js:2529
   uses `<br>` + `font-size:12px`). Kept small and quiet: it is a disclosure sitting
   under a number, not a second number. */
/* The fee note must NOT change where the price sits. `.ts-stub-cell` is a flex row with
   `align-items: center`, so stacking the note under the value in flow made the Price cell
   taller than Sec and Row — and centring that taller stack lifted "$13" off the line the
   other two values sit on.

   So the note is taken OUT OF FLOW and hung below the value: the wrapper keeps exactly the
   value's own height, all three cells stay the same height, and the three numbers stay on
   one baseline. The stub's 20px block padding is what it hangs into. */
#modal-ticket[data-variant='sheet'] .ts-stub-valwrap {
   position: relative;
   display: block;
   min-width: 0;
}
#modal-ticket[data-variant='sheet'] .ts-stub-fees {
   position: absolute;
   top: 100%;
   left: 0;
   margin-top: 2px;
   font-size: 12px;
   font-weight: 400;
   line-height: 1.2;
   white-space: nowrap;
}
#modal-ticket[data-variant='sheet'][data-sheet-mode='panel'] .ts-stub-fees {
   color: rgba(255, 255, 255, .72);   /* the panel stub is blue (T4) */
}
#modal-ticket[data-variant='sheet'] .ts-stub-val {
   font-size: 28px;
   font-weight: 400;
   line-height: 1;
   color: var(--color-primary, #002448);
   white-space: nowrap;
   overflow: hidden;
   text-overflow: ellipsis;
}
/* (3b) bottom row: dashed perforation above, BUY label, qty + checkout.
   Inline padding comes from .modal-bleed; only pad the block here. */
#modal-ticket[data-variant='sheet'] .ts-stub-bottom {
   display: flex;
   align-items: center;
   gap: 12px;
   padding-block: 20px;
   border-top: 2px dashed #d5d9e0;
}
#modal-ticket[data-variant='sheet'] .ts-stub-buy { flex: 1 1 auto; min-width: 0; }
/* the moved checkout section flows inside the stub — strip its standalone chrome */
#modal-ticket[data-variant='sheet'] .ts-stub-buy section[data-role='checkout'] {
   margin: 0 !important;
   padding: 0 !important;
   box-shadow: none !important;
   background: transparent !important;
}

/* ---- (4b) no "full comparison and rankings" link ------------------- */
#modal-ticket[data-variant='sheet'] [data-role='compare-rankings'] { display: none !important; }

/* ---- (4a) reviews section — drop the SeatScore breakdown; keep stars - */
#modal-ticket[data-variant='sheet'] [data-role='ss-chart'] { display: none !important; }

/* ---- (6) te.1 deal "half-moon" gauge (borrowed; te.1 CSS isn't loaded
        on te.3, so copied here scoped to the sheet) ------------------ */
#modal-ticket[data-variant='sheet'] .deal-meter-container { text-align: center; margin: 6px 0 18px; }
#modal-ticket[data-variant='sheet'] .deal-gauge {
   position: relative; width: 200px; height: 100px; margin: 0 auto 12px; overflow: hidden;
}
#modal-ticket[data-variant='sheet'] .deal-gauge::before {
   content: ''; position: absolute; top: 0; left: 0; width: 200px; height: 200px; border-radius: 50%;
   background: conic-gradient(#1A8828 calc(var(--percentage) * 1% / 2), #e9ecef 0 .5turn, transparent 0);
   transform: rotate(-90deg);
}
#modal-ticket[data-variant='sheet'] .deal-gauge[data-deal='great']::before { background: conic-gradient(#429B46 calc(var(--percentage) * 1% / 2), #e9ecef 0 .5turn, transparent 0); }
#modal-ticket[data-variant='sheet'] .deal-gauge[data-deal='verygood']::before { background: conic-gradient(#5fa95d calc(var(--percentage) * 1% / 2), #e9ecef 0 .5turn, transparent 0); }
#modal-ticket[data-variant='sheet'] .deal-gauge[data-deal='good']::before { background: conic-gradient(#aac6b0 calc(var(--percentage) * 1% / 2), #e9ecef 0 .5turn, transparent 0); }
#modal-ticket[data-variant='sheet'] .deal-gauge[data-deal='fair']::before { background: conic-gradient(#CCC calc(var(--percentage) * 1% / 2), #e9ecef 0 .5turn, transparent 0); }
#modal-ticket[data-variant='sheet'] .deal-gauge[data-deal='belowaverage']::before { background: conic-gradient(#e38a8a calc(var(--percentage) * 1% / 2), #e9ecef 0 .5turn, transparent 0); }
#modal-ticket[data-variant='sheet'] .deal-gauge::after {
   content: ''; position: absolute; top: 20px; left: 20px; width: 160px; height: 160px; background: #fff; border-radius: 50%;
}
#modal-ticket[data-variant='sheet'] .gauge-label { position: absolute; bottom: 0; left: 0; right: 0; z-index: 2; }
/* te.1's gauge sizes were rem @ a 16px root; this theme's root is 10px
   (defs.css font-size:62.5%), so the rems rendered tiny — use px to match. */
#modal-ticket[data-variant='sheet'] .gauge-label .score { display: block; font-size: 40px; font-weight: 800; color: #333; line-height: 1; }
#modal-ticket[data-variant='sheet'] .gauge-label .rating-text { font-size: 13px; text-transform: uppercase; letter-spacing: 1px; color: #666; }
#modal-ticket[data-variant='sheet'] .deal-description { font-size: 15px; color: #4a5568; margin: 0; }
/* te.1 deal-category colours (its ._color-deal-* aren't loaded on te.3). */
#modal-ticket[data-variant='sheet'] ._color-deal-amazing,
#modal-ticket[data-variant='sheet'] ._color-deal-excellent { color: #1A8828; }
#modal-ticket[data-variant='sheet'] ._color-deal-great { color: #429B46; }
#modal-ticket[data-variant='sheet'] ._color-deal-verygood { color: #5fa95d; }
#modal-ticket[data-variant='sheet'] ._color-deal-good { color: #aac6b0; }
#modal-ticket[data-variant='sheet'] ._color-deal-fair { color: #CCC; }
#modal-ticket[data-variant='sheet'] ._color-deal-belowaverage { color: #e38a8a; }
/* border top + bottom around the gauge section (scoped; !important beats the
   `#modal-ticket section { border:0; padding:0 }` reset without touching others). */
#modal-ticket[data-variant='sheet'] section[data-role='deal-gauge'] {
   border-top: 1px solid #e5e5e5 !important;
   border-bottom: 1px solid #e5e5e5 !important;
   padding-block: 16px !important;
}

/* Swipe-to-close drag: JS sets inline transform; this just smooths the snap-back. */
#modal-ticket[data-variant='sheet'].ts-snapping { transition: transform .22s cubic-bezier(.32, .72, 0, 1); }

/* (#5) Sticky bottom bar — match the main-view checkout: qty hugs content (≤160px),
   button takes the rest (≤320px), both 44px tall, wider gap. */
#modal-ticket[data-variant='sheet'] .te-checkout-bar .te-checkout-inner { gap: 24px; }
#modal-ticket[data-variant='sheet'] .te-checkout-bar select[name='quantity-selection'],
#modal-ticket[data-variant='sheet'] .te-checkout-bar .te-checkout-inner > span {
   flex: 0 0 auto !important;
   width: auto !important;
   min-width: 0 !important;
   max-width: 160px !important;
   height: 44px !important;
   padding: 0 14px !important;
}
#modal-ticket[data-variant='sheet'] .te-checkout-bar .te-checkout-btn {
   flex: 1 1 auto !important;
   min-width: 0 !important;
   max-width: 320px !important;
   height: 44px !important;
   padding: 0 !important;
}

/* Sticky checkout bar, app-scroll mode only.
   Lift it by the gap between the layout and visual viewports (published as
   --ts-vv-bottom by TicketSheet._trackVisualViewport) so the browser's own
   chrome can never cover its bottom edge while the body scrolls. Defaults to
   0px, so this is a no-op wherever the two viewports agree — including te.3,
   whose Route A frame never sets the variable. */
#modal-ticket[data-variant='sheet'][data-sheet-mode='app-scroll'] .te-checkout-bar {
   bottom: var(--ts-vv-bottom, 0px);
}
/* A little more breathing room under the controls than te.3's fixed shell
   needs, because this bar sits over a scrolling page. */
#modal-ticket[data-variant='sheet'][data-sheet-mode='app-scroll'] .te-checkout-inner {
   padding-bottom: calc(16px + env(safe-area-inset-bottom, 0px));
}
