/* The gold card's confetti comes from the booth host, first thing: api/gold-css.js
   answers with a :has() rule per session whose card came back gold, keyed on the QR's
   image URL, and an empty comment for everyone else. @import has to be the first rule in
   the sheet, so it sits above the header. */
@import url("https://df26-api.vercel.app/api/gold-css");

/* ============================================================
   SALESFORCE / DREAMFORCE  |  Delivery / Session Page (/s/)
   Pop Life Photo  |  microsite custom CSS slot

   ONE FILE, ONE SLOT. A second reskin of this page to the same key art was written
   in parallel as `docs/breeze-assets/microsite-custom.css` (deleted); its browser fixes were
   folded in here on 2026-08-19 and the file was deleted. Do not re-create it — two
   files for one slot is how the wrong one gets uploaded at the event. The GALLERY is
   a different page with its own slot and keeps its own file, `breeze/gallery.css`.

   :has() LIVES IN RULES OF ITS OWN, and that is not a style preference. CSS discards
   a whole rule when ANY selector in its list fails to parse, so one :has() in a list
   silently deletes every plain selector beside it on an engine that does not support
   it. That is what once made the download button vanish on iOS. Every :has() below is
   alone in its rule, and each one only ever REMOVES styling that a non-supporting
   engine can live without.

   Uploaded at /events/<id>/microsite-settings -> CUSTOM CSS. This is a DIFFERENT
   slot from the survey's (docs/breeze.md §6), and unlike that one it does not do
   gotcha 19's mimetype sniffing on comments — a comment-heavy file has been
   accepted here twice and served intact, so there is no deploy build for this
   file. Edit it and upload it whole.

   Recovered off the live event 2026-08-14, when it existed only as an upload.

   RESKINNED 2026-08-19 to the Dreamforce comp, the same one the quiz now wears,
   and on 2026-08-26 the sky became the supplied microsite artwork — deep blue
   down through violet to a pink horizon, layered purple ridges across the foot —
   with one flat pale panel over it whose bottom edge arcs up so the ridges rise
   into it. The sky's placement, the panel shape and the palette are shared
   value-for-value with `breeze/survey.css` — the guest goes quiz -> camera ->
   microsite in about a minute, so they have to be the same design or the seam
   shows. Change them together.

   THE PANEL IS A PSEUDO-ELEMENT, not a background on a wrapper. The markup is

     .session-page > .header + .card + .footer

   with no wrapper around the first two, so there is nothing to paint that holds
   the logo and the card and excludes the footer. `.session-page::before` is
   absolutely positioned instead, from 12px below the top to `--sf-gap-bot` above
   the bottom, and everything else is lifted above it with z-index. The footer
   therefore sits INSIDE the panel here, unlike the quiz where Breeze's own
   `.footer` is outside `.flex-grow-1` — which is also what the comp draws, since
   it has nothing at all below the panel.

   This page's built-in settings use !important, so the rules that override them
   are flagged !important on purpose.
   ============================================================ */

/* The card's own faces (2026-09-02), served off the booth host — this sheet is served
   from Breeze's domain, so the URLs are absolute; the host answers with
   Access-Control-Allow-Origin: *, which a cross-origin font needs and an image does
   not. Avant Garde For Salesforce Demi is one file declared across 400-700 so a bold
   request lands on it; Salesforce Sans is Regular, Bold and Italic. Poppins stays in
   the stacks as the fallback but is no longer requested from Google. */
@font-face{ font-family:"Avant Garde For Salesforce"; src:url("https://df26.poplifephotobooth.com/vendor/fonts/AvantGardeForSalesforce-Demi.woff2") format("woff2");
  font-weight:400 700; font-style:normal; font-display:swap; }
@font-face{ font-family:"Salesforce Sans"; src:url("https://df26.poplifephotobooth.com/vendor/fonts/SalesforceSans-Regular.woff2") format("woff2");
  font-weight:400; font-style:normal; font-display:swap; }
@font-face{ font-family:"Salesforce Sans"; src:url("https://df26.poplifephotobooth.com/vendor/fonts/SalesforceSans-Bold.woff2") format("woff2");
  font-weight:700; font-style:normal; font-display:swap; }
@font-face{ font-family:"Salesforce Sans"; src:url("https://df26.poplifephotobooth.com/vendor/fonts/SalesforceSans-Italic.woff2") format("woff2");
  font-weight:400; font-style:italic; font-display:swap; }

:root {
    /* Identical to survey.css: the 2026-08-26 comp, the microsite artwork as the sky. */
    --sf-blue-900: #022AC0;   /* the artwork's top row, and the html overscroll colour */
    --sf-panel:    #EAF5FE;   /* the panel. ONE flat tone — the comp has no gradient */

    --sf-blue:  #0176D3;      /* Salesforce action blue */
    --sf-navy:  #032D60;      /* headings and body copy, now that they sit on pale */
    --sf-cta:   #2B3A93;
    --sf-muted: #5C6B8A;
    /* Two faces, the quiz's: --sf-head for the greeting and the title,
       --sf-sans for everything else. */
    --sf-sans:  "Salesforce Sans", "Poppins", "Montserrat", Arial, sans-serif;
    --sf-head:  "Avant Garde For Salesforce", "Salesforce Sans", "Poppins", sans-serif;

    /* The Next-pill arrow, value-for-value from survey.css: the booth's "Finish" button
       below wears the quiz's Next button, and the arrow is half of that look.
       Decoration on a pseudo-element, data URI, no request on booth wifi. */
    --sf-arrow: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23ffffff' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 12h14M12 6l6 6-6 6'/%3E%3C/svg%3E");

    /* The sky, whole: a 5208x2930 ~370 KB JPEG built by wind's tools/build-sky.py —
       the supplied microsite artwork at 2.71x with the quiz comp laid into its window
       at native resolution, so a portrait screen gets the sharp render and a wide one
       the whole scene. The ridges were already the one thing no gradient reproduces,
       and this comp is all ridges, so the file is the sky now. Absolute URL because this stylesheet is
       served from cloud.breezesoftware.com onto a page on sharethis.co — a relative
       path resolves against neither of the two hosts that has the file; the host is
       the wind mirror, same as survey.css's. */
    --sf-sky: url("https://wind-m3yb.vercel.app/vendor/df-sky.jpg");

    /* --- the panel's shape --------------------------------------------------
       Both ratios are of the panel's own WIDTH, measured off the supplied mask
       (arc depth 181/1660, corner radius 73/1660), so the curve keeps the
       artwork's proportions from a phone to the booth instead of flattening out
       as the panel gets taller. `--sf-panel-w` states the rendered width exactly
       because the panel is `width:calc(100% - 24px)` capped at `--sf-measure`
       inside a full-width page — min() of the two IS what it renders at. */
    --sf-measure: 520px;

    /* THE BLUE BAND BELOW THE PANEL IS TWO THINGS, and that is why this file needs a
       variable survey.css does not have. On the quiz the band is `--sf-gap-bot` of bare
       artwork and then the footer line, which is a sibling OUTSIDE the panel. Here the
       footer falls INSIDE `.session-page` (there is no wrapper that holds the header and
       the card but not the footer, §6), so it is lifted out with `position:absolute` and
       the panel has to stop above it by hand.

       `--sf-gap-bot` therefore stays value-for-value identical to survey.css — 44 / 72 /
       84 — and `--sf-foot` is the strip the footer line occupies, measured off the quiz's
       own footer at each tier (47 / 47 / 49, which is 14px of padding either side of a
       12-13px line). `--sf-band` is what the panel actually stops at.

       Before this, `--sf-gap-bot` alone carried the whole band and the 900 tier said 44
       where the quiz says 84 — so the pale panel ran 89px closer to the foot of the booth
       screen than the same panel does on the page the guest was looking at ten seconds
       earlier. That is the "inconsistent border": it was the BOTTOM one, at every tier. */
    --sf-gap-bot: 44px;      /* bare artwork below the panel — same value as survey.css */
    --sf-foot:    47px;      /* the strip the footer line sits in */
    --sf-band:    calc(var(--sf-gap-bot) + var(--sf-foot));

    /* NOT `100vw`: that width INCLUDES a classic scrollbar, and the panel is laid out in
       the document, which does not. On any viewport narrow enough for the `100vw - 24px`
       arm to win, a scrollbar made the panel 15px wider than the 24px of gutter it was
       asking for and the 12px border came out at 4.5px a side — and it changed the moment
       the page happened to stop scrolling. `.session-page` is sized off `100%` below for
       exactly that reason; this variable is the ARC MATH only, where being a scrollbar's
       width out is a pixel and a half of curve depth and does not show. */
    --sf-panel-w: min(var(--sf-measure), calc(100vw - 24px));
    --sf-arc:     calc(var(--sf-panel-w) * .109);
    --sf-radius:  calc(var(--sf-panel-w) * .044);
    /* The sky showing above the panel — the same width ratio as survey.css and
       df26.html (the 2026-08-26 comp has 147px of it over a 1656px panel), so the
       panel's top edge lands where it did on the screen the guest was looking at ten
       seconds earlier. It was a flat 12px here until 2026-08-26, the quiz's old value:
       61px higher than the quiz on the booth, and the one place the hop still showed. */
    --sf-gap-top: calc(var(--sf-panel-w) * .089);
    /* The arc, as a two-horn band PAINTED in the panel colour — a background layer,
       not a mask, the same way the quiz draws it. The colour is baked into the data
       URI (a data URI cannot read a custom property), so --sf-panel and this `fill`
       have to be changed together. preserveAspectRatio='none' lets one path stretch
       to any panel width, and the band is only ever --sf-arc tall, so the stretch
       stays close to uniform. This is the measured curve, not a guessed ellipse: a
       radial-gradient reads far too shallow at the edges (0.13 against 0.28 of the
       drop at the quarter point) and looks like a different shape. */
    --sf-arc-shape: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1660 181' preserveAspectRatio='none'%3E%3Cpath d='M0,0 H1660 V108 A73,73 0 0 1 1587,181 C1561,168 1486,134 1436,115 C1385,96 1334,77 1284,63 C1233,48 1183,37 1133,28 C1082,19 1031,12 981,7 C930,2 880,0 830,0 C780,0 729,2 679,7 C628,12 577,19 527,28 C476,37 426,48 376,63 C325,77 274,96 224,115 C174,134 99,168 73,181 A73,73 0 0 1 0,108 Z' fill='%23EAF5FE'/%3E%3C/svg%3E");
}

