/* The public deck viewer's chrome styles, extracted verbatim from
   public/decks/show.html.erb (bd sales-estimates-3101). MOSTLY
   #deck-viewer-scoped (or viewer-only classes), so shipping app-wide via
   propshaft's :app bundle is almost inert outside the viewer — but NOT
   entirely: the `html, body` rules below are global, and land on every page
   of the app. Check the selector before assuming a change here is contained.
   Do NOT normalize the [style*="grid-row: 4"] selectors — the timeline
   lane-split recut keys on inline grid-row values across three surfaces.

   Grounds read --deck-* off #deck-viewer, which the template stamps from
   Decks::Palette; the literal beside each var() is the shipped dark value, so
   an unstamped viewer is unchanged. */
  /* PRESENTATION MODE (.deck-immersive) — what the "Full screen" button does,
     on every device. A CSS takeover rather than the browser's element
     fullscreen: someone screen-sharing a proposal wants the DECK's chrome gone,
     not their whole desktop blacked out over the meeting window. The deck's own
     ground (a light deck ringed in black reads as a broken render, not a
     frame), no footer (counter, brand, Back to editor, the button itself), no
     floating upper-left Editor/Dashboard pill — the same "hide the extra
     pieces" treatment the phone always got. The nav arrows and the ✕ stay: they
     are how the deck is driven and left. dvh so a phone still grows into the
     space Safari's minimizing chrome releases. The :fullscreen rules remain for
     a viewer who uses the BROWSER's own full screen (F11) on top of all this. */
  #deck-viewer:fullscreen { background: var(--deck-ground, #000); }
  #deck-viewer:fullscreen [data-viewer-footer] { display: none; }
  #deck-viewer:-webkit-full-screen { background: var(--deck-ground, #000); }
  #deck-viewer:-webkit-full-screen [data-viewer-footer] { display: none; }
  #deck-viewer.deck-immersive {
    position: fixed;
    inset: 0;
    height: 100dvh;
    z-index: 50;
    background: var(--deck-ground, #000);
  }
  #deck-viewer.deck-immersive [data-viewer-footer] { display: none; }
  #deck-viewer.deck-immersive #deck-viewer-home { display: none; }
  /* An invitation to join someone else's shared control is internal chatter and
     must never appear on the screen being shared. Entering presentation mode
     joins the session anyway, so a presenter never needs it; the transient
     "shared control on" confirmation is allowed through. */
  #deck-viewer.deck-immersive #deck-share-chip[data-state="invite"] { display: none; }
  /* Shared control: the dot goes live only while THIS tab is in the session, so
     "am I driving?" reads at a glance from across a room. */
  .deck-share-dot {
    width: 6px; height: 6px; border-radius: 999px;
    background: currentColor; opacity: .4;
  }
  #deck-share-control[data-state="on"] .deck-share-dot,
  #deck-share-chip[data-state="on"] .deck-share-dot {
    background: #4ade80; opacity: 1;
    animation: deck-share-pulse 2s ease-in-out infinite;
  }
  #deck-share-control[data-state="on"] { color: #fff; }
  @keyframes deck-share-pulse {
    0%, 100% { opacity: 1; }
    50% { opacity: .35; }
  }
  /* iOS pull-to-refresh: with the zoomed overscroll lift below, a downward
     drag chaining to the page top read as pull-to-refresh — pinch-fiddling
     the timeline kept RELOADING the deck (and crash-looped into Safari's
     "a problem repeatedly occurred" when it struck mid-gesture). Nothing on
     this page wants the browser's edge gestures: kill vertical overscroll
     effects at the root. In-range scrolling — including the immersive
     chrome-collapse runway — is unaffected; only the past-the-edge gesture
     dies. */
  html, body {
    overscroll-behavior: none;
  }
  /* Scrollable slide regions own their overscroll — reaching the end of a
     timeline/roadmap must not chain into the page (in immersive that page
     scroll is the chrome-collapse runway and would yank the whole view). */
  .deck-viewer-stage .deck-scroll-x,
  .deck-viewer-stage .deck-scroll-xy {
    overscroll-behavior: contain;
  }
  /* Vertical-only scrollers (timeline week cards, .deck-scroll-y regions —
     overflow-x is hidden on both) contain just the axis they own. Horizontal
     stays at default: a shift+wheel over a white card is a gesture the card
     can never consume, and containing that axis dead-ended the scroll here
     instead of letting it chain to the surrounding .deck-scroll-x — same
     lesson as the editor (see .deck-tl-scroll in sales_deck.css). The parent
     .deck-scroll-x's own containment still keeps it off the page. */
  .deck-viewer-stage .deck-scroll-y,
  .deck-viewer-stage .deck-tl-scroll {
    overscroll-behavior-y: contain;
  }
  /* Baked slides carry the EDITOR's touch-action:none on image slots; in this
     read-only viewer that swallowed every vertical swipe over an image region
     (chrome-collapse only worked in the side gutters). Nothing pans here —
     let all gestures through. */
  .deck-viewer-stage .deck-img-wrap {
    touch-action: auto;
  }
  /* Mobile: a combined (both-lanes) timeline presents as the SAME two
     focused slides the editor's Split produces — the viewer script clones
     the baked slide and these rules re-cut each copy. The row templates come
     from the shared --deck-tl-rows-* custom properties in sales_deck.css (which
     the viewer loads), so the editor's lane preview and this clone stay in
     lockstep from one definition. Desktop keeps the deck exactly as
     configured. Class-driven, not @media-gated: .deck-coarse is stamped on
     #deck-viewer for real coarse pointers AND for the editor's ?phone=1
     preview (a fine-pointer iframe reports pointer: fine no matter how
     narrow, so a media query could never simulate the phone rendering). */
  .deck-coarse .deck-tl-focus-strategy [data-timeline-root] > .grid {
    grid-template-rows: var(--deck-tl-rows-strategy) !important;
  }
  .deck-coarse .deck-tl-focus-strategy [data-timeline-root] > .grid > [style*="grid-row: 5"],
  .deck-coarse .deck-tl-focus-strategy [data-timeline-root] > .grid > [style*="grid-row: 6"] {
    display: none;
  }
  /* In "both" the strategy cards bake a 275px cap; focused they fill. The
     .bg-white arm stays for already-baked decks; [data-deck-card] is the
     semantic name new bakes carry. */
  .deck-coarse .deck-tl-focus-strategy [data-timeline-root] > .grid > .bg-white[style*="grid-row: 4"],
  .deck-coarse .deck-tl-focus-strategy [data-timeline-root] > .grid > [data-deck-card][style*="grid-row: 4"] {
    max-height: calc(100% - 12px) !important;
  }
  .deck-coarse .deck-tl-focus-development [data-timeline-root] > .grid {
    grid-template-rows: var(--deck-tl-rows-development) !important;
  }
  /* Keep the 28px package-label row only when the bake has labels. */
  .deck-coarse .deck-tl-focus-development:has([data-timeline-root] > .grid > [style*="grid-row: 5"]:not(.sticky)) [data-timeline-root] > .grid {
    grid-template-rows: var(--deck-tl-rows-development-labeled) !important;
  }
  .deck-coarse .deck-tl-focus-development [data-timeline-root] > .grid > [style*="grid-row: 4"] {
    display: none;
  }
  /* Nav arrows hug their edges except where the notch/Dynamic Island
     intrudes. iOS's env() insets can't say WHICH edge that is — landscape
     reports the same ~59px on both sides — so the script mirrors the
     orientation angle into deck-notch-left/right and the island-free edge
     drops back to the portrait offset. Without a notch (and in portrait)
     the env() values are 0 and every arrow sits at 0.375rem. */
  #deck-nav-prev { left: max(0.375rem, env(safe-area-inset-left, 0px)); }
  #deck-nav-next { right: max(0.375rem, env(safe-area-inset-right, 0px)); }
  #deck-viewer.deck-notch-left #deck-nav-next { right: 0.375rem; }
  #deck-viewer.deck-notch-right #deck-nav-prev { left: 0.375rem; }
  /* Notch-side breathing room for the PAN surface: a zoomed slide (or
     mobile timeline) scrolled hard against the island's edge would pin its
     content under the island with no way to read it. Pad the island side
     of the zoom content so the pan range runs one island-width further —
     content still renders under the island mid-pan, but the end of the pan
     settles the edge in the safe area. The pad lives on the CHILD, not the
     scroller: browsers disagree on whether a scroll container's own
     end-side padding joins the scrollable region. Gated to slides that can
     actually pan so fitted slides keep their exact centering. */
  #deck-viewer.deck-notch-left .deck-viewer-slide:is(.deck-zoomed, .deck-tl-mobile) .deck-zoom-content {
    padding-left: env(safe-area-inset-left, 0px);
  }
  #deck-viewer.deck-notch-right .deck-viewer-slide:is(.deck-zoomed, .deck-tl-mobile) .deck-zoom-content {
    padding-right: env(safe-area-inset-right, 0px);
  }
  /* Full-bleed timelines overflow the stage PAST the content box, so an
     end-side pad must sit on the grid itself — in canvas px, hence the
     un-scale by --deck-scale (screen px = canvas px × scale). */
  #deck-viewer.deck-notch-right .deck-tl-mobile [data-timeline-root] > .grid {
    padding-right: calc(env(safe-area-inset-right, 0px) / var(--deck-scale, 1)) !important;
  }
  /* Invisible touch halo around the nav arrows (phones only): a thumb aiming
     at a 40px mid-edge button misses by a few px routinely, and a miss lands
     on the slide — where a quick second tap becomes double-tap ZOOM. The halo
     makes near-misses hit the button (which is touch-action: manipulation),
     so rapid stepping stays stepping. Not on desktop: an invisible oversized
     cursor target reads as broken hover. */
  @media (pointer: coarse) {
    #deck-nav-prev::before,
    #deck-nav-next::before {
      content: "";
      position: absolute;
      inset: -18px -14px;
    }
  }
  /* Hold-to-pick slide list: pressing and HOLDING either arrow (~0.4s) opens
     this panel beside it with the CURRENT slide highlighted under the finger
     (releasing in place browses instead of navigating); once the finger
     roams, the row under it highlights, resting in the top/bottom edge bands
     scrolls the list, and RELEASE jumps to the highlighted slide (release
     far off the flanks dismisses). Display-only chrome: targets freeze at touchstart,
     so the pressed ARROW receives the whole gesture and this panel keeps the
     layer's pointer-events:none — it can never steal the touch or a click.
     Living in #deck-vv-ui, it stays screen-glued in every zoom state. */
  .deck-slide-picker {
    position: absolute; top: 50%; transform: translateY(-50%);
    display: none; flex-direction: column;
    width: min(76vw, 19rem);
    max-height: 72%;
    background: color-mix(in srgb, var(--deck-menu, #0a0b0e) 93%, transparent);
    border: 1px solid rgba(255, 255, 255, .22);
    border-radius: 14px;
    box-shadow: 0 14px 44px rgba(0, 0, 0, .55);
    overflow: hidden;
    -webkit-user-select: none; user-select: none; -webkit-touch-callout: none;
  }
  .deck-slide-picker.deck-picker-on { display: flex; }
  /* Browse mode: a hold RELEASED in place (the finger never roamed off the
     arrow) keeps the list open and interactive — native swipes scroll it,
     tapping a row jumps, tapping anywhere else / Esc / a new hold dismisses.
     pointer-events opts back in from the layer's none; the hold flow stays
     display-only chrome exactly as before. */
  .deck-slide-picker.deck-picker-browse { pointer-events: auto; }
  .deck-slide-picker[data-side="prev"] { left: max(3.4rem, calc(env(safe-area-inset-left, 0px) + 3.1rem)); }
  .deck-slide-picker[data-side="next"] { right: max(3.4rem, calc(env(safe-area-inset-right, 0px) + 3.1rem)); }
  /* Mirror of the arrows' island dodge: the island-free edge keeps the
     portrait offset instead of iOS's phantom mirrored inset. */
  #deck-viewer.deck-notch-left .deck-slide-picker[data-side="next"] { right: 3.4rem; }
  #deck-viewer.deck-notch-right .deck-slide-picker[data-side="prev"] { left: 3.4rem; }
  .deck-slide-picker-list {
    position: relative; overflow-y: auto; overscroll-behavior: contain;
    padding: 6px 0; scrollbar-width: none;
  }
  .deck-slide-picker-list::-webkit-scrollbar { display: none; }
  /* Real <button type="button"> rows, not divs: the list is keyboard-reachable
     and screen-reader-announced, and the shared picker (utils/deck_slide_jump)
     builds the same element for both decks. width/text-align undo the UA button
     defaults that a div never had. */
  .deck-slide-picker-item {
    display: flex; align-items: baseline; gap: 10px;
    width: 100%;
    padding: 9px 14px 9px 11px;
    border-left: 3px solid transparent;
    font-size: 13px; line-height: 1.3; text-align: left;
    color: rgba(255, 255, 255, .78);
  }
  .deck-slide-picker.deck-picker-browse .deck-slide-picker-item:active {
    background: rgba(255, 255, 255, .17); color: #fff;
  }
  .deck-slide-picker-item.deck-picker-current {
    border-left-color: rgba(255, 255, 255, .9); color: #fff;
  }
  .deck-slide-picker-item.deck-picker-hot { background: rgba(255, 255, 255, .17); color: #fff; }
  .deck-slide-picker-num {
    flex: none; min-width: 1.6em; text-align: right;
    font-size: 11px; font-variant-numeric: tabular-nums; color: rgba(255, 255, 255, .42);
  }
  .deck-slide-picker-item.deck-picker-hot .deck-slide-picker-num,
  .deck-slide-picker-item.deck-picker-current .deck-slide-picker-num { color: rgba(255, 255, 255, .8); }
  .deck-slide-picker-title { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
  /* A long-press on an arrow is a control gesture, never a text one — no
     iOS selection loupe or callout may spawn from it. */
  #deck-nav-prev, #deck-nav-next {
    -webkit-user-select: none; user-select: none; -webkit-touch-callout: none;
  }
  /* ZOOM ARCHITECTURE: the PAGE never pinch-zooms — each slide zooms INSIDE
     this fixed-screen scroller. touch-action pan-x pan-y suppresses Safari's
     page zoom (and its double-tap zoom) over the content; two-finger pinch
     is handled by the script against the stage, one-finger panning is 100%
     NATIVE scrolling. Fixed chrome (arrows, drawer, ✕) is therefore truly
     static: no visual-viewport tracking, no per-scroll math, no jitter —
     the compositor never moves it because the page never zooms. */
  /* NOTHING on this page may start a page pinch-zoom: touch-action is
     consulted at the TOUCHED element, so any surface left at auto or
     manipulation (which permits pinch) is a hole — a pinch starting on an
     arrow used to zoom the whole page. pan-x pan-y everywhere; the slide
     zoom is the script's. */
  html, body { touch-action: pan-x pan-y; }
  #deck-viewer button, [data-viewer-footer] button { touch-action: pan-x pan-y; }
  .deck-zoom-scroller {
    position: absolute; inset: 0; overflow: auto;
    touch-action: pan-x pan-y;
    overscroll-behavior-x: contain;
    scrollbar-width: none;
  }
  .deck-zoom-scroller::-webkit-scrollbar { display: none; }
  .deck-zoom-content {
    display: flex; align-items: center; justify-content: center;
    min-width: 100%; min-height: 100%;
    width: max-content; height: max-content;
  }
  /* While a slide is zoomed in, its internal scrollers chain into the zoom
     scroller at their ends (same lesson as the old page-zoom model). */
  .deck-zoomed .deck-viewer-stage :is(.deck-scroll-x, .deck-scroll-y, .deck-scroll-xy, .deck-tl-scroll) {
    overscroll-behavior: auto;
  }
  /* Mobile timeline = FULL-BLEED, one scroll surface (phones; classes applied
     by the script below). The internal week scroller is DISABLED — the grid
     flows past the slide bounds and the PAGE scrolls horizontally, so
     panning, zooming, and reaching every week are the same native gesture;
     no inner/outer scroll fight, and nothing left to strand a zoomed viewer.
     The rail drawer, lane chip, and toggle live in the screen-pinned control
     layer (#deck-vv-ui), so they never scroll away with the page and stay
     put while pinch-zoomed. The baked 208px gutter column is pulled
     off-canvas; the drawer replaces it. */
  /* Full-bleed slides anchor flush-LEFT: their ink overflows rightward only,
     so centering the stage BOX shifts it right as zoom drops and manufactures
     a dead black band on the left (stage-black on page-black = "why can I
     pan into nothing?"). */
  .deck-tl-mobile .deck-zoom-content { justify-content: flex-start; }
  .deck-tl-mobile .deck-viewer-stage { overflow: visible !important; }
  .deck-tl-mobile .sales-deck { overflow: visible !important; } /* the canvas is the clipper */
  .deck-tl-mobile [data-timeline-root],
  .deck-tl-mobile .deck-tl-scroll { overflow: visible !important; }
  .deck-tl-mobile [data-timeline-root] > .grid { margin-left: -208px !important; }
  .deck-tl-mobile [data-timeline-root] > .grid > [style*="grid-column: 1;"] { visibility: hidden; }
  /* Lane badge: fixed at the bottom-left (the notch owns the top), shown on
     timeline slides only. Fixed chrome is truly immobile under the container
     -zoom architecture, so this can never jitter or drift. */
  .deck-lane-badge {
    position: fixed; left: max(0.5rem, env(safe-area-inset-left, 0px));
    bottom: max(3.25rem, calc(env(safe-area-inset-bottom, 0px) + 2.75rem));
    z-index: 20; display: none;
    background: rgba(0, 0, 0, .62); color: rgba(255, 255, 255, .85);
    border: 1px solid rgba(255, 255, 255, .2); border-radius: 8px;
    padding: 5px 11px; font-size: 10px; letter-spacing: .18em;
    text-transform: uppercase; pointer-events: none;
  }
  .deck-lane-badge.deck-lane-on { display: block; }
  /* Week pills (phones): one per week column, straddling the phase accent
     line — injected grid items, so they scale/scroll with the canvas. */
  /* The grown labels row (54->78 in the recuts above) buys breathing room:
     milestone boxes sit low in it, clear of the week pills straddling the
     phase line above. "grid-row: 3;" (with semicolon) misses the phase
     backdrops' "grid-row: 3 / -1". */
  .deck-tl-mobile [data-timeline-root] > .grid > [style*="grid-row: 3;"] {
    margin-top: 20px;
  }
  /* Phase titles LIFT off the accent line (Jon: move the labels up, not the
     pill down) — they're self-end cells, so extra bottom padding raises the
     text. "grid-row: 2;" misses the dashed dividers' "grid-row: 2 / -1". */
  .deck-tl-mobile [data-timeline-root] > .grid > [style*="grid-row: 2;"] {
    padding-bottom: 26px !important;
  }
  .deck-tl-week-pill {
    justify-self: start; align-self: start; z-index: 6;
    margin: -12px 0 0 10px; width: max-content;
    pointer-events: none;
    background: var(--deck-menu, #0c0d10); color: var(--deck-muted, rgba(255, 255, 255, .82));
    border: 1px solid rgba(255, 255, 255, .3); border-radius: 999px;
    padding: 3px 11px; font-size: 11px; letter-spacing: .15em;
    line-height: 1.35; text-transform: uppercase;
  }
  /* One-time nudge on the first (auto-zoomed) timeline: the readable zoom
     means most weeks are off-screen right. Dismisses on first scroll / 4s. */
  .deck-pan-hint {
    position: fixed; left: 50%; bottom: 34%; transform: translateX(-50%);
    z-index: 20; display: none; align-items: center; gap: 8px;
    background: rgba(0, 0, 0, .72); color: #e5e7eb;
    border: 1px solid rgba(255, 255, 255, .3); /* silhouette on black (see rotate hint) */
    box-shadow: 0 4px 16px rgba(0, 0, 0, .45);
    border-radius: 999px; padding: 10px 16px;
    font-size: 12px; letter-spacing: .14em; text-transform: uppercase;
    pointer-events: none; white-space: nowrap;
  }
  .deck-pan-hint.deck-lane-on {
    display: flex;
    animation: deck-pan-hint-shake 0.7s ease-in-out;
  }
  @keyframes deck-pan-hint-shake {
    /* Every keyframe carries the pill's translateX(-50%) centering — the
       animation replaces the base transform, not composes with it. */
    0%, 100% { transform: translateX(-50%); }
    20% { transform: translateX(calc(-50% - 7px)); }
    40% { transform: translateX(calc(-50% + 7px)); }
    60% { transform: translateX(calc(-50% - 5px)); }
    80% { transform: translateX(calc(-50% + 3px)); }
  }
  /* One-time landscape nudge on portrait-phone arrival: the deck is a 16:9
     landscape artifact — upright it fits ~a third of the screen tall.
     Non-blocking (portrait stays fully usable via zoom): a pill that
     leaves on rotation or after a few seconds. bottom 46% clears the pan
     hint (34%) when a deep link lands both on a portrait timeline. */
  .deck-rotate-hint {
    position: fixed; left: 50%; bottom: 46%; transform: translateX(-50%);
    z-index: 20; display: flex; align-items: center; gap: 10px;
    background: rgba(0, 0, 0, .72); color: #e5e7eb;
    /* Hint chips float over arbitrary content — black letterbox one moment,
       a white or photo slide the next (and it changes under them). The white
       hairline + shadow give the chip a silhouette on black while the dark
       fill covers light slides — same treatment as the arrows/lane badge, no
       backdrop sniffing. */
    border: 1px solid rgba(255, 255, 255, .3);
    box-shadow: 0 4px 16px rgba(0, 0, 0, .45);
    border-radius: 999px; padding: 10px 16px;
    font-size: 12px; letter-spacing: .14em; text-transform: uppercase;
    pointer-events: none; white-space: nowrap;
  }
  .deck-rotate-hint svg {
    animation: deck-rotate-hint-turn 2.4s ease-in-out infinite;
  }
  @keyframes deck-rotate-hint-turn {
    0%, 18% { transform: rotate(0deg); }
    50%, 82% { transform: rotate(-90deg); }
    100% { transform: rotate(0deg); }
  }
  .deck-swipe-hint-chevron {
    animation: deck-swipe-hint-nudge 1.4s ease-in-out infinite;
  }
  @keyframes deck-swipe-hint-nudge {
    0%, 100% { transform: translateY(2px); }
    50% { transform: translateY(-3px); }
  }
  /* Safari (iPhone) exposes no API to hide its chrome — but it TUCKS the bar
     and tab strip away on its own when the page scrolls. Immersive mode
     therefore leaves scrollable runway behind the fixed viewer: the user's
     first upward swipe collapses Safari to the slim pill, the dvh viewport
     grows, resize fires, and scale() enlarges the slide to match. Not in the
     editor's phone-sim iframe: no browser chrome exists there, and the
     runway would just hang a desktop scrollbar off the fake phone. Gated on
     .deck-coarse for the same reason presentation mode is now the desktop
     path too: a desktop browser's chrome does not tuck away on scroll, so the
     runway there is a scrollbar and 160dvh of nothing. */
  body:has(#deck-viewer.deck-immersive.deck-coarse:not(.deck-phone-sim))::after {
    content: "";
    display: block;
    height: 160dvh;
  }
