      /* Admin stylesheet — extracted from index.html's inline <style> block on
         2026-08-23 (the monolith programme's "cheapest remaining win"); linked
         render-blocking in <head> via <link href="/admin.css">. Inline styles in
         main.js also apply. Jewel design tokens (2026-06-25 re-skin): amber owns
         every primary action; raspberry is the admin product-identity accent;
         neutral warm base. */
      :root {
        --paper:    #FAF8F2;  /* page background */
        --surface:  #FFFFFF;  /* cards / raised */
        --ink:      #21242B;  /* primary text */
        --charcoal: #14161B;  /* dark bars / strong contrast */
        --slate:    #565E6B;  /* secondary text */
        --hairline: #ECE7DF;  /* borders / dividers (fix: was a circular var() self-ref from the reskin sweep) */
        --action:        #F99E16;  /* amber — every primary action */
        --action-ink:    #14161B;  /* text on amber */
        --business-accent: #C41E3A;  /* admin identity = raspberry */
        --accent-tint:     #FCE9EC;  /* raspberry wash for chips/highlights */
        --danger: #C41E3A;
        --ok:     #046A38;  /* emerald */
        /* Full jewel palette (category accents + V5 auth panels) */
        --amber:     #F99E16;
        --raspberry: #C41E3A;
        --sapphire:  #1B3FA0;
        --turquoise: #0099A8;
        --amethyst:  #8B2FC9;
        --emerald:   #046A38;
        --navy:      #211B36;  /* V5 brand panel */
      }
      /* Super-admin god-view gets its own jewel identity = amethyst. */
      .super-admin, [data-surface="super-admin"] { --business-accent: #8B2FC9; --accent-tint: #F2E6FA; }

      /* ===== V5 jewel auth pages (login + signup) — 2026-06-26 ===== */
      /* Fixed full-page overlay (scrolls independently of body overflow:hidden, like the
         trial/claim overlays). Two-panel on desktop; stacks on mobile (B-W19). */
      .auth-v5 { position: fixed; inset: 0; z-index: 6; overflow-y: auto; background: var(--paper); -webkit-overflow-scrolling: touch; }
      .auth-grid { display: grid; grid-template-columns: 1fr 1fr; min-height: 100%; }
      .auth-brand { background: var(--navy); color: #fff; padding: 56px 52px; display: flex; flex-direction: column; justify-content: center; gap: 22px; }
      .auth-mark { display: flex; align-items: center; gap: 14px; }
      .auth-mark .cube { width: 46px; height: 46px; background: #FAF8F2; border-radius: 12px; padding: 7px; box-sizing: border-box; object-fit: contain; }  /* contain preserves the cube's 849:1147 ratio (no squash) inside the square cream tile */
      .auth-mark .wordmark { height: 26px; width: auto; }
      .auth-brand h1 { font-family: 'Sora','DM Sans',system-ui,sans-serif; font-weight: 800; font-size: clamp(30px, 3.6vw, 46px); line-height: 1.08; letter-spacing: -1px; margin: 6px 0 0; }
      .auth-brand .auth-sub { font-size: 17px; line-height: 1.5; color: rgba(255,255,255,0.82); margin: 0; max-width: 32ch; }
      .auth-bar { height: 10px; width: 210px; border-radius: 6px; background: url('/email-assets/jewel-bar.png') left center / cover; }
      .auth-steps { display: flex; flex-direction: column; gap: 13px; margin: 4px 0 0; padding: 0; list-style: none; }
      .auth-steps li { display: flex; align-items: center; gap: 13px; font-size: 15px; font-weight: 500; color: rgba(255,255,255,0.92); }
      .auth-steps .n { flex: 0 0 26px; height: 26px; border-radius: 50%; background: rgba(255,255,255,0.14); display: inline-flex; align-items: center; justify-content: center; font-weight: 700; font-size: 13px; }
      .auth-form { display: flex; flex-direction: column; justify-content: center; padding: 56px 52px; background: var(--surface); }
      .auth-card { width: 100%; max-width: 420px; margin: 0 auto; }
      .auth-card h2 { font-family: 'Sora',system-ui,sans-serif; font-weight: 800; font-size: 24px; color: var(--ink); margin: 0 0 6px; }
      .auth-lead { color: var(--slate); font-size: 15px; line-height: 1.5; margin: 0 0 22px; }
      .auth-input { width: 100%; padding: 13px 14px; border: 1px solid var(--hairline); border-radius: 12px; font-size: 16px; color: var(--ink); background: #fff; box-sizing: border-box; outline: none; transition: border-color .15s, box-shadow .15s; }
      .auth-input:focus { border-color: var(--action); box-shadow: 0 0 0 3px rgba(249,158,22,0.18); }
      .auth-label { font-size: 13px; font-weight: 600; color: var(--ink); margin: 0 0 6px; }
      .auth-cta { width: 100%; padding: 15px; border: 0; border-radius: 13px; background: var(--action); color: var(--navy); font-family: 'Sora',system-ui,sans-serif; font-weight: 800; font-size: 16px; cursor: pointer; box-shadow: 0 6px 18px rgba(249,158,22,0.34); }
      .auth-cta:hover { filter: brightness(1.03); }
      .auth-cta:disabled { opacity: .55; cursor: default; box-shadow: none; }
      .auth-speed { text-align: center; font-size: 13px; color: var(--slate); margin: 11px 0 0; }
      .auth-foot { font-size: 14px; color: var(--slate); margin: 18px 0 0; line-height: 1.6; }
      .auth-foot a { color: var(--business-accent); font-weight: 600; text-decoration: none; }
      .auth-foot a:hover { text-decoration: underline; }
      /* category jewel buttons (selected fills with the category jewel) */
      .auth-cat { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 8px; }
      .auth-cat .signupCategoryBtn { padding: 12px 8px; border-radius: 12px; border: 1px solid var(--hairline); background: #fff; cursor: pointer; font-size: 14px; font-weight: 600; color: var(--ink); transition: background .12s, border-color .12s, color .12s; }
      .signupCategoryBtn.is-selected { color: #fff; border-color: transparent; }
      .signupCategoryBtn.is-selected[data-value="HOSPITALITY"] { background: var(--amber); color: var(--action-ink); }
      .signupCategoryBtn.is-selected[data-value="RETAIL"]      { background: var(--turquoise); }
      .signupCategoryBtn.is-selected[data-value="EXPERIENCES"] { background: var(--amethyst); }
      .signupCategoryBtn.is-selected[data-value="WELLNESS"]    { background: var(--emerald); }
      .signupCategoryBtn.is-selected[data-value="TRAVEL"]      { background: var(--sapphire); }
      .signupCategoryBtn.is-selected[data-value="SERVICES"]    { background: var(--raspberry); }
      @media (max-width: 768px) {
        .auth-grid { grid-template-columns: 1fr; min-height: auto; }
        .auth-brand { padding: 26px 22px; gap: 12px; }
        .auth-brand h1 { font-size: clamp(25px, 6.6vw, 32px); margin-top: 2px; }
        /* batch 3 mobile parity: keep the brand sub-line (cube + wordmark +
           headline + sub-line); only the ①②③ anticipation strip is desktop-only. */
        .auth-steps { display: none; }
        .auth-brand .auth-sub { display: block; font-size: 15px; line-height: 1.45; max-width: none; }
        .auth-bar { width: 140px; height: 8px; }
        .auth-form { padding: 26px 20px 52px; }
        .auth-cat { grid-template-columns: 1fr 1fr; }
      }

      /* ===== Guided-create rail (guided first-run Stages 4–6) — 2026-06-29 ===== */
      /* Persistent docked navy guide beside the REAL venue/campaign/promote forms
         (Option 3 hybrid). Desktop = fixed left rail; the centered create modals
         shift right to clear it. Mobile = collapsible coach-card docked at top. */
      .guided-rail {
        position: fixed; top: 0; left: 0; bottom: 0; width: 340px; z-index: 10001;
        background: var(--navy); color: #fff; box-sizing: border-box;
        padding: 30px 26px; overflow-y: auto; -webkit-overflow-scrolling: touch;
        font-family: 'Sora','DM Sans',system-ui,sans-serif;
        box-shadow: 6px 0 34px rgba(20,22,27,0.30);
        transform: translateX(-100%); transition: transform .34s ease;
      }
      .guided-rail.in { transform: translateX(0); }
      .guided-rail .gr-bar { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 20px; }
      .guided-rail .gr-toggle { display: none; }
      .guided-rail .gr-mark { display: flex; align-items: center; gap: 11px; }
      .guided-rail .gr-cube { width: 34px; height: 34px; background: #FAF8F2; border-radius: 9px; padding: 5px; box-sizing: border-box; object-fit: contain; }
      .guided-rail .gr-wordmark { height: 19px; width: auto; }
      .guided-rail .gr-dots { display: flex; gap: 8px; margin-bottom: 18px; }
      .guided-rail .gr-dot { width: 26px; height: 5px; border-radius: 999px; background: rgba(255,255,255,0.18); transition: background .2s; }
      .guided-rail .gr-dot.is-done { background: rgba(255,255,255,0.5); }
      .guided-rail .gr-dot.is-cur { background: var(--action); }
      .guided-rail .gr-kicker { font-size: 12px; font-weight: 700; letter-spacing: 0.13em; text-transform: uppercase; color: var(--action); margin-bottom: 10px; }
      .guided-rail .gr-title { font-family: 'Sora',system-ui,sans-serif; font-weight: 800; font-size: 22px; line-height: 1.18; margin: 0 0 14px; color: #fff; }
      .guided-rail .gr-lead { font-size: 15px; line-height: 1.55; color: rgba(255,255,255,0.92); margin: 0 0 13px; }
      .guided-rail .gr-note { font-size: 13px; line-height: 1.5; color: rgba(255,255,255,0.66); margin: 0 0 13px; }
      .guided-rail .gr-explain { background: rgba(255,255,255,0.07); border-radius: 12px; padding: 14px 15px; font-size: 13.5px; line-height: 1.55; color: rgba(255,255,255,0.85); margin: 4px 0 14px; }
      .guided-rail .gr-explain-h { font-weight: 700; color: #fff; margin-bottom: 5px; font-size: 13.5px; }
      .guided-rail .gr-steps { margin: 0 0 16px; padding-left: 20px; }
      .guided-rail .gr-steps li { font-size: 14.5px; line-height: 1.5; color: rgba(255,255,255,0.92); margin-bottom: 10px; }
      .guided-rail .gr-cta { width: 100%; padding: 14px; border: 0; border-radius: 13px; background: var(--action); color: var(--navy); font-family: 'Sora',system-ui,sans-serif; font-weight: 800; font-size: 16px; cursor: pointer; box-shadow: 0 6px 18px rgba(249,158,22,0.34); }
      .guided-rail .gr-cta:hover { filter: brightness(1.03); }
      /* FX-22: "Show me" — a ghost secondary that tucks the guide so the spotlit option shows. */
      .guided-rail .gr-showme { width: 100%; padding: 12px; margin: 0 0 10px; border: 1px solid rgba(255,255,255,0.32); border-radius: 13px; background: transparent; color: #fff; font-family: 'Sora',system-ui,sans-serif; font-weight: 700; font-size: 15px; cursor: pointer; min-height: 44px; }
      .guided-rail .gr-showme:hover { background: rgba(255,255,255,0.08); }
      .guided-rail .gr-escape { display: block; margin-top: 18px; font-size: 13px; color: rgba(255,255,255,0.6); text-decoration: underline; text-align: center; }
      .guided-rail .gr-escape:hover { color: rgba(255,255,255,0.85); }
      .guided-rail .gr-inline-link { font-size: 14px; font-weight: 700; color: var(--action); text-decoration: none; }
      .guided-rail .gr-inline-link:hover { text-decoration: underline; }
      /* FX-6: the 'I'll stay & reappear' hint is a mobile-only aid — the desktop rail
         is a permanent fixture, so it's hidden there and shown in the mobile @media block. */
      .guided-rail .gr-reappear { display: none; }

      /* Cluster B — guided campaign two-beat sectioning (onboarding only; classes added/removed by JS). */
      #createCampaignModal .cg-beat-head { display: flex; align-items: center; gap: 10px; margin: 2px 0; }
      #createCampaignModal .cg-beat-n { flex: 0 0 24px; height: 24px; border-radius: 50%; background: var(--business-accent, #C41E3A); color: #fff; font-weight: 800; font-size: 13px; display: inline-flex; align-items: center; justify-content: center; }
      #createCampaignModal .cg-beat-label { font-family: 'Sora',system-ui,sans-serif; font-weight: 800; font-size: 16px; color: var(--ink, #21242B); }
      #createCampaignModal .cg-break { height: 1px; background: var(--hairline, #ECE7DF); margin: 6px 0 16px; }
      #createCampaignModal .cg-continue { width: 100%; padding: 14px; border: 0; border-radius: 13px; background: var(--action, #F99E16); color: var(--action-ink, #14161B); font-family: 'Sora',system-ui,sans-serif; font-weight: 800; font-size: 16px; cursor: pointer; box-shadow: 0 6px 18px rgba(249,158,22,0.30); }
      #createCampaignModal .cg-continue:hover { filter: brightness(1.03); }
      /* FQ-21 — Beat-1 Continue is gated until title + total uses are filled. Disabled = dimmed,
         no pulse, not clickable; the empty required FIELDS keep glowing to show what's missing. */
      #createCampaignModal .cg-continue:disabled { opacity: 0.45; cursor: not-allowed; box-shadow: none; animation: none; filter: none; }
      /* FQ-19 — guided glows must clearly draw the eye: stronger amber, a bright tight ring
         + a wide soft halo, faster pulse. Its own keyframe (cgGlowStrong) so FQ-18's parked
         .promote-spotlight, which reuses cgGlow, is unaffected. Guided mode only — the class
         is only ever applied by guidedCampaign.js. */
      #createCampaignModal .cg-glow { animation: cgGlowStrong 1.05s ease-in-out infinite; border-radius: 12px; }
      @keyframes cgGlowStrong {
        0%,100% { box-shadow: 0 0 0 0 rgba(249,158,22,0.35), 0 0 6px 0 rgba(249,158,22,0.25); }
        50%     { box-shadow: 0 0 0 5px rgba(249,158,22,0.85), 0 0 26px 8px rgba(249,158,22,0.60); }
      }
      /* keep the original softer pulse available for the parked promote spotlight */
      @keyframes cgGlow { 0%,100% { box-shadow: 0 0 0 0 rgba(249,158,22,0); } 50% { box-shadow: 0 0 0 4px rgba(249,158,22,0.30); } }
      /* FQ-15 (guided only): make the FIELDS the sole scroll area with a genuinely pinned
         footer, so all Beat-1 fields (incl. total uses) stay reachable AND the Continue
         button never occludes them. The panel base is overflow:auto — we must override it to
         hidden here, otherwise the whole panel also scrolls and the "pinned" footer isn't
         pinned (that double-scroll is the batch-#2 regression that hid total uses). */
      /* Mobile first-run v2 (2026-07-05): lock the page behind the full-screen build flow / tour. */
      body.mbfActive, body.matActive { overflow: hidden; }
      body.guidedCreateActive #createCampaignModal .modalPanel { display: flex; flex-direction: column; max-height: 92vh; overflow: hidden; }
      body.guidedCreateActive #createCampaignModal .createFormGrid { flex: 1 1 auto; overflow-y: auto; min-height: 0; padding-bottom: 16px; }
      /* footer stack (result line · Cancel/Create row · Continue) must not shrink */
      body.guidedCreateActive #createCampaignModal .createFormGrid ~ * { flex: 0 0 auto; }
      body.guidedCreateActive #createCampaignModal #cgContinueRow { margin-top: 12px; }
      /* FQ-14 — one soft image nudge (guided only). */
      #createCampaignModal .cg-nudge { border: 1px solid #ecdca8; border-radius: 12px; padding: 14px 16px; background: #FFF9EE; }
      #createCampaignModal .cg-nudge-t { margin: 0 0 12px; font-size: 14px; line-height: 1.5; color: var(--ink, #21242B); }
      #createCampaignModal .cg-nudge-row { display: flex; gap: 10px; align-items: stretch; }
      #createCampaignModal .cg-nudge-add { flex: 0 0 auto; padding: 12px 18px; border: 1px solid var(--business-accent, #C41E3A); border-radius: 12px; background: #fff; font-weight: 700; cursor: pointer; }
      #createCampaignModal .cg-nudge .cg-continue { flex: 1 1 auto; }
      /* P4 — the guided spotlight must be UNMISTAKABLE: reuse FQ-19's strong pulse. General (not
         modal-scoped) so it also glows the real campaign-card Promote button on the landing (P2).
         Only ever applied by the guided rail, so it can't bleed into normal admin use. */
      .promote-spotlight { animation: cgGlowStrong 1.05s ease-in-out infinite; border-color: var(--business-accent, #C41E3A) !important; border-radius: 12px; }
      @keyframes recapGlow {
        0%,100% { box-shadow: 0 0 0 0 rgba(249,158,22,0.45), 0 0 8px 0 rgba(249,158,22,0.30); }
        50%     { box-shadow: 0 0 0 4px rgba(249,158,22,0.85), 0 0 22px 6px rgba(249,158,22,0.55); }
      }
      /* P8 — the recap shows REAL admin screenshots right of the LHS rail, with an amber
         highlight box over the relevant control. Purely visual: pointer-events off, dims behind. */
      .rc-stage { position: fixed; inset: 0 0 0 340px; z-index: 10000; display: flex; align-items: center; justify-content: center; padding: 40px; pointer-events: none; background: rgba(20,22,27,0.28); backdrop-filter: blur(2px); opacity: 0; transition: opacity .3s ease; }
      .rc-stage.in { opacity: 1; }
      .rc-shot { position: relative; width: min(640px, 96%); border-radius: 14px; overflow: hidden; background: #fff; border: 1px solid var(--hairline, #ECE7DF); box-shadow: 0 26px 64px rgba(20,22,27,0.5); }
      .rc-shot-img { width: 100%; display: block; }
      .rc-hi { position: absolute; border-radius: 10px; background: rgba(249,158,22,0.14); box-shadow: 0 0 0 3px rgba(249,158,22,0.95), 0 0 26px 7px rgba(249,158,22,0.6); animation: recapGlow 1.2s ease-in-out infinite; }

      /* FQ-18 v2 — the rail's "N of 5" sub-progress for the option walk. */
      .guided-rail .gr-subprogress { font: 700 12px/1 'Sora',system-ui,sans-serif; letter-spacing: .4px; color: rgba(255,255,255,0.72); margin-bottom: 10px; }
      /* P5 — the rail footer holds Back (promote walk) + the escape link, side by side. */
      .guided-rail .gr-foot { display: flex; align-items: center; justify-content: space-between; gap: 14px; margin-top: 18px; }
      .guided-rail .gr-foot .gr-escape { margin-top: 0; text-align: left; }
      .guided-rail .gr-back { font-size: 13px; font-weight: 700; color: #fff; text-decoration: none; opacity: 0.9; white-space: nowrap; }
      .guided-rail .gr-back:hover { opacity: 1; }

      /* P2b — landing SCENE: the real campaign card cloned into a centred overlay, clear of the
         340px LHS rail, dimming the rest. Real card markup (.campaignCard) + its real Promote
         button (glowing, wired to start the walk). */
      .pc-stage { position: fixed; inset: 0 0 0 340px; z-index: 10000; display: flex; align-items: center; justify-content: center; padding: 40px; background: rgba(20,22,27,0.34); backdrop-filter: blur(3px); opacity: 0; transition: opacity .3s ease; }
      .pc-stage.in { opacity: 1; }
      .pc-scene { width: min(420px, 94%); max-height: 88dvh; overflow: auto; }
      .pc-scene .campaignCard { box-shadow: 0 26px 64px rgba(20,22,27,0.5); margin: 0; }

      /* FQ-18 v2 — WhatsApp message mockup (Beat 2), built from the real campaign's link/hero/title. */
      /* P6 — a realistic phone + WhatsApp message showing the REAL og-card link preview. */
      .pw-phone { width: 208px; margin: 6px auto 14px; border-radius: 26px; background: #111; padding: 8px; box-shadow: 0 16px 36px rgba(20,22,27,0.42); box-sizing: border-box; }
      .pw-phone-screen { background: #E5DDD5; border-radius: 20px; overflow: hidden; display: flex; flex-direction: column; min-height: 296px; }
      .pw-wa-head { display: flex; align-items: center; gap: 8px; background: #075E54; color: #fff; padding: 10px 11px; }
      .pw-wa-back { font-size: 18px; line-height: 1; }
      .pw-wa-avatar { width: 22px; height: 22px; border-radius: 50%; background: #cfd8dc; flex: 0 0 auto; }
      .pw-wa-name { font: 700 13px/1 system-ui,sans-serif; }
      .pw-wa-thread { padding: 14px 10px; flex: 1 1 auto; }
      .pw-wa-bubble { background: #DCF8C6; border-radius: 10px; border-top-right-radius: 3px; padding: 5px 6px; max-width: 176px; margin-left: auto; box-shadow: 0 1px 1px rgba(0,0,0,0.12); }
      /* P6b — replicate WhatsApp's link-preview card: og image + og:title (bold) + og:description (grey) + domain. */
      .pw-wa-og { background: rgba(255,255,255,0.72); border-radius: 7px; overflow: hidden; margin-bottom: 4px; }
      .pw-wa-og-img { width: 100%; display: block; }
      .pw-wa-og-body { padding: 5px 7px 6px; }
      .pw-wa-og-title { font: 700 11px/1.25 system-ui,sans-serif; color: #111; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
      .pw-wa-og-desc { font: 400 10px/1.3 system-ui,sans-serif; color: #667781; margin-top: 1px; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
      .pw-wa-og-link { font: 400 10px/1.3 system-ui,sans-serif; color: #8a9aa5; margin-top: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
      .pw-wa-msg { font: 400 12px/1.35 system-ui,sans-serif; color: #111; padding: 0 3px; }
      .pw-wa-time { font: 400 9px/1 system-ui,sans-serif; color: #667781; text-align: right; margin-top: 3px; padding: 0 3px; }
      .pw-wa-ticks { color: #34B7F1; }

      /* FQ-18 v2 — Instagram/Stories frame mockup (Beat 4) with a tap-to-claim link sticker. */
      .pw-story { display: flex; justify-content: center; margin: 4px 0 14px; }
      .pw-story-frame { position: relative; width: 132px; aspect-ratio: 9 / 16; border-radius: 16px; overflow: hidden; background: #14161B; box-shadow: 0 10px 26px rgba(20,22,27,0.38); }
      .pw-story-img { width: 100%; height: 100%; object-fit: cover; display: block; }
      .pw-story-sticker { position: absolute; left: 50%; bottom: 18%; transform: translateX(-50%) rotate(-4deg); background: #fff; color: #14161B; font: 800 11px/1 'Sora',system-ui,sans-serif; padding: 7px 12px; border-radius: 999px; box-shadow: 0 4px 12px rgba(0,0,0,0.28); white-space: nowrap; }

      /* Shift the centered create modals right to clear the desktop rail. */
      body.guidedCreateActive #createVenueModal,
      body.guidedCreateActive .modalOverlay.open { padding-left: 340px; }
      /* The congrats moment (.ob-backdrop) sets inline padding → needs !important. */
      body.guidedCreateActive .ob-backdrop { padding-left: 340px !important; }

      @media (max-width: 768px) {
        .guided-rail {
          top: 0; left: 0; right: 0; bottom: auto; width: auto; max-height: 82dvh;
          padding: calc(env(safe-area-inset-top, 0px) + 12px) 16px calc(env(safe-area-inset-bottom, 0px) + 12px);
          transform: translateY(-100%);
          box-shadow: 0 8px 28px rgba(20,22,27,0.34);
        }
        .guided-rail.in { transform: translateY(0); }
        .guided-rail .gr-toggle { display: block; flex: 0 0 auto; background: rgba(255,255,255,0.14); color: #fff; border: 0; border-radius: 999px; font-size: 12px; font-weight: 700; padding: 7px 13px; cursor: pointer; }
        .guided-rail .gr-bar { margin-bottom: 0; }
        /* FX-20 (2026-07-02): the guide's content is NOT its own scroller on mobile — the
           rail itself scrolls (.guided-rail { max-height:82dvh; overflow-y:auto }). Nested
           scroll containers (rail + gr-scroll) broke touch-scroll on real iOS; one scroller
           fixes it. After FX-24/25 (mockups gone) no beat should need scroll anyway. */
        .guided-rail .gr-scroll { overflow: visible; max-height: none; margin-top: 14px; }
        .guided-rail .gr-scroll.is-collapsed { display: none; }
        .guided-rail .gr-title { font-size: 19px; }
        /* FX-6: show the mobile 'I'll stay & reappear' hint. */
        .guided-rail .gr-reappear { display: block; font-size: 12px; line-height: 1.45; color: rgba(255,255,255,0.62); margin: 0 0 12px; }
        /* FX-33: fixed lead box → every option beat is the same size (buttons never move).
           Bumped 84→96 so the two longer leads (which wrapped an extra line = 483 vs 474px)
           fit inside the box and all five beats match exactly. */
        .guided-rail .gr-beat-lead { min-height: 96px; }
        .guided-rail .gr-beat-lead .gr-lead { margin-bottom: 0; }
        /* FX-15 (mobile-first rebuild): 'I'll explore on my own' ENDS the whole guided flow,
           so on a phone it must not sit right beside the primary CTA / Back (James may have
           cancelled by mistake). Stack the footer — Back gets a 44px tap row of its own; the
           escape drops to its own quiet, separated line. Desktop keeps the side-by-side layout. */
        .guided-rail .gr-foot { flex-direction: column; align-items: stretch; gap: 0; margin-top: 16px; }
        .guided-rail .gr-foot .gr-back { align-self: flex-start; padding: 11px 2px; }
        .guided-rail .gr-foot .gr-escape { margin-top: 20px; font-size: 12px; opacity: 0.6; text-align: center; padding: 8px 0; }
        /* P-1 (2026-07-02): cap the walk mockups (WhatsApp beat 2 / Stories beat 4) so
           copy + mockup + 'Got it →' fit without scrolling — the primary action must be
           visible on beat-entry. Phone sizes to content (min-height:0); story capped. */
        .guided-rail .pw-phone { width: 168px; margin: 4px auto 10px; }
        .guided-rail .pw-phone-screen { min-height: 0; }
        .guided-rail .pw-story { margin: 2px 0 10px; }
        .guided-rail .pw-story-frame { width: auto; height: 30dvh; max-height: 200px; }
        /* coach-card docked at top → push the promote modal (and any other overlay)
           below the collapsed bar. Create/venue get their own scroll model just below. */
        body.guidedCreateActive .modalOverlay.open { padding-left: 0; padding-top: 64px; }

        /* FX-12/13/14 (mobile-first rebuild, 2026-07-02): the create/venue modal scrolls
           BENEATH the coach-card as ONE container (the overlay), with the panel at its
           NATURAL height — so the Continue/Create footer is in normal flow and always
           reachable, never pushed off-screen by a panel max-height (that was FX-12, the
           unreachable total-uses field). padding-top clears the coach-card (measured
           --coach-h when expanded, 64px collapsed — FX-7); safe-area bottom padding keeps
           the CTA above Safari's collapsing bottom bar (FX-13). Horizontal overflow clipped
           on the overlay. Desktop keeps FQ-15 (this block is media-scoped). */
        body.guidedCreateActive #createVenueModal,
        body.guidedCreateActive #createCampaignModal.modalOverlay.open {
          align-items: flex-start !important;
          overflow-y: auto !important;
          overflow-x: hidden !important;
          -webkit-overflow-scrolling: touch;
          /* Device-pass fix (2026-07-02): the desktop rule #createVenueModal { padding-left:340px }
             (clears the side-rail) wins by ID specificity over the mobile .modalOverlay.open reset
             — and #createVenueModal isn't a .modalOverlay, so that reset never matched it. Result:
             the venue modal rendered ~62px wide, squished right. Reset it here with !important. */
          padding-left: 0 !important;
          padding-right: 0 !important;
          padding-top: 64px !important;
          padding-bottom: calc(env(safe-area-inset-bottom, 0px) + 16px) !important;
        }
        body.guidedCoachExpanded.guidedCreateActive #createVenueModal,
        body.guidedCoachExpanded.guidedCreateActive #createCampaignModal.modalOverlay.open {
          padding-top: calc(var(--coach-h, 58dvh) + 12px) !important;
        }
        body.guidedCreateActive #createCampaignModal .modalPanel {
          display: block !important;
          max-height: none !important;
          overflow: visible !important;
          width: 100% !important;
        }
        body.guidedCreateActive #createCampaignModal .createFormGrid { overflow: visible !important; max-height: none !important; }
        body.guidedCreateActive #createCampaignModal .createFormGrid * { max-width: 100%; box-sizing: border-box; }
        body.guidedCreateActive .ob-backdrop { padding-left: 20px !important; padding-top: 64px !important; }
        /* FQ-23/18v2: on mobile the rail is a top coach-card → the mini-shell / landing card sit below it. */
        /* FX-30: the close-out scene (lifted campaign card) sits BELOW the expanded guide and
           is capped so it's fully visible — not rendered too large behind the card. */
        .pc-stage { inset: 0; align-items: flex-start; padding: calc(var(--coach-h, 42dvh) + 14px) 16px calc(env(safe-area-inset-bottom, 0px) + 16px); }
        /* FX-39: JS (fitLandingScene) measures the space below the guide and SCALES the card to
           fit — so no max-height cap / scroll here (that produced the cut-off card). */
        .pc-scene { width: min(340px, 92%); max-height: none; overflow: visible; }

        /* ===== FX-36 (2026-07-02): recap half-and-half, OPAQUE (supersedes v2 one-card) =====
           Guide card = TOP half (capped below); screenshot crop = BOTTOM half on an OPAQUE
           backdrop so the admin page behind is NOT visible. FX-27b: the crop box is sized to
           the viewport (no overflow-clip), so the centred amber ring lands on the region. */
        body.guidedRecap .guided-rail { max-height: 50dvh; }
        .rc-stage--mobile {
          inset: 0; z-index: 10000;
          background: #14161B; backdrop-filter: none;
          display: flex; align-items: flex-end; justify-content: center;
          padding: 0 16px calc(env(safe-area-inset-bottom, 0px) + 18px);
        }
        /* FX-44 (2026-07-04): the frame SIZE is CSS-driven (aspect-ratio floor) so it can never
           collapse to a wrong-sized sliver when the JS measurement is off on a smaller viewport
           (FX-41 set a fixed measured height, which failed on James's device). JS now sets only an
           inline max-height CAP (URL-bar-aware). overflow:hidden + a dvh max-height are the HARD
           containment floor, so the screenshot is always clamped inside the frame regardless. */
        .rc-stage--mobile .rc-shot--mobile {
          width: calc(100vw - 32px); max-width: 460px;
          aspect-ratio: 4 / 3; height: auto; max-height: 52dvh;
          overflow: hidden; margin: 0;
        }
        /* R-1: readable ZOOM-CROP (background-size ~2.6x; JS sets background-position to the
           region centre; a centred amber ring marks it). Absolutely filled so the crop is exactly
           the frame box no matter how the frame's height resolves (FX-44 containment). */
        .rc-shot--mobile .rc-crop {
          position: absolute; inset: 0;
          width: 100%; height: 100%;
          background-repeat: no-repeat;
          background-size: 260% auto;
        }
        .rc-hi-center {
          position: absolute; left: 19%; top: 24%; width: 62%; height: 50%;
          border-radius: 12px; background: rgba(249,158,22,0.12);
          box-shadow: 0 0 0 3px rgba(249,158,22,0.95), 0 0 26px 7px rgba(249,158,22,0.6);
          animation: recapGlow 1.2s ease-in-out infinite; pointer-events: none;
        }
      }

      /* ===== MOBILE-FIRST GUIDED REBUILD (2026-07-02) — jewel modal shell =====
         One coherent mobile model for EVERY jewel onboarding modal (firstRun /
         congrats / redeem explainer / dashDownload / promoteGuide). 100dvh-based
         (never 100vh) so Safari's collapsing bottom bar can't bury the CTA (FX-13);
         safe-area inset padding on the bottom; sized to content, no forced internal
         scroll; 44px tap targets. Overrides framework.js inline styles → !important.
         Desktop keeps the framework defaults (this block is media-scoped). */
      @media (max-width: 768px) {
        .ob-backdrop { padding: 12px !important; }
        .ob-card {
          max-width: none !important;
          width: 100% !important;
          max-height: calc(100dvh - 24px) !important;
          border-radius: 16px !important;
        }
        .ob-body { padding: 22px 18px calc(20px + env(safe-area-inset-bottom, 0px)) !important; }
        .ob-body button { min-height: 44px; }
      }

      body { font-family: -apple-system, system-ui, Arial; margin: 24px; line-height: 1.35; }
      .card { border: 1px solid var(--business-accent, #ddd); border-radius: 12px; padding: 16px; margin: 12px 0; }
      .row { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }
      input, select { border: 1px solid var(--business-accent, #ddd); padding: 10px; font-size: 16px; max-width: 100%; }

      /* Unified picker style — Campaigns header (filter + sort) and
         Dashboard header (campaign + timeframe) all share the same
         visual treatment. */
      #campaignFilterSelect,
      #campaignSortSelect,
      .dashboardHeaderSelect {
        height: 36px;
        font-size: 15px;
        font-weight: 400;
        font-family: inherit;
        line-height: 1.2;
        color: var(--ink);
        border-radius: 12px;
        padding: 0 12px;
        border: 1px solid var(--business-accent, #ddd);
        background: rgba(255,255,255,0.85);
        appearance: none;
        -webkit-appearance: none;
        -moz-appearance: none;
        box-sizing: border-box;
      }

      #btnOpenCreateCampaign {
        height: 36px;
        padding: 0 14px;
        font-size: 15px;
        line-height: 36px;
      }


      input[type="checkbox"] { width: auto !important; max-width: none !important; padding: 0 !important; margin: 0 !important; flex: 0 0 auto; }
      button {
        border: 1px solid var(--business-accent, #ddd);
        background: rgba(255,255,255,0.85);
        padding: 10px 14px;
        font-size: 16px;
        cursor: pointer;
        color: inherit;
      }

      button,
      .campaignActions button,
      #topBar button,
      #mainPane button,
      #editCampaignModal button,
      #editVenueModal button,
      #createCampaignModal button,
      #createVenueModal button {
        border: 1px solid var(--business-accent, #ddd) !important;
      }

      code { background: #f6f6f6; padding: 2px 6px; border-radius: 6px; }
      .pill { display: inline-block; border: 1px solid var(--business-accent, #ddd); border-radius: 999px; padding: 4px 10px; font-size: 12px; }
      .muted { color: #666; }
      pre { background: #111; color: #eee; padding: 12px; border-radius: 10px; overflow: auto; }
      .ok { color: var(--ok); font-weight: 600; }
      .bad { color: var(--danger); font-weight: 600; }
      hr { border: none; border-top: 1px solid #eee; margin: 18px 0; }
      .hide { display: none; }
      .internal-only { display: none !important; }
      body.super-mode .internal-only { display: revert !important; }

      body {
        background: var(--paper);
        color: var(--ink);
        height: 100vh;
        overflow: hidden;
      }

      #appShell {
        display: flex;
        flex-direction: column;
        gap: 0;
        height: calc(100vh - 48px);
      }

      #adminRoot > :not(#appShell):not(#createCampaignModal):not(#campaignAssetModal):not(#createVenueModal):not(#loginSection) {
        display: none !important;
      }

      #topBar {
        position: relative;
        display: flex;
        align-items: center;
        justify-content: space-between;
        gap: 16px;
        padding: 18px 22px;
        background: rgba(255,255,255,0.95);
        backdrop-filter: blur(10px);
        border: 1px solid var(--business-accent, var(--hairline));
        border-radius: 18px;
        margin-bottom: 18px;
        position: fixed;
        top: 12px;
        left: 24px;
        right: 24px;
        z-index: 400;
      }

      #topBarLeft {
        display: flex;
        flex-direction: column;
        gap: 2px;
      }

      #brandTitle {
        font-size: 22px;
        font-weight: 800;
        letter-spacing: -0.02em;
      }

      #brandSubtitle {
        font-size: 13px;
        color: var(--slate);
      }

      #currentBusinessCategory {
        color: var(--business-accent, var(--slate)) !important;
      }

      #topBarRight {
        display: flex;
        align-items: center;
        gap: 12px;
        flex-wrap: wrap;
      }

      #shellBody {
        display: grid;
        grid-template-columns: 220px minmax(0, 1fr);
        gap: 28px;
        align-items: start;
        margin-top: 112px;
        height: calc(100vh - 166px);
        overflow: hidden;
      }

      #sideNav {
        background: rgba(255,255,255,0.88);
        border: 1px solid var(--business-accent, var(--hairline));
        border-radius: 18px;
        padding: 12px;
        display: flex;
        flex-direction: column;
        gap: 8px;
        align-self: start;
        margin-top: 2px;
      }

      .navBtn {
        appearance: none;
        border: 1px solid transparent;
        background: transparent;
        border-radius: 12px;
        padding: 12px 14px;
        text-align: left;
        font-size: 15px;
        font-weight: 600;
        color: var(--ink);
        cursor: pointer;
      }

      .navBtn:hover {
        background: var(--paper);
      }

      .navBtn.active {
        background: var(--hairline);
        border-color: var(--hairline);
      }

      #mainPane {
        display: flex;
        flex-direction: column;
        gap: 18px;
        min-width: 0;
        height: 100%;
        overflow: hidden;
        padding-right: 0;
      }

      .appPane {
        display: none;
        flex-direction: column;
        gap: 18px;
      }

      /* B-X3 Stage 2c: hide every mutation affordance for VIEW_ONLY
         members. main.js toggles body.viewOnly based on the current
         business's BusinessMembership role from /me. Mutations are
         already 403'd server-side (Stage 2a); this is the UI mirror. */
      body.viewOnly .js-mutate-only { display: none !important; }

      /* FIX 1 (2026-06-15): the old "hide every .js-publish-only control
         when identity is incomplete" rule was removed. Publish / distribute
         / notify controls now stay visible; clicking them when legal
         identity is incomplete opens showLegalIdentityModal() (see main.js).
         The server still 403s IDENTITY_REQUIRED on the matching routes, so
         the gate stays authoritative regardless of the UI. */

      .appPane.active {
        display: flex;
        height: 100%;
        min-height: 0;
      }

      .paneShell {
        display: flex;
        flex-direction: column;
        gap: 0;
        height: 100%;
        min-height: 0;
      }

      .paneHeaderCard {
        position: sticky;
        top: 0;
        z-index: 80;
        background: var(--paper);
        padding-top: 2px;
        padding-bottom: 10px;
      }

      .paneHeaderCard .card {
        border-color: var(--business-accent, var(--hairline));
        background: rgba(255,255,255,0.94);
        box-sizing: border-box;
      }

      .paneBody {
        display: flex;
        flex-direction: column;
        gap: 18px;
        flex: 1 1 auto;
        min-height: 0;
        overflow-y: auto;
        padding-top: 0;
      }

      .paneBody {
        border: 1px solid var(--business-accent, var(--hairline));
        border-radius: 18px;
        padding: 12px;
        background: rgba(255,255,255,0.55);
        box-sizing: border-box;
        overflow-x: hidden;
      }



      .paneHeaderCard .card {
        border-radius: 18px;
        box-shadow: 0 1px 0 0 var(--business-accent, var(--hairline));
      }

      .appPane.active > .card {
        border-color: var(--business-accent, var(--hairline));
      }

      .super-only {
        display: none;
      }

      body.super-mode .super-only {
        display: block;
      }

      .sectionTitle {
        margin: 0;
        font-size: 26px;
        font-weight: 800;
        letter-spacing: -0.03em;
      }

      /* Help-icon badge — same look in two places:
         (a) topBar #btnHelpDrawer.helpIconBtn — opens the help drawer
         (b) per-section .sectionHelpBtn — JS-appended to every .sectionTitle
             by the DOMContentLoaded bootstrap in main.js; positioned
             absolutely to the top-right of each section header card so
             the title can centre cleanly underneath.
         !important on every property because the global button {} +
         mobile polish .paneHeaderCard button { width:100% !important }
         rules would otherwise inflate to full-width / oversized. */
      .helpIconBtn,
      .sectionHelpBtn {
        display: inline-flex !important;
        align-items: center !important;
        justify-content: center !important;
        width: 32px !important;
        height: 32px !important;
        min-width: 32px !important;
        min-height: 32px !important;
        max-width: 32px !important;
        padding: 0 !important;
        font-size: 15px !important;
        font-weight: 700 !important;
        letter-spacing: 0 !important;
        border-radius: 50% !important;
        border: 1px solid var(--slate) !important;
        background: #fff !important;
        cursor: pointer !important;
        color: var(--slate) !important;
        vertical-align: middle !important;
        line-height: 1 !important;
        flex: 0 0 auto !important;
      }
      .helpIconBtn:hover,
      .sectionHelpBtn:hover {
        background: var(--paper) !important;
      }

      /* .paneHeaderCard .card needs position:relative so the mobile-only
         absolute positioning of .sectionHelpBtn below has an anchor.
         At desktop widths the badge stays inline next to the title (which
         is its natural JS-appended position — see main.js bootstrap) so
         it doesn't collide with the right-side actions cluster (filter
         selects, New Campaign button, etc). */
      .paneHeaderCard .card {
        position: relative;
      }

      .sectionIntro {
        display: none;
      }
      .internal-only { display: none !important; }
      body.super-mode .internal-only { display: revert !important; }
    
      table td {
        padding: 14px 10px;
        vertical-align: middle;
      }

      table th { border-bottom: 1px solid var(--business-accent, #eee);
        padding: 10px 10px;
        font-size: 12px;
        text-transform: uppercase;
        color: var(--slate);
        letter-spacing: 0.04em;
      }

      table tr { border-bottom: 1px solid var(--business-accent, #eee);
        background: #fff;
      }

      button { border: 1px solid var(--business-accent, #ddd);
        border-radius: 10px;
        border: 1px solid #ddd;
        background: #fafafa;
        padding: 8px 12px;
        font-size: 13px;
        font-weight: 600;
      }

      button:hover {
        background: #f0f0f0;
      }

    
      .campaignGrid {
        display: grid;
        grid-template-columns: repeat(3, minmax(0, 1fr));
        gap: 18px;
      }

      .campaignCard {
        border: 1px solid var(--business-accent, var(--hairline));
        background: #fff;
        border-radius: 18px;
        overflow: hidden;
        box-shadow: 0 10px 26px rgba(0,0,0,0.05);
        display: flex;
        flex-direction: column;
      }

      .campaignCard.expired {
        filter: grayscale(0.35);
        background: var(--paper);
        box-shadow: 0 4px 14px rgba(0,0,0,0.03);
      }

      .campaignCard.expired .campaignHero {
        opacity: 0.68;
      }

      .campaignHero {
        position: relative;
      }

      .campaignEndedStamp {
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%) rotate(-28deg);
        font-size: 34px;
        font-weight: 800;
        letter-spacing: 0.12em;
        color: rgba(60, 50, 40, 0.22);
        pointer-events: none;
        user-select: none;
        white-space: nowrap;
      }



      .campaignCard.expired .campaignBody {
        opacity: 0.72;
      }

      .campaignCard.expired .campaignTitle {
        color: var(--slate);
      }

      .campaignCard.expired .campaignStatValue {
        color: var(--slate);
      }



      .campaignHero {
        aspect-ratio: 1.63;
        background: var(--paper);
        border-bottom: 1px solid var(--business-accent, var(--paper));
        display: flex;
        align-items: center;
        justify-content: center;
        overflow: hidden;
      }

      .campaignHero img {
        width: 100%;
        height: 100%;
        object-fit: cover;
        display: block;
      }

      .campaignHeroPlaceholder {
        color: var(--slate);
        font-size: 14px;
        font-weight: 600;
      }

      .campaignBody {
        padding: 18px;
        display: flex;
        flex-direction: column;
        gap: 14px;
      }

      .campaignTitle {
        font-size: 24px;
        line-height: 1.05;
        font-weight: 800;
        letter-spacing: -0.03em;
        margin: 0;
      }

      .campaignDesc {
        margin: 0;
        color: var(--slate);
        font-size: 14px;
        line-height: 1.4;
      }

      .campaignMetaGrid {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: 10px 14px;
      }

      .campaignMetaItem {
        display: flex;
        flex-direction: column;
        gap: 3px;
      }

      .campaignMetaLabel {
        font-size: 11px;
        text-transform: uppercase;
        letter-spacing: 0.06em;
        color: var(--slate);
        font-weight: 700;
      }

      .campaignMetaValue {
        font-size: 15px;
        font-weight: 600;
        color: var(--ink);
      }

      .campaignMetaCompact {
        display: flex;
        flex-wrap: wrap;
        gap: 10px 16px;
        font-size: 13px;
        color: var(--slate);
        align-items: center;
      }

      .campaignMetaCompact span {
        white-space: nowrap;
      }

      .campaignMetaCompact strong {
        font-weight: 600;
        color: var(--ink);
        margin-right: 4px;
      }

      .campaignMetaInlineBtn {
        border: 1px solid var(--business-accent, #ddd);
        background: transparent;
        border-radius: 8px;
        padding: 4px 8px;
        font-size: 12px;
        cursor: pointer;
      }



      .campaignPills {
        display: flex;
        flex-wrap: wrap;
        gap: 8px;
      }

      .campaignActions {
        display: flex;
        flex-wrap: wrap;
        gap: 10px;
        margin-top: 2px;
      }

      /* Venue card action buttons: 2x2 grid at ALL widths. Was previously
         1x4 on desktop + 2x2 below 1024px. James preferred the 2x2 look
         on his MacBook ("much nicer than 1x4") so promoting to default. */
      .venueActions {
        display: grid;
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 10px;
        margin-top: 6px;
      }

      .venueActions button {
        width: 100%;
        min-width: 0;
        display: inline-flex;
        align-items: center;
        justify-content: center;
      }



      .campaignActions button { border: 1px solid var(--business-accent, #ddd);
        min-width: 0;
      }


      .campaignStatusLine {
        display: grid;
        grid-template-columns: repeat(3, minmax(0, 1fr));
        gap: 8px;
        font-size: 13px;
        color: var(--slate);
        align-items: center;
      }

      .campaignStatusLine strong {
        color: var(--ink);
        font-weight: 700;
      }

      .campaignExpiryRow {
        display: grid;
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 10px;
        padding: 10px 12px;
        border: 1px solid rgba(124, 109, 91, 0.16);
        border-radius: 12px;
        background: rgba(245, 241, 234, 0.7);
        font-size: 13px;
        color: var(--slate);
      }

      .campaignExpiryLabel {
        font-size: 10px;
        text-transform: uppercase;
        letter-spacing: 0.06em;
        color: var(--slate);
        font-weight: 700;
        margin-bottom: 3px;
      }

      .campaignExpiryValue {
        font-size: 13px;
        line-height: 1.25;
        color: var(--ink);
        font-weight: 700;
      }

      .campaignCountdown {
        color: var(--ink);
      }

      .campaignStatsTable {
        display: grid;
        grid-template-columns: repeat(3, minmax(0, 1fr));
        gap: 8px;
      }

      .campaignStatCell {
        border: 1px solid rgba(124, 109, 91, 0.14);
        border-radius: 12px;
        background: var(--paper);
        padding: 9px 8px 8px;
        min-width: 0;
        text-align: center;
      }

      .campaignStatLabel {
        font-size: 9px;
        text-transform: uppercase;
        letter-spacing: 0.05em;
        color: var(--slate);
        font-weight: 700;
        margin-bottom: 4px;
        line-height: 1.2;
        white-space: normal;
      }

      .campaignStatValue {
        font-size: 14px;
        line-height: 1.1;
        font-weight: 700;
        color: var(--ink);
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
      }

      .campaignActions {
        display: grid;
        grid-template-columns: repeat(3, minmax(0, 1fr));
        gap: 8px;
        margin-top: 2px;
      }

      .campaignActions button {
        border: 1px solid var(--business-accent, #ddd);
        min-width: 0;
        width: 100%;
        display: inline-flex;
        align-items: center;
        justify-content: center;
        min-height: 34px;
        padding: 8px 10px;
        font-size: 13px;
        line-height: 1.1;
      }

      .campaignNotifyBtn {
        width: 100%;
        min-width: 0;
        min-height: 34px;
        padding: 8px 10px;
        font-size: 13px;
        line-height: 1.1;
        white-space: nowrap;
      }

      .campaignTitleRow {
        display: flex;
        align-items: flex-start;
        justify-content: space-between;
        gap: 10px;
      }

      .campaignTitleClamp {
        flex: 1 1 auto;
        min-width: 0;
      }

      .campaignTitleClamp .campaignTitle {
        margin: 0;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
      }

      .campaignMetaTop {
        display: grid;
        grid-template-columns: repeat(3, minmax(0, 1fr));
        gap: 8px;
      }

      .campaignMetaTop .campaignMetaItem {
        min-width: 0;
      }

      @media (max-width: 980px) {
        .campaignGrid {
          grid-template-columns: 1fr;
        }
      }

      /* B-W19 Phase A Step 5 — Campaigns list: better card sizing on
         iPad portrait and below. */

      /* Tablet / iPad portrait (981-1280): 2-col instead of crammed 3-col.
         3 cards at iPad portrait (~258px each) cramp the card body content
         (hero + title + stats + 3 action buttons). 2-col gives ~388px
         per card — comfortable. */
      @media (min-width: 981px) and (max-width: 1280px) {
        .campaignGrid {
          grid-template-columns: repeat(2, minmax(0, 1fr));
        }
      }

      /* Narrow phones (<=480px): tighten card chrome so more content
         fits without scrolling per card. */
      @media (max-width: 480px) {
        .campaignBody {
          padding: 14px !important;
          gap: 10px !important;
        }
        .campaignTitle {
          font-size: 19px !important;
        }
        .campaignStatusLine {
          font-size: 12px !important;
        }
        .campaignExpiryRow {
          padding: 8px 10px !important;
        }
        .campaignActions button,
        .campaignNotifyBtn {
          font-size: 12px !important;
          min-height: 36px !important;
          padding: 6px 8px !important;
        }
      }

    
      .campaignHeroFallback {
        width: 100%;
        height: 100%;
        background: linear-gradient(180deg, var(--paper) 0%, var(--paper) 100%);
        display: flex;
        align-items: center;
        justify-content: center;
        padding: 22px;
      }

      .campaignHeroFrame {
        width: 100%;
        height: 100%;
        border: 1px solid rgba(124, 109, 91, 0.18);
        border-radius: 14px;
        display: flex;
        align-items: center;
        justify-content: center;
        position: relative;
        overflow: hidden;
      }

      .campaignHeroFrame::before {
        content: "";
        position: absolute;
        inset: 12px;
        border: 1px solid rgba(124, 109, 91, 0.12);
        border-radius: 10px;
      }

      .campaignHeroInner {
        position: relative;
        z-index: 1;
        text-align: center;
        display: flex;
        flex-direction: column;
        gap: 10px;
        align-items: center;
        max-width: 78%;
      }

      .campaignHeroKicker {
        font-size: 11px;
        text-transform: uppercase;
        letter-spacing: 0.12em;
        color: var(--slate);
        font-weight: 700;
      }

      .campaignHeroTitle {
        font-size: 28px;
        line-height: 1.02;
        letter-spacing: -0.03em;
        font-weight: 800;
        color: var(--ink);
      }

      .campaignHeroSub {
        font-size: 13px;
        color: var(--slate);
        font-weight: 600;
      }

    
      .campaignHero {
        aspect-ratio: 1.63;
        background: linear-gradient(180deg, var(--paper) 0%, var(--paper) 100%);
        border-bottom: 1px solid var(--paper);
        display: flex;
        align-items: center;
        justify-content: center;
        padding: 16px;
      }

      .campaignHeroMedia {
        width: 100%;
        height: 100%;
        background: var(--paper);
        border-radius: 14px;
        border: 1px solid rgba(124, 109, 91, 0.18);
        position: relative;
        display: flex;
        align-items: center;
        justify-content: center;
        overflow: visible;
      }

      .campaignHeroMedia::before {
        content: "";
        position: absolute;
        inset: 10px;
        border: 1px solid rgba(124, 109, 91, 0.28);
        border-radius: 10px;
        pointer-events: none;
      }

      .campaignHeroMedia img {
        width: 100%;
        height: 100%;
        object-fit: cover;
        position: relative;
        z-index: 1;
        display: block;
      }

    
      .campaignKicker {
        font-size: 11px;
        text-transform: uppercase;
        letter-spacing: 0.12em;
        color: var(--slate);
        font-weight: 700;
      }

      .campaignBusiness {
        font-size: 14px;
        color: var(--slate);
        font-weight: 600;
      }

      .campaignTitle {
        font-size: 26px;
        line-height: 1.05;
        font-weight: 800;
        letter-spacing: -0.03em;
        margin: 0;
      }

    
      .campaignHeroOuterFrame {
        position: absolute;
        inset: 0;
        pointer-events: none;
      }

      .frameLine {
        position: absolute;
        border-color: rgba(124, 109, 91, 0.18);
      }

      .frameTopLeft {
        top: 20px;
        left: 56px;
        width: 22%;
        border-top: 1px solid;
      }

      .frameTopRight {
        top: 20px;
        right: 56px;
        width: 22%;
        border-top: 1px solid;
      }

      .frameBottomLeft {
        bottom: 20px;
        left: 56px;
        width: 22%;
        border-top: 1px solid;
      }

      .frameBottomRight {
        bottom: 20px;
        right: 56px;
        width: 22%;
        border-top: 1px solid;
      }

      .frameLeft {
        left: 48px;
        top: 44px;
        bottom: 44px;
        border-left: 1px solid;
      }

      .frameRight {
        right: 48px;
        top: 44px;
        bottom: 44px;
        border-left: 1px solid;
      }

    
      .modalOverlay {
        display: none !important;
        position: fixed;
        inset: 0;
        background: rgba(0,0,0,0.42);
        align-items: center;
        justify-content: center;
        z-index: 9999;
      }

      .modalOverlay.open {
        display: flex !important;
      }

      .modalPanel {
        width: 820px;
        max-width: 92%;
        max-height: 88vh;
        overflow: auto;
        border-radius: 18px;
        background: #fff;
        padding: 20px;
      }

      .modalPanel--sm {
        width: 520px;
        max-height: 90vh;
        overflow-y: auto;
        border-radius: 16px;
      }

      .modalPanel--accent-business {
        border: 1px solid var(--business-accent, var(--hairline));
      }

      .heroImageTopSlot {
        height: 40px;
        margin-bottom: 12px;
        display: flex;
        align-items: center;
        justify-content: center;
      }

      .heroImageTopSlot > button,
      .heroImageTopSlot > div {
        width: 100%;
      }

      .heroImageZoomSlot {
        display: none;
        width: 100%;
      }

      .heroImageZoomSlot label {
        display: block;
        margin-bottom: 6px;
      }

      .heroImageZoomSlot input[type="range"] {
        width: 90%;
        display: block;
        margin: 0 auto;
      }

      .heroReplaceOverlayBtn {
        position: absolute;
        left: 50%;
        bottom: 10px;
        transform: translateX(-50%);
        display: none;
        border: 1px solid rgba(255,255,255,0.75);
        background: rgba(35, 28, 24, 0.58);
        color: #fff;
        border-radius: 999px;
        padding: 7px 12px;
        font-size: 13px;
        line-height: 1;
        cursor: pointer;
        z-index: 2;
        backdrop-filter: blur(6px);
      }

      #newCampaignVenuesList {
        scrollbar-width: thin;
        scrollbar-color: rgba(124,109,91,0.55) var(--paper);
      }

      #newCampaignVenuesList::-webkit-scrollbar {
        width: 10px;
      }

      #newCampaignVenuesList::-webkit-scrollbar-track {
        background: var(--paper);
        border-radius: 10px;
      }

      #newCampaignVenuesList::-webkit-scrollbar-thumb {
        background: rgba(124,109,91,0.55);
        border-radius: 10px;
        border: 2px solid var(--paper);
      }



      button[data-selected="1"] {
        background: var(--business-accent, #C41E3A);
        color: #fff;
      }


      #dashboardMount {
        flex: 1 1 auto;
        min-height: 0;
        height: 100%;
        display: flex;
        flex-direction: column;
      }

      .dashboardGrid {
        display: grid;
        grid-template-columns: minmax(0, 1.05fr) minmax(0, 1.1fr) 360px;
        gap: 16px;
        align-items: stretch;
        width: 100%;
        height: 100%;
        min-height: 0;
        flex: 1 1 auto;
      }

      .dashboardCol {
        min-width: 0;
        min-height: 0;
        display: flex;
        flex-direction: column;
        gap: 18px;
        height: 100%;
      }

      .dashboardCol > * {
        flex: 1 1 auto;
      }

      .dashboardCard {
        border: 1px solid var(--business-accent, var(--hairline));
        background: rgba(255,255,255,0.96);
        border-radius: 18px;
        box-shadow: 0 10px 26px rgba(0,0,0,0.05);
        padding: 18px;
        min-width: 0;
        height: 100%;
        box-sizing: border-box;
      }

      .dashboardCardTitle {
        margin: 0 0 6px 0;
        font-size: 20px;
        font-weight: 800;
        letter-spacing: -0.03em;
        color: var(--ink);
      }

      .dashboardCardIntro {
        margin: 0;
        color: var(--slate);
        font-size: 13px;
        line-height: 1.45;
      }

      .dashboardHeroMetric {
        font-size: 42px;
        line-height: 1;
        letter-spacing: -0.05em;
        font-weight: 800;
        color: var(--ink);
        margin: 14px 0 8px 0;
      }

      .dashboardMetricGrid {
        display: grid;
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 12px;
      }

      .dashboardMiniStat {
        border: 1px solid var(--business-accent, var(--paper));
        border-radius: 14px;
        background: var(--paper);
        padding: 14px;
      }

      .dashboardMiniStatLabel {
        font-size: 12px;
        color: var(--slate);
        margin-bottom: 6px;
      }

      .dashboardMiniStatValue {
        font-size: 28px;
        line-height: 1;
        font-weight: 800;
        letter-spacing: -0.04em;
        color: var(--ink);
      }

      .dashboardMiniStatSplitRow {
        display: flex;
        align-items: stretch;
        gap: 10px;
        margin-top: 2px;
      }

      .dashboardMiniStatSplitCell {
        flex: 1;
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        gap: 4px;
      }

      .dashboardMiniStatSplitDivider {
        width: 1px;
        background: var(--business-accent, var(--paper));
        opacity: 0.6;
      }

      .dashboardMiniStatSubLabel {
        font-size: 11px;
        color: var(--slate);
        letter-spacing: 0.01em;
      }

      .dashboardMiniStatSplit .dashboardMiniStatValue {
        font-size: 22px;
      }

      .dashboardRevenuePrompt {
        display: flex;
        flex-direction: column;
        gap: 14px;
        align-items: flex-start;
      }

      .dashboardSubActions {
        display: flex;
        align-items: center;
        gap: 10px;
        flex-wrap: wrap;
      }

      .dashboardHeaderControls {
        display: flex;
        align-items: center;
        justify-content: flex-end;
        gap: 12px;
        flex-wrap: wrap;
      }

      .dashboardHeaderSelect {
        width: 170px;
        min-width: 170px;
      }

      .dashboardHeaderSelect--time {
        width: 150px;
        min-width: 150px;
      }

      .dashboardLiveHeader {
        width: 100%;
        display: grid;
        grid-template-columns: 1fr auto 1fr;
        align-items: center;
        gap: 12px;
        margin-bottom: 10px;
      }

      .dashboardLiveHeader .dashboardStoreLink:first-child {
        justify-self: start;
      }

      .dashboardLiveHeader .dashboardStoreLink:last-child {
        justify-self: end;
      }

      .revenueGrid {
        display: grid;
        grid-template-columns: 320px minmax(0, 1fr);
        gap: 18px;
        align-items: stretch;
        min-height: 0;
        height: 100%;
      }

      .revenueCol {
        min-width: 0;
        min-height: 0;
        display: flex;
        flex-direction: column;
        gap: 18px;
      }

      .revenueCard {
        border: 1px solid var(--business-accent, var(--hairline));
        background: rgba(255,255,255,0.96);
        border-radius: 18px;
        box-shadow: 0 10px 26px rgba(0,0,0,0.05);
        padding: 18px;
        min-width: 0;
        min-height: 0;
        box-sizing: border-box;
      }

      .revenueList {
        display: flex;
        flex-direction: column;
        gap: 10px;
        max-height: 100%;
        overflow: auto;
        padding-right: 4px;
      }

      .revenueCampaignBtn {
        width: 100%;
        text-align: left;
        border: 1px solid var(--business-accent, var(--hairline));
        background: #fff;
        border-radius: 14px;
        padding: 12px 14px;
        cursor: pointer;
      }

      .revenueCampaignBtn.active {
        background: rgba(183,121,31,0.10);
        border-color: var(--business-accent, #C41E3A);
      }

      .revenueEditorLabel {
        font-size: 13px;
        color: var(--slate);
        margin-bottom: 6px;
      }

      .dashboardStoreLink {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        min-height: 38px;
        padding: 0 14px;
        border: 1px solid var(--business-accent, var(--hairline));
        border-radius: 999px;
        background: #fff;
        color: var(--ink);
        text-decoration: none;
        font-size: 13px;
        font-weight: 700;
        white-space: nowrap;
      }

      .dashboardPhoneCard {
        border: 1px solid var(--business-accent, var(--hairline));
        background: rgba(255,255,255,0.96);
        border-radius: 18px;
        box-shadow: 0 10px 26px rgba(0,0,0,0.05);
        padding: 18px;
        min-width: 0;
        min-height: 0;
        height: 100%;
        box-sizing: border-box;
        display: flex;
        flex-direction: column;
        align-items: center;
      }

      .dashboardPhoneFrame {
        width: 192px;
        height: 392px;
        position: relative;
        display: flex;
        align-items: flex-start;
        justify-content: center;
        margin-top: 8px;
      }

      .dashboardPhoneShell {
        position: absolute;
        inset: 0;
        border: 3px solid rgba(31,26,23,0.28);
        border-radius: 38px;
        background: linear-gradient(180deg, rgba(250,248,244,0.85) 0%, rgba(244,240,232,0.82) 100%);
        box-shadow: 0 8px 24px rgba(0,0,0,0.06);
        box-sizing: border-box;
      }

      .dashboardPhoneNotch {
        width: 74px;
        height: 5px;
        border-radius: 999px;
        background: rgba(31,26,23,0.40);
        margin: 14px auto 0 auto;
      }

      .dashboardPhoneViewport {
        position: absolute;
        top: 26px;
        left: 50%;
        transform: translateX(-50%);
        width: 320px;
        height: 332px;
        overflow-y: auto;
        overflow-x: visible;
        display: flex;
        flex-direction: column;
        gap: 10px;
        box-sizing: border-box;
        z-index: 2;
      }

      .dashboardEmptyState {
        color: var(--slate);
        font-size: 14px;
        line-height: 1.5;
      }

      /* B-W19 — hide the desktop "Get the Flycket Dash app" callout at
         iPad portrait (1024px) and below. At iPad landscape (1366px)
         it now fits because the campaign-picker dropdown labels were
         shortened (the "Campaign:" prefix was removed in main.js so
         the longest option string went from "Campaign: All campaigns"
         to "All campaigns" — saved enough width to keep the row from
         wrapping). On mobile (<=768px) the callout is also hidden via
         the dedicated rule in the mobile media block. */
      @media (max-width: 1024px) {
        .dashboardHeaderControls > div:nth-child(2) {
          display: none !important;
        }
      }

      @media (max-width: 1280px) {
        .dashboardGrid {
          grid-template-columns: 1fr;
        }

        .dashboardPhoneCard {
          align-items: flex-start;
        }
      }

      /* === B-W19 polish: global anchor color reset ======================== */
      /* Default browser link color was leaking through on anchors that
         didn't have an explicit inline color (e.g. <a class="navBtn">
         in the sidebar, anchors inside pane content). Inherit the
         surrounding text color, no decoration by default, underline only
         on hover. Anchors with explicit inline color/text-decoration
         (signup/login footer links) keep their styling — inline wins. */
      #adminRoot a {
        color: inherit;
        text-decoration: none;
      }
      #adminRoot a:hover {
        text-decoration: underline;
      }

      /* === B-W19 Phase A foundation: mobile responsive layer ============== */
      /* Mobile-only utility — desktop hides, mobile shows */
      .mobileOnly { display: none; }
      /* Inverse utility — visible everywhere except mobile */
      .hideOnMobile { /* default visible at all widths; @media below hides it on mobile */ }

      /* Hamburger toggle — hidden on desktop, shown <=768px */
      #navToggle {
        display: none;
        appearance: none;
        background: transparent;
        border: 1px solid var(--business-accent, var(--hairline));
        border-radius: 12px;
        width: 44px;
        height: 44px;
        font-size: 22px;
        line-height: 1;
        color: var(--ink);
        cursor: pointer;
        padding: 0;
        flex-shrink: 0;
      }
      #navToggle:hover { background: var(--paper); }

      @media (max-width: 768px) {
        /* Mobile-only items become visible */
        .mobileOnly { display: block; }
        /* Items marked .hideOnMobile disappear on phones */
        .hideOnMobile { display: none !important; }

        /* Hamburger visible */
        #navToggle {
          display: inline-flex;
          align-items: center;
          justify-content: center;
        }

        /* Release the document-level lock so the page can scroll.
           body has `height: 100vh; overflow: hidden` at the top of the
           stylesheet (line 71-76) — that's the actual root cause of
           "no scroll on mobile"; releasing inner containers alone is
           futile while body is overflow:hidden. */
        html {
          overflow-x: hidden !important;
        }
        body {
          height: auto !important;
          overflow-x: hidden !important;
          overflow-y: visible !important;
        }

        /* Release #appShell from the desktop fixed-height cap */
        #appShell {
          height: auto !important;
          min-height: calc(100vh - 48px);
        }

        /* topBar full-bleed + compressed */
        #topBar {
          top: 0;
          left: 0;
          right: 0;
          padding: 10px 14px;
          margin-bottom: 0;
          border-radius: 0;
          border-left: none;
          border-right: none;
          border-top: none;
          gap: 10px;
        }

        /* Hide the entire brand block on mobile — Flycket mark + "Flycket"
           title + "Business Admin" subtitle all go away. Mobile users
           land on admin.flycket.com already knowing the product; the
           limited topBar real estate is better used for active business
           identity (centered) + nav controls (hamburger + ?). */
        #topBarLeft { display: none !important; }

        /* Show businessCenterBlock as a second row BELOW the topBar
           (absolutely positioned at top:100% of topBar). Long business
           names truncate with ellipsis at narrow widths. */
        #businessCenterBlock {
          display: flex !important;
          position: static !important;
          transform: none !important;
          flex: 1 1 auto;
          min-width: 0;
          flex-direction: column !important;
          align-items: center;
          justify-content: center;
          text-align: center;
          pointer-events: auto !important;
          padding: 0 8px !important;
          margin: 0 !important;
        }
        #currentBusinessName {
          font-size: 15px !important;
          white-space: nowrap;
          overflow: hidden;
          text-overflow: ellipsis;
          max-width: 100%;
        }
        #currentBusinessCategory {
          font-size: 10px !important;
          white-space: nowrap;
          overflow: hidden;
          text-overflow: ellipsis;
          max-width: 100%;
        }

        /* topBarRight needs to flex so businessCenterBlock can take
           available width between brand block and ? icon */
        #topBarRight {
          flex: 1 1 auto !important;
          min-width: 0 !important;
        }

        /* Still hide the Change/New/Logout cluster on mobile —
           those moved into Settings pane in 82e40c9. NOTE: #topBusinessSlot
           is NOT in this hide list because its visibility is controlled
           dynamically by window.toggleBusinessSelector() — adding it here
           would silently block Change Business from opening (which was
           the bug James reported after Part 2 landed). The mobile-specific
           positioning override below ensures it renders sensibly when JS
           reveals it. */
        #topBarActions { display: none !important; }
        #topBarRight { gap: 8px; }

        /* Change Business popover (topBusinessSlot) on mobile: pin it
           below the topBar full-width instead of desktop's absolute
           horizontal-centred positioning (which would put it off-screen
           or overlapping the hamburger/help icon on a phone). */
        #topBusinessSlot {
          position: fixed !important;
          top: 68px !important;
          left: 8px !important;
          right: 8px !important;
          transform: none !important;
          width: auto !important;
          max-width: none !important;
          z-index: 600 !important;
        }
        #topBusinessSlot #businessSelect {
          min-width: 0 !important;
          width: 100% !important;
        }
        /* The wrapper div inside topBusinessSlot (built by initAppShell
           in main.js) has inline minWidth/width:320px on businessSelect
           and inline flexWrap:nowrap on the wrap; override so it fits
           phone widths. */
        #topBusinessSlot > div {
          flex-wrap: wrap !important;
          gap: 8px !important;
        }

        /* shellBody: single column; sidebar overlays. Without the brand
           block, topBar tallest child is the 44px hamburger + 20px padding
           = 64px topBar height + 8px buffer = 72px margin-top. */
        #shellBody {
          grid-template-columns: 1fr;
          margin-top: 72px;
          height: auto;
          min-height: calc(100vh - 72px);
          overflow: visible;
          gap: 0;
          padding: 0 8px env(safe-area-inset-bottom, 60px);
          box-sizing: border-box;
        }

        /* B-W19 Phase A Step 4 — release the pane scroll-container chain
           (#mainPane / .appPane / .paneShell / .paneBody all assumed
           desktop-fixed-height with overflow:auto on .paneBody — that's
           the inner scroll trap that prevented natural page scroll).
           Longhand overflow-x:hidden + overflow-y:visible — the previous
           shorthand `overflow:visible` unset desktop's overflow-x:hidden
           on .paneBody, which let horizontal overflow leak (the Revenue
           card "side-scroll" symptom). */
        #mainPane,
        .appPane,
        .appPane.active,
        .paneShell {
          height: auto !important;
          overflow-x: hidden !important;
          overflow-y: visible !important;
        }
        .paneBody {
          height: auto !important;
          overflow-x: hidden !important;
          overflow-y: visible !important;
          padding: 12px !important;
        }

        /* B-W19 Phase A Step 4 — Dashboard: escape desktop-height assumptions
           Longhand overflow-x:hidden + overflow-y:visible — the previous
           shorthand `overflow:visible` let horizontal overflow leak through
           the column chain even though .dashboardCard already had
           overflow-x:hidden, because the cards' parent .dashboardCol was
           not clipping at the boundary. Apply the same longhand pattern
           used on .paneBody / #mainPane in fix-3. */
        #dashboardMount,
        .dashboardGrid,
        .dashboardCol {
          height: auto !important;
          overflow-x: hidden !important;
          overflow-y: visible !important;
        }
        .dashboardCol > * {
          flex: none !important;
        }
        .dashboardCard { height: auto !important; }

        /* Dashboard header controls stack + full-width on mobile */
        .dashboardHeaderControls {
          flex-direction: column !important;
          align-items: stretch !important;
          width: 100% !important;
          justify-content: stretch !important;
        }
        .dashboardHeaderSelect,
        .dashboardHeaderSelect--time {
          width: 100% !important;
          min-width: 0 !important;
        }

        /* The inner selects-container div (3rd child of .dashboardHeaderControls)
           is a flex row via inline style — stack it column + full-width so the
           two <select>s end up one per row, not side-by-side */
        .dashboardHeaderControls > div:nth-child(3) {
          flex-direction: column !important;
          width: 100% !important;
          gap: 10px !important;
        }

        /* Custom-range "popover" doesn't work as an absolute overlay on phones
           (it's wider than the viewport and clips off-screen). On mobile let it
           flow in-page as its own full-width row beneath the selects, with the
           two date inputs flexing to share the width so they fit cleanly. */
        #dashboardCustomRangePopover {
          position: static !important;
          top: auto !important;
          right: auto !important;
          width: 100% !important;
          box-shadow: none !important;
          border: none !important;
          padding: 0 !important;
        }
        #dashboardCustomRangeInputs {
          width: 100% !important;
        }
        #dashboardCustomRangeInputs input[type="date"] {
          flex: 1 1 0 !important;
          width: auto !important;
        }

        /* Cards never horizontally scroll — safety net for any wide
           inner content that would otherwise force a side-scroll */
        .dashboardCard,
        .dashboardPhoneCard {
          overflow-x: hidden !important;
          max-width: 100% !important;
        }

        /* Hide the "Get the Flycket Dash app" callout on mobile —
           it's a desktop promo and overflows phone widths. Owners
           who want the app can find it via the help drawer / store
           search; not load-bearing on the dashboard. */
        .dashboardHeaderControls > div:nth-child(2) {
          display: none !important;
        }

        /* Flatten the phone-mockup framing on Live activity card —
           the decorative 192x392 frame + 320px absolute viewport
           literally cannot render in a 360px phone screen */
        .dashboardPhoneFrame {
          width: 100% !important;
          height: auto !important;
          min-height: 200px;
          margin-top: 8px !important;
        }
        .dashboardPhoneShell,
        .dashboardPhoneNotch {
          display: none !important;
        }
        .dashboardPhoneViewport {
          position: static !important;
          width: 100% !important;
          height: auto !important;
          min-height: 200px;
          transform: none !important;
          top: auto !important;
          left: auto !important;
        }

        /* sideNav becomes off-canvas drawer — top:108 sits flush below the
           topBar + business-info row (combined ~108px) */
        #sideNav {
          position: fixed;
          top: 64px;
          left: 0;
          bottom: 0;
          width: 280px;
          max-width: 80vw;
          margin-top: 0;
          z-index: 500;
          transform: translateX(-100%);
          transition: transform 0.22s ease-out;
          border-radius: 0;
          border-top: none;
          border-left: none;
          border-bottom: none;
          background: #fff;
          box-shadow: 2px 0 18px rgba(0,0,0,0.08);
          overflow-y: auto;
        }
        #sideNav.open { transform: translateX(0); }

        /* Backdrop when drawer is open — inset top matches sideNav top */
        body.navOpen::before {
          content: '';
          position: fixed;
          inset: 64px 0 0 0;
          background: rgba(0,0,0,0.35);
          z-index: 450;
        }

        /* Modal: near full-bleed sheet */
        .modalPanel {
          width: 100% !important;
          max-width: 100% !important;
          max-height: 100vh;
          border-radius: 0;
          padding: 16px;
        }
        .modalPanel--sm {
          width: 100% !important;
          border-radius: 0;
        }
        .modalOverlay { align-items: flex-start; }

        /* B-W19 Phase A Step 2 — Login section: recover vertical space below tablet */
        #loginSection {
          margin: 32px auto !important;
          padding: 0 16px !important;
        }

        /* Fix #2 — Promote modal QR sub-view: stack vertically, full-width
           preview at 1:1 scale (no horizontal squish), single-column print
           buttons. Modal body scrolls naturally. Height tuned for the
           install-gate page in print-qr mode rendering vertically on
           mobile (card on top + QR aside below — see install-gate.ts
           @media (max-width: 768px) rule). */
        .qrPreviewWrap {
          height: 950px !important;
          overflow: hidden !important;
        }
        .qrPreviewFrame {
          width: 100% !important;
          height: 950px !important;
          transform: none !important;
        }
        .qrPrintButtonsGrid {
          grid-template-columns: 1fr !important;
          gap: 14px !important;
        }

        /* Fix #3 — Promote modal Images sub-view: stack the Stories +
           Feed sections vertically instead of side-by-side. Each section
           keeps its internal column structure (title → thumb → social
           icons → buttons); only the wrapping flex direction flips.
           align-items:stretch makes each column take full modal width on
           mobile (overriding the inline align-items:start), so the inner
           align-items:center on .imagesColumn properly centers the thumb
           + button row horizontally. */
        .imagesColumnsWrap {
          flex-direction: column !important;
          align-items: stretch !important;
          gap: 32px !important;
        }

        /* Fix #4 — Promote modal Email sub-view footer: 4 buttons (Back,
           Download PNG, Copy HTML, Close) don't fit a single mobile row.
           When the JS adds .campaignAssetFooter--email class (email view
           only), switch the footer to a 2×2 grid:
             Row 1: Download PNG | Copy HTML  (actions on top)
             Row 2: Back         | Close     (navigation on bottom)
           display:contents on the footerSlot wrapper flattens its two
           buttons into direct grid items so they participate in the
           parent grid layout. */
        .campaignAssetFooter--email {
          display: grid !important;
          grid-template-columns: 1fr 1fr !important;
          grid-template-rows: auto auto !important;
          gap: 10px !important;
          justify-content: stretch !important;
        }
        .campaignAssetFooter--email > * {
          width: 100% !important;
          min-width: 0 !important;
        }
        .campaignAssetFooter--email #campaignAssetFooterSlot {
          display: contents !important;
        }
        .campaignAssetFooter--email button[data-promoteaction="email-download"] {
          grid-row: 1;
          grid-column: 1;
        }
        .campaignAssetFooter--email button[data-promoteaction="email-copy-html"] {
          grid-row: 1;
          grid-column: 2;
        }
        .campaignAssetFooter--email #btnPromoteBack {
          grid-row: 2;
          grid-column: 1;
        }
        .campaignAssetFooter--email #btnCloseCampaignAsset {
          grid-row: 2;
          grid-column: 2;
        }

        /* FX-9 (2026-07-02) — Promote modal mobile model REBUILT. The old
           "whole panel scrolls" approach (Fix #2c v2) put the Back/Close footer
           BELOW a 432px QR preview, so on a sub-view it was unreachable — "no
           close, no way to progress." New model: a flex column that fits the
           viewport, the BODY scrolls internally, and the footer is PINNED at the
           bottom so Back/Close are always visible on every sub-view. Force 94vw
           regardless of the JS per-view maxWidth (the QR view's 900px overflowed
           the phone). */
        #campaignAssetModal .modalPanel {
          box-sizing: border-box !important;
          width: 94vw !important;
          max-width: 94vw !important;
          max-height: 92dvh !important;
          display: flex !important;
          flex-direction: column !important;
          overflow: hidden !important;
          padding-bottom: calc(14px + env(safe-area-inset-bottom, 0px)) !important;
        }
        #campaignAssetBody {
          flex: 1 1 auto !important;
          overflow-y: auto !important;
          overflow-x: hidden !important;
          min-height: 0 !important;
          -webkit-overflow-scrolling: touch !important;
        }
        #campaignAssetFooter {
          flex: 0 0 auto !important;
          position: sticky !important;
          bottom: 0 !important;
          background: #fff !important;
          padding-top: 10px !important;
          margin-top: 8px !important;
        }
        #campaignAssetFooter button {
          touch-action: manipulation !important;
          -webkit-tap-highlight-color: rgba(0,0,0,0.1) !important;
          cursor: pointer !important;
        }
        /* Q-1 (2026-07-02): the install-gate iframe preview clipped before the QR (and
           renders blank on iOS anyway). On mobile hide the iframe and show the actual QR
           as an <img> (qrserver) — the point of the preview, and it renders everywhere. */
        #campaignAssetModal .promoteSubView--qr .qrPreviewFrame { display: none !important; }
        #campaignAssetModal .promoteSubView--qr .qrPreviewWrap {
          height: auto !important;
          padding: 14px !important;
          display: flex !important;
          align-items: center !important;
          justify-content: center !important;
        }
        #campaignAssetModal .promoteSubView--qr .qrPreviewImg {
          display: block !important;
          width: 100% !important;
          max-width: 240px !important;
          height: auto !important;
        }
        #campaignAssetModal .qrPrintButtonsGrid { grid-template-columns: 1fr !important; }
        /* Images sub-view: stack the Stories / Feed columns on mobile. */
        #campaignAssetModal .imagesColumnsWrap { flex-direction: column !important; gap: 16px !important; }
      }

      /* Q-1: desktop default — the mobile QR <img> is hidden (desktop shows the rich
         install-gate iframe preview instead). Shown only in the 768px block above. */
      #campaignAssetModal .promoteSubView--qr .qrPreviewImg { display: none; }

      @media (max-width: 480px) {
        /* Tap-target floor: 44px minimum on any admin clickable */
        #adminRoot button,
        #adminRoot input,
        #adminRoot select,
        #adminRoot textarea {
          min-height: 44px;
        }

        /* Tighten topbar further on very narrow phones */
        #topBar { padding: 8px 12px; gap: 6px; }
        #brandTitle { font-size: 16px; }
        #flycketMark {
          width: 32px !important;
          height: 32px !important;
        }
        #topBarLeft {
          grid-template-columns: 32px auto !important;
        }

        /* B-W19 Phase A Step 1 — Signup page tightening */
        #signupRoot {
          margin: 24px auto !important;
          padding-bottom: 40px !important;
        }
        #signupCategoryGrid {
          grid-template-columns: 1fr 1fr !important;
        }
        .signupCategoryBtn {
          min-height: 48px;
        }

        /* B-W19 Phase A Step 2 — Login section tightening (narrow phones) */
        #loginSection {
          margin: 20px auto !important;
        }
        #loginSection .card {
          padding: 20px !important;
        }
        #loginSection h2 {
          font-size: 22px;
        }

        /* B-W19 Phase A Step 3 — Welcome modal tightening (narrow phones) */
        #adminWelcomeCard {
          padding: 24px 20px !important;
        }
        #adminWelcomeTitle {
          font-size: 26px !important;
        }

        /* B-W19 polish — section header consistency across all panes
           (Dashboard / Campaigns / Venues / Revenue / Settings / Help all
           share .paneHeaderCard > .card > [flex row]). On mobile the
           title-row stacks above the actions-row, the actions-row stacks
           its children vertically, and all selects/buttons inside the
           header go full-width. */
        .paneHeaderCard .card {
          padding: 12px !important;
        }
        .paneHeaderCard .card > div {
          flex-direction: column !important;
          align-items: stretch !important;
          gap: 10px !important;
          flex-wrap: wrap !important;
        }
        .paneHeaderCard .card > div > div {
          flex-direction: row !important;
          flex-wrap: wrap !important;
          align-items: stretch !important;
          width: 100% !important;
          gap: 8px !important;
        }
        /* Selects in section headers share width 50/50 on mobile; buttons
           take full row so primary actions (New Campaign, New Venue) get
           their own line below the picker pair. */
        .paneHeaderCard select {
          flex: 1 1 calc(50% - 4px) !important;
          width: auto !important;
          min-width: 0 !important;
        }
        .paneHeaderCard button {
          flex: 1 1 100% !important;
          width: 100% !important;
          min-width: 0 !important;
        }
        /* Dashboard's selects-container (3rd child of .dashboardHeaderControls)
           needs the same row treatment — overrides the column rule I'd
           applied in the original Step 4 patch. */
        .dashboardHeaderControls {
          flex-direction: column !important;
          align-items: stretch !important;
          width: 100% !important;
        }
        .dashboardHeaderControls > div:nth-child(3) {
          flex-direction: row !important;
          flex-wrap: wrap !important;
          width: 100% !important;
          gap: 8px !important;
        }
        .dashboardHeaderSelect,
        .dashboardHeaderSelect--time {
          flex: 1 1 calc(50% - 4px) !important;
          width: auto !important;
          min-width: 0 !important;
        }
        .sectionTitle {
          font-size: 20px !important;
          text-align: center !important;
        }

        /* Mobile-only: pull the section ? out of inline flow and pin it
           top-right of the header card. Title can then centre cleanly
           without the button affecting its width / alignment. */
        .sectionHelpBtn {
          position: absolute !important;
          top: 12px !important;
          right: 12px !important;
          margin: 0 !important;
          z-index: 1 !important;
        }

        /* B-W19 Phase A Step 4 — Dashboard tightening (narrow phones) */
        .dashboardCard {
          padding: 14px !important;
        }
        .dashboardHeroMetric {
          font-size: 32px !important;
        }
        .dashboardMiniStatValue {
          font-size: 24px !important;
        }
        .dashboardMiniStatSplit .dashboardMiniStatValue {
          font-size: 18px !important;
        }
        .dashboardCardTitle {
          font-size: 18px !important;
        }

        /* B-W19 Phase A Step 6 — Edit AND Create Campaign modals on mobile.
           Both modals share the same structure: 2-col grids for hero+image
           / title+desc+expiry; 2-col grids for venues / visibility+sharing;
           4-col grid for usage controls. Plus inner 1fr 1fr toggle pairs
           (Private/Public, Yes/No, Unlimited/Limited) which stay 2-col by
           design.

           One set of rules covers both modals via list of selectors. */

        /* Collapse the desktop 2-col / 4-col grids to single column on
           mobile. Catches `minmax(...)` AND `repeat(...)` patterns; the
           1fr 1fr toggle pairs are intentionally NOT matched so they stay
           side-by-side. */
        #editCampaignModal div[style*="grid-template-columns:minmax"],
        #createCampaignModal div[style*="grid-template-columns:minmax"],
        #editCampaignModal div[style*="grid-template-columns: minmax"],
        #createCampaignModal div[style*="grid-template-columns: minmax"],
        #editCampaignModal div[style*="grid-template-columns:repeat"],
        #createCampaignModal div[style*="grid-template-columns:repeat"],
        #editCampaignModal div[style*="grid-template-columns: repeat"],
        #createCampaignModal div[style*="grid-template-columns: repeat"] {
          grid-template-columns: 1fr !important;
        }

        /* Both venues lists: 100px fixed height is cramped when stacked
           vertically; raise to 200px (max 240px) so the scroll area is
           usable. */
        #editCampaignVenuesList,
        #newCampaignVenuesList {
          height: 200px !important;
          max-height: 240px !important;
        }

        /* Both modal footers stack their action buttons full-width.
           Edit footer is a .row with space-between; Create footer is a
           grid (collapsed above) containing a .row with flex-end. Both
           need their inner .row to switch to column + stretch. */
        #editCampaignModal .row[style*="space-between"],
        #editCampaignModal .row[style*="flex-end"],
        #createCampaignModal .row[style*="space-between"],
        #createCampaignModal .row[style*="flex-end"] {
          flex-direction: column !important;
          align-items: stretch !important;
          gap: 10px !important;
          width: 100% !important;
        }
        #editCampaignModal #btnCloseEdit,
        #editCampaignModal #btnSaveEdit,
        #createCampaignModal #btnCloseCreateCampaign,
        #createCampaignModal #btnCreate {
          width: 100% !important;
        }

        /* === Step 6 modal tidy patch — 5 issues James spotted ============= */

        /* 5+top: Modal panel bottom-clearance (140px fallback for iOS
           Safari URL bar + Android nav) AND top padding so the heading
           ("Edit Campaign" / "Create Campaign") doesn't hug the top of
           the screen. env(safe-area-inset-top) covers iOS notch /
           dynamic island. */
        /* Modal full-bottom + internal padding for Save/Create visibility.
           CRITICAL: box-sizing:border-box so max-height INCLUDES the
           padding. Without it, default content-box means max-height
           refers to CONTENT area only, and 130px padding-bottom adds
           OUTSIDE that — total panel = 100dvh + 130px, overflows
           viewport. Padding zone (and Save button above it) end up
           below visible viewport, unreachable via scroll.
           THIS was the root cause of the persistent "Save covered"
           issue — not the padding value, but the box model. */
        #editCampaignModal .modalPanel,
        #createCampaignModal .modalPanel {
          box-sizing: border-box !important;
          max-height: 100dvh !important;
          padding-top: calc(20px + env(safe-area-inset-top, 0px)) !important;
          padding-bottom: calc(40px + env(safe-area-inset-bottom, 0px)) !important;
          overflow-x: hidden !important;
          /* position:relative for .modalCloseBtn absolute anchoring */
          position: relative !important;
        }

        /* Close X button — mobile-only, top-right of the modal panel.
           Delegates to the existing footer Cancel button via onclick.
           Desktop hides via .mobileOnly because the modal there is
           small enough that the footer Cancel is always in view. */
        .modalCloseBtn {
          position: absolute !important;
          top: 8px !important;
          right: 8px !important;
          width: 44px !important;
          height: 44px !important;
          min-width: 44px !important;
          padding: 0 !important;
          font-size: 28px !important;
          line-height: 1 !important;
          background: transparent !important;
          border: 1px solid var(--business-accent, var(--hairline)) !important;
          border-radius: 50% !important;
          color: var(--slate) !important;
          cursor: pointer !important;
          z-index: 10 !important;
          display: inline-flex !important;
          align-items: center !important;
          justify-content: center !important;
        }
        .modalCloseBtn:hover {
          background: var(--paper) !important;
        }

        /* Normalize heading spacing across BOTH modals. Edit's <h3> has
           inline margin-top:0; Create's <h2> uses browser default margin.
           That mismatch caused the previous "Edit hugs top, Create doesn't"
           inconsistency.

           Fix: zero out both headings' margin-top + set a matching
           margin-bottom. All visible breathing room comes from the modal
           panel's padding-top — which is the SAME rule applied to both
           modals, so the spacing is guaranteed identical. (Same rules
           across similar surfaces.) */
        #editCampaignModal .modalPanel > h2,
        #editCampaignModal .modalPanel > h3,
        #createCampaignModal .modalPanel > h2,
        #createCampaignModal .modalPanel > h3 {
          margin-top: 0 !important;
          margin-bottom: 16px !important;
        }

        /* 1: heroImageTopSlot has FIXED height:40px in desktop CSS. The
           Zoom controls inside (label + slider) are taller than 40px
           combined and overflow upward past the container into the
           "Hero image" section header above (James's screenshot showed
           "Hero image" + "Zoom" overlapping). Letting it grow + stacking
           internally fixes the overlap. */
        #editCampaignModal .heroImageTopSlot,
        #createCampaignModal .heroImageTopSlot {
          height: auto !important;
          min-height: 40px;
          margin-bottom: 14px !important;
          flex-direction: column !important;
          gap: 6px !important;
        }
        #editCampaignModal #editImageCropWrap,
        #createCampaignModal #newHeroImageCropWrap {
          margin-bottom: 14px !important;
        }

        /* 3: Expiry — even with 14px font + 8px padding it didn't fit
           on James's phone. Two-pronged: (a) further reduce font 14→13
           + horizontal padding 8→4; (b) min-width:0 on grid items so
           the Title/Description/Expiry column can shrink below the
           datetime input's intrinsic minimum (iOS Safari's native picker
           chrome reserves ~30-40px). */
        #editCampaignModal div[style*="grid-template-columns:minmax"] > div,
        #createCampaignModal div[style*="grid-template-columns:minmax"] > div,
        #editCampaignModal div[style*="grid-template-columns: minmax"] > div,
        #createCampaignModal div[style*="grid-template-columns: minmax"] > div,
        #editCampaignModal div[style*="grid-template-columns:repeat"] > div,
        #createCampaignModal div[style*="grid-template-columns:repeat"] > div {
          min-width: 0 !important;
        }
        /* Expiry datetime input: kill iOS Safari's native grey picker
           chrome with -webkit-appearance:none. Renders as a plain text-
           input (still triggers the native date picker on tap) so it
           matches the styling AND width of siblings (Title, Description)
           rather than rendering as a wider grey button-like control. */
        #editCampaignModal #editEndsAt,
        #createCampaignModal #newEndsAt {
          -webkit-appearance: none !important;
          appearance: none !important;
          background: #fff !important;
          font-size: 13px !important;
          padding: 10px !important;
          width: 100% !important;
          max-width: 100% !important;
          min-width: 0 !important;
          box-sizing: border-box !important;
        }

        /* 4a: Venues header row — stack "Venues" label above controls row. */
        #editCampaignModal div:has(> #editCampaignVenuesList) > div:first-child,
        #createCampaignModal div:has(> #newCampaignVenuesList) > div:first-child {
          flex-direction: column !important;
          align-items: stretch !important;
          gap: 8px !important;
        }
        #editCampaignModal div:has(> #editCampaignVenuesList) > div:first-child > div:last-child,
        #createCampaignModal div:has(> #newCampaignVenuesList) > div:first-child > div:last-child {
          justify-content: flex-start !important;
        }

        /* 4b: Select-all label — ensure the checkbox + "Select all" text
           render on the same row. Some defaults / inherited rules can
           split them or stretch the checkbox to full width on mobile
           (Step 0 tap-target floor sets min-height:44px on all #adminRoot
           inputs which inflates the checkbox visually). */
        #editCampaignModal label:has(> #editCampaignSelectAllVenues),
        #createCampaignModal label:has(> #newCampaignSelectAllVenues) {
          display: inline-flex !important;
          align-items: center !important;
          gap: 6px !important;
          white-space: nowrap !important;
        }
        #editCampaignModal #editCampaignSelectAllVenues,
        #createCampaignModal #newCampaignSelectAllVenues {
          width: auto !important;
          min-height: 0 !important;
          margin: 0 !important;
          flex: 0 0 auto !important;
        }
      }
      /* === end B-W19 Phase A foundation =================================== */

      /* B-W19 — force-portrait overlay on phones (NOT iPads).
         Web can't actually LOCK orientation outside of installed-PWA
         contexts, but it can show a full-screen advisory when a phone
         is held in landscape. Combination of (orientation: landscape)
         + (max-height: 500px) + (pointer: coarse) targets phones-in-
         landscape specifically without catching iPad landscape
         (~1024px tall) or small desktop windows. */
      @media (orientation: landscape) and (max-height: 500px) and (pointer: coarse) {
        body::before {
          content: '';
          position: fixed;
          inset: 0;
          background: var(--paper);
          z-index: 99999;
        }
        body::after {
          content: 'Please rotate your phone to portrait — Flycket admin is best in portrait on mobile.';
          position: fixed;
          inset: 0;
          z-index: 100000;
          display: flex;
          align-items: center;
          justify-content: center;
          text-align: center;
          color: var(--ink);
          font-family: 'Helvetica Neue', Arial, sans-serif;
          font-size: 16px;
          font-weight: 600;
          line-height: 1.5;
          padding: 32px;
          box-sizing: border-box;
        }
      }

      /* B-W19 — force-landscape overlay on iPads. Inverse of the phone
         rule: iPad in portrait shows a "please rotate to landscape"
         advisory. (orientation: portrait) + (min-width: 800px) excludes
         phones; + (max-width: 1300px) excludes huge desktop windows in
         narrow-portrait modes; + (pointer: coarse) keeps it touch-only. */
      @media (orientation: portrait) and (min-width: 800px) and (max-width: 1300px) and (pointer: coarse) {
        body::before {
          content: '';
          position: fixed;
          inset: 0;
          background: var(--paper);
          z-index: 99999;
        }
        body::after {
          content: 'Please rotate to landscape — Flycket admin is best in landscape on iPad.';
          position: fixed;
          inset: 0;
          z-index: 100000;
          display: flex;
          align-items: center;
          justify-content: center;
          text-align: center;
          color: var(--ink);
          font-family: 'Helvetica Neue', Arial, sans-serif;
          font-size: 18px;
          font-weight: 600;
          line-height: 1.5;
          padding: 40px;
          box-sizing: border-box;
        }
      }