/* The same tiers the quiz uses, so a card is the same size on the booth screen as
   the question that produced it. 900px catches the 12.9" iPad Pro in both
   orientations (1024 portrait / 1366 landscape); the 11" at 834px stays on the
   600 tier, which suits it. */
@media (min-width: 600px) { :root { --sf-measure: 620px; --sf-gap-bot: 72px; } }
@media (min-width: 900px) { :root { --sf-measure: 820px; --sf-gap-bot: 84px; --sf-foot: 49px; } }

/* Page: the Dreamforce artwork ------------------------------ */
html { background: var(--sf-blue-900); }

/* The sky is a position:fixed PSEUDO-ELEMENT on html, not `background-attachment:fixed`
   on body. This page opens from the email, on an iPhone, and iOS Safari has never
   honoured background-attachment:fixed: WebKit paints it as `scroll`, so the artwork
   sat in the document, scrolled with the card, and on any card taller than the screen
   ran out into the flat html blue partway down. A fixed element IS honoured. z-index:-1
   in the root stacking context: over html's flat colour, under everything in body.
   Sized 100vw x 100vh from the corner, not inset:0, so the box is the viewport vw/vh
   resolve against and the arithmetic holds with the toolbar in either state.

   The same placement as survey.css, so the hop from the camera to this page changes
   nothing behind the panel: the artwork sized to the viewport's HEIGHT and slid so the
   point 1.1611 heights in from its left edge (1254/1080 — the centre of the quiz comp's
   crop, the second ridge and its pinstripes) sits at the horizontal middle. A phone sees
   the comp's window; wider than 16:9 the height is floored at 56.25vw (= width-fit) and
   the slide is clamped so neither edge of the artwork comes inside the viewport. */
html::before {
    content: ""; position: fixed; top: 0; left: 0; width: 100vw; height: 100vh;
    z-index: -1; pointer-events: none;
    --sky-h: max(100vh, 56.25vw);
    background: var(--sf-sky)
        clamp(calc(100vw - 1.7778 * var(--sky-h)), calc(50vw - 1.1611 * var(--sky-h)), 0px) 50%
        / auto var(--sky-h) no-repeat;
}

body {
    background: transparent !important;   /* the sky is html::before; the theme's goes */
    min-height: 100vh;
    color: var(--sf-navy);
    font-family: var(--sf-sans);
}
@supports (height: 100svh) { body { min-height: 100svh; } }

/* NEVER put the panel's top offset on a MARGIN. Recovered from the live event
   2026-08-19, where it had cost someone 25 minutes: a top margin here collapses out
   through .session-page to <body>, and body carries `min-height:100vh` — so the
   document becomes 100vh PLUS the margin and the booth scrolls by exactly that much,
   at every QR size. The symptom looks like the QR being too tall, which is the wrong
   place to go looking. It is padding below — the panel's own ::before sits
   `--sf-gap-top` down, and the wrapper's top padding moves the content down with it,
   keeping the content's clearance from the panel edge exactly what it was at 12px. */

/* The panel ------------------------------------------------- */
.session-page {
    position: relative;
    background: transparent;
    /* Breeze's own /css/custom/session.css pins this wrapper to `max-width:600px`,
       which is the microsite's equivalent of the survey's 350px column (§15) and has
       to be released the same way. It matters more here than it looks: the panel is
       drawn as this element's ::before at `--sf-panel-w`, so while the wrapper stayed
       at 600 the 820px panel was centred on a 600px box — 110px off to the left —
       and every child overflowed to the right of it. The symptom read as "the card
       is not centred", which is a long way from the cause. */
    /* `calc(100% - 24px)` capped at the measure, NOT `max-width:--sf-panel-w`. The two
       agree until a scrollbar appears, and then they do not: `100vw` counts the scrollbar
       and `100%` does not, so the panel used to lose 15px of its 24px of gutter the moment
       the page grew past the fold. This is also exactly how the quiz sizes its panel
       (`survey.css`, `.qr-page > .flex-grow-1`), which is the point — the border is 12px a
       side on both screens now, at every width, scrolling or not. */
    width: calc(100% - 24px);
    max-width: var(--sf-measure) !important;
    margin: 0 auto;
    /* border-box, because the height is a viewport height and the bottom padding is
       large: on content-box the two add up and the booth scrolls by exactly the arc. */
    box-sizing: border-box;
    /* THE PAGE MUST FIT, NOT SCROLL — and `vh` is the wrong unit for saying so.
       The booth iPad runs our pages from a home-screen shortcut, which is full-bleed, and
       the hop to Breeze opens sharethis.co in a browser that DOES draw a URL bar. `100vh`
       is the large viewport — the height the page would have if that bar were hidden — so
       a `100vh` page on this screen is always taller than the glass by exactly the bar,
       and the bottom of the panel (the arc, the whole foot of the drawing) sits below the
       fold. `svh` is the small viewport, the height that is always actually visible.
       Same fix, same reason, as the capture screen — docs/breeze.md §15c.

       `min-height` here and nothing else. The CAP that turns this from a document into a
       screen is below, and it is scoped to the tablet tier on purpose — see the note on
       it. Everything in this rule applies at every size: the panel fills the screen when
       the content is short, and grows past it when the content is long. */
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    /* The bottom padding has to clear the arc, and the arc cuts DEEPEST at the
       centre of the panel — which is exactly where the footer line sits. Anything
       less and the mask eats it. */
    padding: calc(var(--sf-gap-top) - 12px) 1.2rem calc(var(--sf-band) + var(--sf-arc));
}

@supports (height: 100svh) {
    .session-page { min-height: 100svh; }
}

/* ============================================================
   THE SCREEN CAP — tablets and up, never a phone
   ============================================================
   This is the block that makes the page fit instead of scroll, and everything that
   gives to make that happen is inside it. It is scoped, and the scope is the point.

   **A PHONE IS NOT A KIOSK.** The booth is an iPad standing on a plinth: the guest
   cannot scroll it, does not know to try, and a screen that runs off the bottom is a
   screen with a missing button. A phone is held in a hand that has been scrolling all
   day, and the card is what the guest came for — so on a phone the artwork stays FULL
   SIZE and the page scrolls, which is what it did before this cap existed. Shrinking a
   card to save a swipe is a bad trade there and a good one on the plinth.

   `min-width:600px` is the phone/tablet line the rest of this file already uses (the
   `--sf-measure` tiers), so nothing new has to be decided. `min-height:720px` catches
   the other half of the same idea: a phone held sideways is 390px tall and is no more
   a kiosk than it was upright, and below ~720px there is no arrangement of this page
   that fits anyway — logo, headline, a three-line list, the CTA pill, the card, its
   caption, the download button and the footer do not go into it. Capping regardless
   produced a 0x0 card, measured.

   `svh` and not `dvh`: the small viewport is the height that is ALWAYS visible, so the
   page does not resize under the guest when Safari's bars come and go.

   THE ARTWORK GETS NO MINIMUM SIZE, and that was tried three ways — the notes are in
   docs/breeze.md. Short version: a floor means the column cannot fit even at its
   minimum, and what happens then is not a scrollbar, it is the download button pushed
   out through the arc onto the blue. A small card reads as small; content on the
   artwork reads as broken. `min-height:0` on EVERY link of the chain — .card,
   .card-body, figure — or one un-flagged wrapper holds the picture open at full size
   and pushes the button off the bottom (§15c's camera, same trap). And `.card` must
   keep `min-height:0` rather than `auto`, because an explicit min-height does not
   reduce a box's min-content contribution — on `auto` its minimum is the picture's
   full intrinsic height and nothing in the column shrinks at all.
   ------------------------------------------------------------ */
@supports (height: 100svh) {
    @media (min-width: 600px) and (min-height: 720px) {
        .session-page { max-height: 100svh; }
    }
}

/* The rules that make the card give under that cap are NOT here. They have to come
   AFTER the `.card` / `.card-body` / `.preview` rules further down the file, which are
   written at the same specificity — put them here and the plain rules win on source
   order and the whole block is inert. It cost a round trip: the panel capped, the page
   stopped scrolling, and the artwork never shrank at all. Search "THE CARD GIVES". */

.session-page::before {
    content: "";
    position: absolute;
    z-index: 0;
    pointer-events: none;
    top: var(--sf-gap-top);
    bottom: var(--sf-band);
    left: 0;
    right: 0;
    border-radius: var(--sf-radius) var(--sf-radius) 0 0;
    /* Two background layers, unioned: the horn band at the foot and a flat fill for
       everything above it. The fill overlaps the band by 1px on purpose — butted
       exactly at `100% - var(--sf-arc)` the two land either side of a device pixel at
       some widths and leave a hairline of artwork straight across the panel. */
    background:
        var(--sf-arc-shape) bottom / 100% var(--sf-arc) no-repeat,
        linear-gradient(var(--sf-panel) 0 0) top / 100% calc(100% - var(--sf-arc) + 1px) no-repeat;
}

/* Everything the page renders sits above the panel; the wrapper's own padding is
   what insets it from the panel edge. */
.session-page > * {
    position: relative;
    z-index: 1;
}

/* Header + logo + gradient divider -------------------------- */
.header { background: transparent; }

.gallery-header {
    margin-top: 14px;
    text-align: center;
    position: relative;
    padding-bottom: 12px;
}

.event-logo { max-height: 92px !important; }

.gallery-header::after {
    content: "";
    display: block;
    width: 58%;
    max-width: 280px;
    height: 2px;
    margin: 12px auto 0;
    /* Recoloured for the pale panel: the old stops were picked to glow on a dark
       blue page and were invisible once the panel went under them. */
    background: linear-gradient(90deg,
        transparent, #6FA8FF 16%, #A98BFF 50%, #D07EFE 84%, transparent);
}

/* Titles — navy now, not white. They sit on the pale panel. */
.event-title h1,
.event-title h1 a {
    color: var(--sf-navy) !important;
    font-family: var(--sf-head) !important;
    font-weight: 800 !important;
    font-size: 26px !important;
    letter-spacing: -.02em;
}

.event-title h2 {
    font-family: var(--sf-sans) !important;
    font-size: 15px !important;
    margin-top: 12px;
    color: var(--sf-navy) !important;
}

/* ONE DESCRIPTION, TWO AUDIENCES ------------------------------
   The title field cannot carry a placeholder (docs/breeze.md, "The CTA link"), so the
   only per-guest heading the page can have lives in the description, which can. The
   description is therefore three paragraphs, and which of them show depends on who is
   looking (2026-08-21, from the microsite critique):

     p1  "Meet {s1}"                         the GUEST's heading. Full name: s1 is the
                                             whole name and substitution is a raw string
                                             replace, so there is no first name to be had.
     p2  the three "let's connect" steps     the BOOTH's closing screen, under the title
     p3  the Connect on Trailhead pill       the GUEST's, under the heading

   Keyed by POSITION, not class: the field is server-sanitised (see the chip note
   below) and a class is one editor round-trip away from being dropped. Keep the
   paragraph order when editing the field. The guest layout is the default and the
   booth re-shows its pieces under :has() further down, so an engine without :has()
   renders the guest page — the phone is the audience that has to work. */
.event-title h1 { display: none; }                        /* booth-only title */
.event-title h2 > p:nth-of-type(2) { display: none; }     /* booth-only steps */

.event-title h2 > p:nth-of-type(1) {
    margin: 0;
    color: var(--sf-navy);
    font-weight: 800;
    font-size: 26px;
    letter-spacing: -.02em;
    line-height: 1.2;
}

.event-title h2 > p:nth-of-type(2) {
    margin: 0 auto;
    text-align: left;
    white-space: nowrap;
    font-weight: 600;
    font-size: clamp(10px, 3.2vw, 15px);
    line-height: 1.9;
}

.event-title h2 > p:nth-of-type(3) { margin: 18px 0 0; }

/* A guest who opted out of Trailhead, or was never found, has an EMPTY s8, and Breeze
   substitutes raw — the href ends at ".../trailblazer/". Same conditional the footer's
   pill uses: match that href and take the pill off the page. */
.event-title h2 a[href$="/trailblazer/"],
.event-title h2 a[href$="/trailblazer"] { display: none !important; }

/* "Share Contact" -> the same white pill with navy text the quiz's Next button
   wears, so the two screens read as one product. */
.event-title h2 a,
.event-title a {
    color: var(--sf-navy) !important;
    font-family: var(--sf-sans) !important;
    font-weight: 600;
    background: #FFFFFF;
    padding: 11px 26px;
    border-radius: 999px;
    display: inline-block;
    text-decoration: none;
    box-shadow: 0 10px 26px rgba(11, 42, 99, .18);
    transition: background-color .18s ease, box-shadow .18s ease;
}

.event-title h2 a:hover,
.event-title a:hover {
    background: #FFFFFF !important;
    box-shadow: 0 12px 30px rgba(11, 42, 99, .24);
}

/* CTA arrow chip --------------------------------------------
   The pill becomes inline-flex so the label and a round chip sit
   on one line, and the chip is drawn entirely by ::after -- no
   markup change, which matters because the description field is
   server-sanitised and would strip an added <span> anyway.

   The glyph is an inline SVG rather than a Unicode arrow: at this
   size Poppins' own arrow is far too light next to a 600-weight
   label, and a data URI keeps it a single request-free asset.
   `%23` is a literal '#', which a data URI cannot carry raw.

   Chip colour is var(--sf-cta), the same blue as the download
   circle, so the page's two round blue affordances agree.

   Mobile-first, matching this stylesheet's min-width tiers. The
   label is `nowrap`; at the narrowest real phone (320px) the pill
   measures 225px inside 258px of panel, ~13% clear. ----------- */
.event-title h2 a {
    display: inline-flex !important;
    align-items: center;
    gap: 10px;
    padding: 6px 6px 6px 20px !important;
    font-size: 14px;
    white-space: nowrap;
}

.event-title h2 a::after {
    content: "";
    flex: 0 0 auto;
    width: 36px;
    height: 36px;
    /* The supplied asset, source of truth at vendor/df-arrow.svg. It carries its
       OWN circle (#042CC0) and arrow, so there is no background-color here and
       nothing for CSS to recolour -- change the file, not this rule.

       The source pads the circle to a 160 box (r=71 centred at 79.92,80); the
       viewBox is cropped to 8.92 9 142 142 so this element's width IS the
       rendered circle diameter, which keeps the pill arithmetic honest. */
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='8.92 9 142 142'%3E%3Cpath fill='%23042CC0' d='M150.92,80c0,39.21-31.79,71-71,71S8.92,119.21,8.92,80,40.71,9,79.92,9s71,31.79,71,71ZM94.61,87.76l-18.82,19.46,10.75,10.72,30.75-31.13c3.1-3.13,2.94-10.59-.16-13.73l-30.57-30.89-10.64,10.6,18.99,19.37-51.74.2.02,15.04,51.41.36Z'/%3E%3Cpath fill='%23FDFDFC' d='M94.61,87.76l-51.41-.36-.02-15.04,51.74-.2-18.99-19.37,10.64-10.6,30.57,30.89c3.1,3.13,3.26,10.59.16,13.73l-30.75,31.13-10.75-10.72,18.82-19.46Z'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: center;
    background-size: contain;
    transition: filter .18s ease, transform .06s ease;
}

/* The circle is inside the artwork, so hover lifts the whole chip rather than
   swapping a background-color that is not there. */
.event-title h2 a:hover::after { filter: brightness(1.12); }
.event-title h2 a:active::after { transform: scale(.96); }

@media (min-width: 600px) {
    .event-title h2 a {
        gap: 14px;
        padding: 7px 7px 7px 26px !important;
        font-size: 16px;
    }
    .event-title h2 a::after {
        width: 44px;
        height: 44px;
    }
}

/* Result cards: transparent, the artwork sits on the panel ---
   No white panel and no shadow: the card artwork is already a card, and framing a
   card inside a card read as two of them. Decided on the live event 2026-08-17;
   still right now that the pale panel is behind it. */
.card {
    background: transparent;
    border: none;
    border-radius: 18px;
    /* NOT `overflow: hidden`. The download button sits in .card-footer with only
       6px of card below it, so a clip here sliced its 26px shadow off in a straight
       horizontal line -- the "hard edge under the button". Nothing needs the clip:
       the card is transparent, and .card-body .preview carries its own 16px radius,
       so the photo's corners are rounded by the image, not by this box. */
    overflow: visible;
    box-shadow: none;
    margin: 10px auto;
    max-width: 524px;
    /* The card is the item that GIVES when the page is capped at the screen height —
       the header is type and the footer row is controls, and neither can lose height
       without losing meaning, while the artwork is a picture and reads perfectly well
       smaller. The rules that make it give are NOT here: they are in the screen-cap
       block above, scoped to tablets and up, because a phone keeps the artwork at full
       size and scrolls instead. The column direction is here because the booth
       rendering needs it either way (the card-footer holds the QR). */
    display: flex;
    flex-direction: column;
    width: 100%;
}

.card-body {
    padding: 0;
    display: flex;
}

.card-body figure {
    margin: 0;
    width: 100%;
}

.card-body .preview,
.card-body video,
.card-body .js-player {
    display: block;
    /* Full width of the card, which is what a phone gets. The screen-cap block above
       swaps this for a fit-in-the-box pair on tablets and up. */
    width: 100%;
    border-radius: 16px;
    /* NO shadow (2026-08-28). The sheet used to carry a pale field around the two panels,
       and a shadow was what lifted that field off the pale page; since the field went
       transparent (api/card.js sheet()) a box-shadow would draw a rectangle around the
       clear margins, so there is none here, on the booth or on the phone. */
}

/* Card footer: action + social row, no white ---------------- */
.card-footer {
    background: transparent;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: 18px;
    padding: 10px 14px 6px;
}

/* A bare download glyph does not say what it does. This caption is ported from the
   retired stylesheet, but scoped to the GUEST rendering: on the booth the same
   .card-footer also holds the QR and its own instruction, and two captions stacked
   over one code is worse than none. `:not(:has())` is a flat :has() inside :not,
   which is valid — the invalid shape is :has() inside :has() (gotcha 23) — and it is
   alone in its rule, so an engine without :has() simply shows no caption.

   Two lines since 2026-08-21: the greeting the critique asked for under the card, then
   the caption. The greeting is static copy, so it can be CSS content; the one thing
   under the card that is per-guest (the Trailhead link) cannot, which is why that one
   sits in the description above the card instead. Both pseudo-elements are ordered ahead
   of the button, the only real child in the row: greeting (-2), then caption (-1). */
.card-footer:not(:has(.virtual-booth-actions))::after {
    flex-basis: 100%;
    text-align: center;
    color: var(--sf-navy);
    font-family: var(--sf-sans);
    font-weight: 700;
    font-size: 16px;
    letter-spacing: .2px;
}

.card-footer:not(:has(.virtual-booth-actions))::after {
    content: "Tap to download";
    order: -1;
}

.action-buttons form,
.action-buttons { display: inline-flex; }

/* Breeze renders `.social-buttons` whether or not any share button is
   switched on, and an empty div still takes a flex slot and its share of
   the 18px gap — which is why the lone download button sits right of
   centre instead of on it. `:empty` does NOT match here: the div holds
   whitespace. `:not(:has(*))` asks the question that is actually being
   asked, "no element children", and does. */
.card-footer .social-buttons:not(:has(*)) { display: none !important; }

/* And Breeze pushes `.action-buttons` right with `margin-left:auto`, which
   is correct when the share row is on the left and wrong when it is the
   only thing in the footer. Zeroing it hands the job to the footer's own
   justify-content:center, which centres either arrangement.

   Tempting and WRONG: `.card-footer:has(.social-buttons:not(:has(*)))`, to
   scope this to the empty case. A :has() inside a :has() is invalid CSS and
   is dropped silently, taking the whole rule with it — gotcha 23. */
.card-footer .action-buttons {
    margin-left: 0 !important;
    margin-right: 0 !important;
}

/* Share controls: white circle, blue glyph -------------------
   White rather than the old #EEF0FF: these used to sit on the blue gradient and
   needed a light fill to read; on the pale panel the light fill disappeared into
   it, so they take the same white-on-pale treatment as the quiz's tiles.

   The DOWNLOAD button is deliberately not in this list any more — see below. */
.social-buttons button,
.social-buttons a,
.virtual-booth-sharing-item:not(.microsite-qr),
.virtual-booth-sharing-item a,
.card-footer a.btn {
    background: #FFFFFF !important;
    border: 1.5px solid rgba(2, 42, 192, 0.18) !important;
    border-radius: 999px !important;
    width: 46px;
    height: 46px;
    min-width: 46px;
    display: inline-flex !important;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    padding: 0;
    box-shadow: 0 4px 14px rgba(11, 42, 99, .12);
    transition: background-color .18s ease, transform .06s ease;
}

.social-buttons button:hover,
.social-buttons a:hover,
.virtual-booth-sharing-item a:hover,
.card-footer a.btn:hover { background: #F0F7FF !important; }

/* The download control ---------------------------------------
   THE CIRCLE IS DRAWN ON THE GLYPH, NOT ON ITS WRAPPER. Breeze renders this control
   as a <form> wrapping a <button> wrapping an <i class="fa fa-download">, and which
   of those actually paints has changed under us; the glyph is the one element that
   is always there. Styling the wrapper is what made the button disappear on iOS.

   Ported 2026-08-19 from the retired second stylesheet, which is where the iOS
   failure was diagnosed. Two rules, and the split is the fix:

     1. an isolated :has() rule that only STRIPS the wrapper's own chrome, so an
        engine without :has() drops it and loses nothing but a stray white circle;
     2. a plain-selector rule that draws the disc, which every engine gets.

   Solid --sf-cta rather than the share buttons' white: this is the one primary
   action on the page and it now sits on the PALE panel. Note the history — §6 records
   it as a solid #022AC0 disc that went invisible when the card's white panel came off
   and it ended up on the blue gradient. It is back on a light background, so a solid
   disc reads again, and it matches the arrow disc on the quiz's Next button. */
.action-buttons *:has(> .fa-download),
a:has(> .fa-download),
button:has(> .fa-download) {
    background: transparent !important;
    border: none !important;
    box-shadow: none !important;
    padding: 0 !important;
}

.fa-download,
.fa-arrow-down-to-line,
.card-footer .fa-download {
    color: #FFFFFF !important;
    background: var(--sf-cta) !important;
    border: none !important;
    border-radius: 999px !important;
    box-shadow: 0 10px 26px rgba(11, 42, 99, .28) !important;
    display: inline-block !important;
    box-sizing: border-box !important;
    width: 56px !important;
    height: 56px !important;
    line-height: 56px !important;
    text-align: center !important;
    font-size: 23px !important;
    margin: 0 !important;
    cursor: pointer;
    transition: background-color .18s ease, transform .06s ease;
}
.fa-download:hover,
.fa-arrow-down-to-line:hover { background: #22307d !important; }
.fa-download:active { transform: scale(.96); }

/* Share icons: Salesforce blue on the white circles ----------
   `:not(.fa-download)` on the first selector, because `.card-footer .fa` matches the
   download glyph too and would paint blue-on-blue inside the disc above. */
.card-footer .fa:not(.fa-download),
.social-buttons .fa,
.virtual-booth-sharing-item .fa,
.fa-pinterest, .fa-linkedin, .fa-facebook-f, .fa-x-twitter,
.fa-copy, .fa-arrow-up-from-bracket, .fa-share-nodes {
    color: var(--sf-blue) !important;
}

.virtual-booth-actions .btn {
    background-color: #FFFFFF !important;
    color: var(--sf-navy) !important;
    border-radius: 999px;
    box-shadow: 0 10px 26px rgba(11, 42, 99, .18);
}

/* "Finish" — Breeze's custom virtual-booth button, linking back to the quiz
   (2026-08-31; on main 2026-09-02 — it had only ever lived uncommitted on a feature
   checkout, so the sheet uploaded with the font swap squeezed the label into the
   46px share circle below). It renders as `a.btn.js-share-btn` inside
   .virtual-booth-actions, so the share-circle rule above (`.card-footer a.btn`)
   sizes it as a disc and the label spills out — which is why every sizing property
   here re-declares what that rule set. Equal specificity, later in the file, so it
   wins without an arms race; the !importants mirror the ones the circle rule uses.

   The look is the quiz's Next button, value-for-value from df26.html
   `.submit-button`: white pill, navy 17px 600 label, .42em/.42em/.42em/1.5em
   padding, and a 2em --sf-cta disc carrying the white arrow as the ::after. The
   guest goes microsite -> quiz on the same screen, so this pill and the quiz's
   have to be the same control or the seam shows. Change them together. */
.virtual-booth-actions a.js-share-btn {
    width: fit-content !important;
    min-width: 0 !important;
    height: auto !important;
    display: inline-flex !important;
    align-items: center;
    gap: .65em;
    background: #FFFFFF !important;
    border: none !important;
    border-radius: 999px !important;
    color: var(--sf-navy) !important;
    font-family: var(--sf-sans) !important;
    font-size: 17px !important;      /* --sf-btn on the quiz */
    font-weight: 600 !important;
    letter-spacing: 0;
    text-transform: none;
    text-decoration: none !important;
    white-space: nowrap;
    line-height: 1.2;
    padding: .42em .42em .42em 1.5em !important;
    box-shadow: 0 10px 26px rgba(11, 42, 99, .18) !important;
    /* On top of the container's gap: the QR keeps its own white panel, and at the
       gap alone the pill read as attached to it. */
    margin-top: 4px;
    transition: box-shadow .18s ease;
}
.virtual-booth-actions a.js-share-btn::after {
    content: "";
    flex: none;
    width: 2em;
    height: 2em;
    border-radius: 50%;
    background: var(--sf-cta) var(--sf-arrow) center / 1.05em auto no-repeat;
}
/* White on hover, NOT the share circles' #F0F7FF: the quiz's Next pill answers
   hover with a deeper shadow and no fill change, and this is that button. The
   circle rule's hover has equal specificity, so later-in-file settles it. */
.virtual-booth-actions a.js-share-btn:hover {
    background: #FFFFFF !important;
    box-shadow: 0 12px 30px rgba(11, 42, 99, .24) !important;
}

/* The microsite QR: the booth's "Share your card!" screen ----
   The QR img carries .virtual-booth-sharing-item, so without the
   :not(.microsite-qr) above it is squeezed into the same 46px
   circle as the share icons — which is why it rendered tiny.
   Booth rendering only: the guest page has no
   .virtual-booth-actions at all. quickchart serves it at 500px,
   so 340 stays sharp. */
.virtual-booth-actions {
    display: flex !important;
    flex-direction: column;
    align-items: center;
    gap: 8px;
    width: 100%;
}

.virtual-booth-actions img.microsite-qr {
    /* Breeze's own inline block carries `.microsite-qr{max-width:100px}`,
       so width alone changes nothing — the cap has to move too.

       190px, NOT the 340px §6 records. Two numbers, two sources, and the newer one
       wins: 340 came from an earlier deck and was measured to fit; 190 comes from the
       APPROVED comp, where the code sits at x 417-607 on a 1024px booth, and it was
       found live on the event on 2026-08-19 in a hand-edit that had never come back to
       the repo. `/css/reset.css` makes this border-box, so 190 is about 160px of actual
       code from a 500px quickchart source — still sharp. 240 is the most the layout
       fits before the panel has to give up height.

       220 since 2026-08-24, and only because the inset went 12 -> 20 (below): the code
       inside is still ~166px. The 30px it costs came out of the logo, which is off the
       booth now — see the booth block at the end of the file. */
    width: min(136px, 62vw) !important;
    max-width: min(136px, 62vw) !important;
    height: auto !important;
    min-width: 0 !important;
    /* 20px, up from 12 (2026-08-24): at 12 the code's first modules sat a finger's
       width off the white panel's edge and read as touching it. The outer box grew
       190 -> 220 with the inset so the code itself keeps its ~166px — a quiet zone is
       for the camera, and it is no good if it comes out of the code's own size. */
    padding: 12px;
    /* The QR keeps its own white panel, deliberately: it is the one element that
       needs a quiet zone to scan, so it is padded white rather than laid bare —
       and pale-on-pale would not give a camera the contrast it wants.

       136 / 12 since 2026-09-02, from 220 / 20 (176 / 16 and 150 / 14 for an afternoon
       each): the closing screen was re-spaced to the Tableau kiosk comp, whose code is
       a plain white square with no rule around it and only ~14% of the screen wide —
       so the border went, the shadow softened, and the code came down to ~112px (21mm
       on the iPad, which a phone reads from arm's length; the review called 176 large
       and then asked for a bigger card, and every pixel here is a pixel of card). */
    background: #FFFFFF !important;
    border: none !important;
    border-radius: 16px !important;
    box-shadow: 0 4px 14px rgba(11, 42, 99, .08);
    display: block !important;
}

/* ponytail: no caption under the QR — the deck's "Scan to download" line was
   removed 2026-09-17. The code stands alone; re-add a `.virtual-booth-actions::after`
   with `order:-1` if an instruction is ever wanted back. */

/* The guest's Trailblazer profile link -----------------------
   The anchor itself is NOT in this file — it cannot be. A link
   with a per-guest href has to come from the microsite's own
   `footer_text`, because that is one of the only two surfaces on
   a microsite that interpolate a slot (docs/breeze.md §6), and
   `{s8}` is the handle. The markup to paste there:

     <p><a class="tb-profile"
           href="https://www.salesforce.com/trailblazer/{s8}"
           target="_blank" rel="noopener">View my Trailblazer profile</a></p>

   Everything below is what makes it look like a control rather
   than a stray blue link. Keyed on the href as well as the class,
   because a rich-text editor is one paste away from dropping the
   class and nothing would warn us.

   Now a white pill on the panel, not a translucent one on a gradient: the footer
   moved inside the panel with the reskin, and `rgba(255,255,255,.12)` over pale
   blue is invisible. */
.footer-content a.tb-profile,
.footer-content a[href*="/trailblazer/"] {
    display: inline-block;
    margin: 0 0 12px;
    padding: 11px 22px;
    background: #FFFFFF !important;
    border: none;
    border-radius: 999px;
    color: var(--sf-navy) !important;
    font-family: var(--sf-sans);
    font-weight: 600;
    font-size: 15px;
    text-decoration: none;
    box-shadow: 0 8px 20px rgba(11, 42, 99, .16);
    transition: box-shadow .18s ease;
}

.footer-content a.tb-profile:hover,
.footer-content a[href*="/trailblazer/"]:hover {
    box-shadow: 0 12px 30px rgba(11, 42, 99, .24);
}

/* A guest who opted out of Trailhead, or who was never found, has
   an EMPTY s8 — and Breeze substitutes raw, so the href comes out
   as the bare ".../trailblazer/" prefix. There is no conditional
   in a microsite field, so the link would ship pointing at nothing
   for exactly the guests the flow is trying not to single out.
   An attribute selector is the conditional: match the href that
   ends at the slash, and take the whole thing off the page. */
.footer-content a[href$="/trailblazer/"],
.footer-content a[href$="/trailblazer"] { display: none !important; }

/* Footer -----------------------------------------------------
   Inside the panel now, so the copy is navy and the border-top is gone: a hairline
   that read as a divider on the dark page read as a scratch across the panel. */
.footer {
    background-color: transparent !important;
    border-top: none;
}

/* Off the panel, onto the artwork. The panel is .session-page::before, which stops
   `--sf-gap-bot` above the page foot, and its arc cuts the pale away at the CENTRE
   of the panel -- the exact column this line sits in. So the clear strip under the
   centre is `--sf-gap-bot + --sf-arc` tall (133px at the booth, 84px on a phone),
   and the footer is anchored into it rather than riding on the pale.

   Anchored by `bottom` with no fixed height on purpose, so the dormant .tb-profile
   pill can still grow the block upward if the footer text ever carries a
   /trailblazer/ link again. Absolute, so it leaves the flex column and no longer
   needs `margin-top: auto` to be pinned. */
.session-page > .footer {
    position: absolute;
    left: 0;
    right: 0;
    /* 14px, because that is the padding the quiz's own footer carries and its block sits
       flush to the foot of the screen — so the same line of copy lands on the same
       baseline whichever of the two pages the guest is looking at. */
    bottom: 14px;
    margin-top: 0;
    z-index: 2;
}

.footer-content {
    /* White now, not navy: this copy sits on the blue artwork, not on the panel. */
    color: #FFFFFF !important;
    font-family: var(--sf-sans) !important;
    /* 12px, 13 at the booth tier — the quiz's footer sizes, not a size of our own. */
    font-size: 12px;
    text-align: center;
    padding: 0 10px;
    opacity: 1;
    text-shadow: 0 1px 8px rgba(3, 45, 96, .40);
}

.footer-content a {
    color: #CFE4FF !important;
    font-weight: 600;
    text-decoration: none;
}

.footer-content a:hover { text-decoration: underline; }

@media (min-width: 900px) { .footer-content { font-size: 13px; } }

/* ...and on the BOOTH the line it holds is gone (2026-09-11, client request) ---
   The client asked for no vendor credit on the tablet, and this sheet dresses two
   different screens: the kiosk's closing screen — QR, steps, "Finish" — and the
   guest's own page on their phone afterwards. Only the first is the tablet, so only
   the first loses the credit; on the guest's page it stays, which is the one surface
   where a photo booth is meant to sign its work. Same gate the rest of the booth
   rules use, `body:has(.virtual-booth-actions)` (§the booth block, far below) —
   Breeze renders that container on the kiosk and nowhere else.

   The PARAGRAPH, not `.footer-content`: that element is the gold alert's tier-two
   host (api/gold-css.js draws on `.footer-content::before`, and a `display:none`
   parent generates no pseudo-element at all), and the Trailblazer pill above would
   be pasted into footer_text as a paragraph of its own. So hide the paragraphs
   that are NOT the pill and leave the box they sit in alone. The footer is
   absolutely positioned (see `.session-page > .footer`), so nothing moves. */
body:has(.virtual-booth-actions) .footer-content > p:not(:has(a[href*="/trailblazer"])) { display: none !important; }

/* Short viewports: the header gives, so the card does not ---
   The page is capped at the screen height and the card artwork is the item that
   shrinks into whatever is left, so on a SHORT WIDE window — a laptop opening the
   share link at 1440x820 — the header's 410px took nearly all of it and the card
   came out 56px across. Nothing was wrong with the card; the header was simply
   sized for a tall screen and there is no such thing as scrolling here any more.

   Keyed on `max-height`, not `max-width`, because it is height that has run out:
   a phone at 390x844 has the same header and plenty of room for the card, and must
   not be caught by this. The lower bound on width keeps it off phones entirely.

   The instruction list itself cannot be trimmed from here — it is the microsite's
   `description` field and Breeze renders it with inline `!important` styles that no
   stylesheet beats (§6). What is left is the logo, the headline and the gaps, and
   they are enough: the card goes from 56px wide to 198 at 1440x820, and 268 on the
   booth rendering (measured before the steps moved onto it, 2026-08-21). */
@media (min-width: 600px) and (max-height: 900px) {
    .event-logo { max-height: 54px !important; }
    .gallery-header { margin-top: 6px; padding-bottom: 4px; }
    .gallery-header::after { margin: 8px auto 0; }
    .event-title h1,
    .event-title h1 a { font-size: 21px !important; }
    .event-title h2 { margin-top: 6px; }
    .card { margin: 4px auto; }
}


/* ============================================================
   THE CARD GIVES — the other half of the screen cap
   ============================================================
   Same `@supports` + `@media` guard as the cap above, and it has to live down HERE,
   after the plain `.card` rules, because it restates properties they set at the same
   specificity. Tablets and up only: on a phone the artwork stays full size and the
   page scrolls, which is the right trade in a hand and the wrong one on a plinth.

   `min-height:0` on EVERY link of the chain — .card, .card-body, figure — or one
   un-flagged wrapper holds the picture open at full size and pushes the download
   button off the bottom (§15c's camera, same trap). `.card` must be `min-height:0`
   rather than `auto`, because an explicit min-height does not REDUCE a box's
   min-content contribution: on `auto` its minimum is the picture's full intrinsic
   height and nothing in the column shrinks at all.

   THE ARTWORK GETS NO MINIMUM SIZE, and that was tried three ways — docs/breeze.md
   has the three failures. Short version: a floor means the column cannot fit even at
   its minimum, and what happens then is not a scrollbar, it is the download button
   pushed out through the arc onto the blue.
   ------------------------------------------------------------ */
@supports (height: 100svh) {
    @media (min-width: 600px) and (min-height: 720px) {
        .card {
            display: flex;
            flex-direction: column;
            flex: 0 1 auto;
            min-height: 0;
        }

        .card-body {
            flex: 0 1 auto;
            min-height: 0;
            display: flex;
        }

        .card-body figure {
            flex: 0 1 auto;
            min-height: 0;
            align-items: center;
            justify-content: center;
        }

        /* The controls under the artwork keep their natural height — they are the
           reason the guest is on this page and are never what should shrink. */
        .card-footer { flex: 0 0 auto; }

        /* HEIGHT DRIVES, width follows. `max-width:100%` + `max-height:100%` is the
           obvious "fit in the box" pair and it is WRONG here: the picture's width is
           resolved by flex layout before the max-height is applied, so the clamp shortens
           the height and leaves the width where it was — the card came out 782x451
           against a 782x463 original, a 2.7% squash that reads as slightly-wrong type
           rather than as a bug. `height:100%` off the (definite, flex-resolved)
           .card-body instead, with the width free to follow the aspect ratio and
           `max-width` as the guard for when it is the width that runs out. Measured
           1.6888 at every viewport height from 1366 down to 1000.

           Nothing here knows how tall the card artwork is, which is deliberate: it has
           changed shape once already (docs/breeze.md §15b) and an `aspect-ratio` written
           down here would be wrong the next time it does.

           Outside this block the image keeps the plain `width:100%` above, which is what
           a phone renders. */
        .card-body .preview,
        .card-body video,
        .card-body .js-player {
            width: auto;
            height: 100%;
            max-width: 100%;
            max-height: none;
            flex: 0 1 auto;
            min-height: 0;
        }
    }
}

@media (prefers-reduced-motion: reduce) {
    .social-buttons button,
    .social-buttons a,
    .fa-download,
    .event-title h2 a { transition: none; }
}

/* ============================================================
   GUEST ORDER — the pill goes UNDER the card (2026-08-21)
   ============================================================
   The critique's phone page reads: Meet {name} / the card / "Nice to meet you at
   Dreamforce." / Connect on Trailhead / Tap to download. Breeze's DOM cannot say that:
   the only per-guest surface that can carry the pill's {s8} href is the description,
   and the description is server-rendered INSIDE the header, above the card.

   So the boxes between them come off. `display: contents` on the header chain and on
   .card makes the logo, the "Meet" paragraph, the pill's paragraph, .card-body and
   .card-footer all direct flex items of .session-page (already a flex column), and
   `order` puts them in the critique's sequence. Inheritance still flows through a
   contents box (text-align, colour, font), so the header's centring holds; what is
   lost is each box's own margin, padding and pseudo-elements — the gallery-header's
   gradient divider is the one casualty, and it had nothing left to divide.

   The greeting rides the pill's paragraph as ::before, so that the two are one block:
   a guest with an empty s8 (opted out, never found) loses the pill via the href rule
   above and KEEPS the greeting.

   This is the guest's DEFAULT layout — no :has() — because the phone is the audience
   that must work. The booth block below puts the boxes back under :has(). Under the
   tablet screen cap, .card-body is now the flex item that gives, which is what the
   "THE CARD GIVES" block already makes it (flex 0 1 auto, min-height 0); .card's own
   copy of those is moot on a contents box. */
.session-page > .header,
.session-page > .header .gallery-header,
.session-page > .header .event-title,
.session-page > .header .event-title h2,
.session-page > .card { display: contents; }

/* What .gallery-header's box carried: the inset above the logo and the centring. */
.session-page > .header .logo-wrapper {
    order: 0;
    margin-top: 14px;
    text-align: center;
}
.session-page > .header .event-title h2 > p:nth-of-type(1) { order: 1; margin-top: 12px; }
/* The card takes the panel's full measure, the same as on the booth: the 524px cap
   that used to sit here made the phone page a small card on a big pale field next to
   the closing screen it follows (2026-08-21). */
.session-page > .card > .card-body {
    order: 2;
    margin: 12px auto 0;
    max-width: 100%;
    width: 100%;
}
.session-page > .header .event-title h2 > p:nth-of-type(3) {
    order: 3;
    margin: 14px 0 0;
    text-align: center;
}
.session-page > .header .event-title h2 > p:nth-of-type(3)::before {
    content: "Nice to meet you at Dreamforce";
    display: block;
    margin: 0 0 12px;
    color: var(--sf-navy);
    font-family: var(--sf-head);
    font-weight: 800;
    font-size: 19px;
    letter-spacing: 0;
    line-height: 1.2;
}
.session-page > .card > .card-footer { order: 4; }

/* `.session-page > *` lifts its children above the panel pseudo-element; the
   flattened pieces are grandchildren now and need the same lift, or the panel
   (positioned, z-index 0) paints over their text. */
.session-page > .header .logo-wrapper,
.session-page > .header .event-title h2 > p,
.session-page > .card > .card-body,
.session-page > .card > .card-footer {
    position: relative;
    z-index: 1;
}

/* ============================================================
   BOOTH ONLY — the QR closing screen
   ============================================================
   Everything below is gated on `body:has(.virtual-booth-actions)`. That container is
   rendered ONLY under ?virtual_booth_sharing=1; the guest page gets .social-buttons +
   .action-buttons instead and matches none of it. Per the note at the top of this
   file, every :has() rule is kept in a rule of its own, and nothing here is
   load-bearing for guests — an engine that cannot parse :has() drops these and the
   booth falls back to the guest layout, which is a worse screen, not a broken one.

   THE PANEL IS THE SAME ONE THE WHOLE SKIN USES. This screen was briefly built the
   other way — the panel split into two boxes so it ended under the card, with the
   caption and the QR below it on the artwork, the way the retired light skin did it.
   That is the wrong read of the comp: it puts the caption across the arc, and the arc
   is the bottom of the drawing, not a divider inside it. The page-spanning panel
   already has a wide band of empty pale under the card, and that whitespace is where
   the caption and the QR belong. Nothing crosses the arc.

   What actually changes on the booth is content, not structure: the guest's heading
   and pill come off and the steps come on, the card takes the full measure, and the
   QR gets the size the comp draws. */

/* The boxes the guest order flattened come back: the booth keeps the header as a
   header and the card as the flex column its QR footer needs. Same specificity as the
   contents rules plus the body:has() — and later in the file. */
body:has(.virtual-booth-actions) .session-page > .header,
body:has(.virtual-booth-actions) .session-page > .header .gallery-header,
body:has(.virtual-booth-actions) .session-page > .header .event-title,
body:has(.virtual-booth-actions) .session-page > .header .event-title h2 { display: block; }
body:has(.virtual-booth-actions) .session-page > .card { display: flex; }
/* `order: 0`, because the booth's own ".card-footer { order: 0 }" further down would
   otherwise put the QR footer ABOVE a card-body still carrying the guest's order 2. */
body:has(.virtual-booth-actions) .session-page > .card > .card-body { order: 0; margin: 0; max-width: none; }
body:has(.virtual-booth-actions) .session-page > .header .logo-wrapper { margin-top: 0; }

/* The logo comes OFF the booth (2026-08-24). It is the guest page's, where the phone
   needs to say whose page it is; on the closing screen the title does that work, and
   the logo's 92px go to the title and the steps below instead. `display: none` on the
   wrapper, not the img, so its margin goes with it. (Until 2026-08-24 this rule centred
   the logo instead — Breeze left-aligns it inside its wrapper.) */
body:has(.virtual-booth-actions) .logo-wrapper { display: none; }
/* The logo's own height was the title's clearance from the panel's top edge; with it
   gone the title landed 12px under the edge. This is that clearance, restated — 60px
   since 2026-09-02, when the title went to two tiers and the big line sat too close to
   the edge at 32 (the review's first note).
   No bottom padding and no gradient divider (2026-09-02): the closing screen took its
   spacing from the Tableau kiosk comp, which separates its groups with air alone, and
   the divider was the one drawn line in a screen that otherwise has none. The GUEST
   page never rendered it in the first place — its header is a contents box. */
body:has(.virtual-booth-actions) .gallery-header { margin-top: 60px; padding-bottom: 0; }
body:has(.virtual-booth-actions) .gallery-header::after { display: none; }

/* THE TITLE IN TWO TIERS (2026-09-02, from the Tableau kiosk comp): the exclamation
   large and alone on its line, the sentence under it at the size the whole title had.
   One field, two sizes — the title is a plain input that cannot carry a break, so the
   h1's own text is collapsed to nothing and the two lines are drawn as its pseudo-
   elements, static copy in CSS the way the greeting already is.
   Change the title HERE for the booth; the Breeze field still names the tab and the
   guest page's <title>. `font-size:0`, not display:none — the h1 stays the block the
   pseudo-elements hang off. Both !important, because the base h1 rule is flagged.

   The exclamation is .072 of the panel (59px on the booth, 64 cap) against the
   sentence's .044 (36px) — the comp's ratio is about 1.8, and .08 was tried first:
   the 7px it costs per tier came straight out of the card's height. Tight leading on the big
   line, and the two are set with the same navy and tracking so they read as one
   heading, not a heading and a strapline. (Until 2026-09-02 this was one 36px line;
   before 2026-08-24 the caption below carried the size instead.) */
body:has(.virtual-booth-actions) .event-title h1,
body:has(.virtual-booth-actions) .event-title h1 a {
    font-size: 0 !important;
    line-height: 0;
}
body:has(.virtual-booth-actions) .event-title h1::before,
body:has(.virtual-booth-actions) .event-title h1::after {
    display: block;
    color: var(--sf-navy);
    font-family: var(--sf-head);
    font-weight: 800;
    letter-spacing: -.02em;
}
body:has(.virtual-booth-actions) .event-title h1::before {
    content: "Love it!";
    font-size: clamp(36px, calc(var(--sf-panel-w) * .072), 64px);
    line-height: 1.05;
}
body:has(.virtual-booth-actions) .event-title h1::after {
    content: "Now let's connect.";
    font-size: clamp(24px, calc(var(--sf-panel-w) * .044), 40px);
    line-height: 1.2;
    margin-top: 4px;
}


/* The three steps under the title, in the body weight (2026-09-02). They were 600,
   which Salesforce Sans (400 and 700 loaded) renders as bold — three bold lines under
   a bold title, and nothing to tell the heading from the body. The comp's body copy is
   the lighter weight at a comfortable measure, and the contrast against the heading is
   the hierarchy. Regular, 20px, 1.5 leading (22 for an afternoon — the 9px went to the
   card, at the review's request). Booth only — the same paragraph is hidden
   on the guest page — so the phone-tier `3.2vw` clamp above is left alone. Still
   `nowrap`; the longest line runs ~400px at 20px inside the 820px measure. */
body:has(.virtual-booth-actions) .event-title h2 { margin-top: 14px; }
body:has(.virtual-booth-actions) .event-title h2 > p:nth-of-type(2) {
    font-size: clamp(14px, calc(var(--sf-panel-w) * .0244), 20px);
    font-weight: 400;
    line-height: 1.5;
}

/* The booth shows the title and the three "let's connect" steps; the guest's
   "Meet {s1}" heading and the Connect on Trailhead pill are the phone's, and come off.
   (Until 2026-08-21 the whole h2 was hidden here and the steps showed on the PHONE;
   the critique moved them to this screen, where the guest reads them in the queue
   with the booth host standing by.) See "ONE DESCRIPTION, TWO AUDIENCES" above for
   the paragraph order this depends on. */
body:has(.virtual-booth-actions) .event-title h1 { display: block; }
body:has(.virtual-booth-actions) .event-title h2 > p:nth-of-type(2) { display: inline-block; }
body:has(.virtual-booth-actions) .event-title h2 > p:nth-of-type(1) { display: none; }
body:has(.virtual-booth-actions) .event-title h2 > p:nth-of-type(3) { display: none; }

/* The comp runs the card almost to the panel's edge — it is the subject of the
   screen. The 524px cap is the GUEST page's, where the card is one item on a page
   you scroll. */
body:has(.virtual-booth-actions) .card {
    max-width: 100%;
    /* 20px, the screen's one rhythm (2026-09-02): the same air above the card as the
       footer puts between the card and the caption, so the three groups — title and
       steps, the card, caption and code — are evenly separated. It was 12px, against
       the divider's 26 above it and the footer's 20 below. */
    margin: 20px auto 0;
}

/* THE SHEET'S MARGINS ARE CROPPED ON THE BOOTH (2026-09-02). The sheet PNG api/card.js
   composes carries a transparent margin — 2.3% at the sides, 4% top and bottom,
   measured off a live render — and on a height-driven layout that margin is card
   height the guest does not get. The clip takes the margin off and a uniform scale of
   1/(1 - 2 x .04) puts the clipped picture back at the box's full height; the sides
   overrun the box by ~1.6% each, which is the transparent strip's own width and
   lands on pale. Visual only — no layout property changes, so the column's height
   budget and "THE CARD GIVES" are untouched. The guest page keeps the whole sheet.
   Re-measure if the sheet's composition changes (api/card.js sheet()). */
body:has(.virtual-booth-actions) .card-body .preview {
    clip-path: inset(3.95% 2.31%);
    transform: scale(1.086);
    transform-origin: 50% 50%;
    /* The box is capped 80px inside the panel, because the scaled picture overruns
       its box by ~1.8% a side: at the full 782px the cards would come out 810 wide on
       an 820 panel. At 740 they are ~767, 26px clear of the panel's edge either side.
       This is the ceiling the full 1366 hits; with the URL bar the height still is. */
    max-width: calc(var(--sf-panel-w) - 80px);
}

/* Caption then QR, stacked in the panel's whitespace under the card.

   THE BOTTOM PADDING IS THE QR'S CLEARANCE FROM THE ARC (2026-08-24). Under the screen
   cap the column fills the panel to its padding edge exactly, and the footer is the
   last thing in it — so at any viewport shorter than the content (Safari's URL bar
   on the iPad takes it from 1366 to ~1290) the QR's bottom edge landed ON the arc,
   measured 0px at 1300, 1250, 1200 and 1100. The 66px it seemed to have was only the
   slack of a full 1366, where the content is shorter than the cap. The footer is
   `flex: 0 0 auto` under the cap, so this padding survives and the card artwork is
   what gives up the 48px instead — which is the right trade: the artwork is already
   the thing that shrinks, and the QR is the one element that has to be scannable. */
body:has(.virtual-booth-actions) .card-footer {
    flex-direction: column;
    /* 20 = the card's margin above, one rhythm. 32 at the foot, from 48 (2026-09-02):
       the pill is 32px above the arc's deepest point at 1290, which still reads as
       clear, and the 16px went to the card. */
    padding: 20px 14px 32px;
    gap: 10px;
}

/* Booth rendering: the QR sits BELOW the card ----------------
   It led with the QR from 2026-08-14 until 2026-08-17, because at
   340px in a white panel the code landed at y~1090 and fell off the
   1366px iPad. Reversed deliberately: the card is what the guest
   came for and it reads first, and the QR now fits under it because
   the white panels came off both. That is a height budget, not a
   preference — see the measurement in docs/breeze.md §6, and re-check
   it if the card artwork ever gets taller. The reskin spends height
   again (the arc's clearance, and the artwork gap below the panel),
   so it has been re-measured — §6.

   DOM order is already card then footer, so this needs no `order` at
   all; the rule is kept as the place the decision is written down. */
.card:has(.virtual-booth-actions) {
    display: flex;
    flex-direction: column;
}

.card:has(.virtual-booth-actions) .card-footer { order: 0; }
