/* ============================================================
   SALESFORCE / DREAMFORCE  |  Event Gallery  (/e/ pages)
   Pop Life Photo  |  Customize Event > Gallery Design CSS

   The gallery's own custom-CSS slot, which is SEPARATE from the
   microsite's (docs/breeze.md §6): reskinning one leaves the
   other on its old theme, and that is how these two pages spent
   a day looking like two different products.

     /events/<id>/microsite-settings  -> the /s/<session> page
     /events/<id>/gallery-settings    -> the /e/<event> page

   Reskinned 2026-08-19 to match `breeze/microsite.css` value for
   value: the same sky (the microsite artwork since 2026-08-26), the same arced
   panel from the supplied mask, the same ratios. A guest goes
   quiz -> camera -> microsite -> gallery, so all four have to be
   one design. Change them together.

   2026-08-25: brought into df26 (this repo is the source of
   truth; the file had lived only on wind).

   2026-09-01: the photo tiles show the stored sheet WHOLE —
   front AND back, the way every other Breeze gallery reads —
   instead of cropping to the card's front panel. The crop was
   also what made this page blurry: Breeze spends its one
   `?width=360` on the whole sheet, so cropping to the front
   threw most of those pixels away. There is no geometry
   contract with api/card.js any more; see the tile section.
   (Live on Breeze 2026-09-01, lost on 2026-09-02 when the font
   swap re-uploaded main's copy — the rules had only ever been
   in a branch working tree. Restored here on 2026-09-04.)

   THE MARKUP IS NEARLY the microsite's shape, and the one
   difference decides a colour:

     body > .gallery-page > .header + .gallery
     body > .footer                              <- NOT in the wrapper

   The microsite's footer is INSIDE `.session-page` and therefore
   inside the panel, so its copy is navy. The gallery's footer is
   a SIBLING of `.gallery-page`, so it lands on the artwork below
   the arc, and its copy is white with a shadow — the same as the
   quiz's. Measured, not assumed: the footer's own rect starts
   where the wrapper's ends.

   There is no panel here any more: the cards sit straight on the
   artwork. The microsite still has one, so the two pages are NOT
   symmetrical -- do not port a panel rule across from it.

   TWO MORE THINGS DIFFER FROM THE MICROSITE, both structural:

   1. `.gallery-header` also carries `.container-fluid`, so it is
      `width:100%` and `display:inline-block` alone will not
      shrink it — it needs `width:auto !important`. It no longer
      needs to: the pale plate that used to sit there is gone,
      because a pale plate on a pale panel is nothing at all.
   2. `<body>` carries an INLINE `min-height:100vh; display:flex;
      flex-direction:column`. Inline styles beat any stylesheet
      rule short of !important, so the wrapper is given
      `flex:1 0 auto` to fill rather than fighting the height.

   The measure is wider here than on the microsite — a grid of
   sixteen photos is not one card — but the arc and the corner
   radius stay ratios OF that width, so the panel is the same
   drawing at a different size.

   NOTE: this event's header carries a logo and, since 2026-09-08,
   the search pill; .event-title renders empty. The type rules
   below are kept for whenever a title or description is switched
   on.
   ============================================================ */

/* 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 microsite.css and to breeze/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-blue:  #0176D3;
    --sf-navy:  #032D60;
    --sf-muted: #5C6B8A;
    --sf-sans:  "Salesforce Sans", "Poppins", "Montserrat", Arial, sans-serif;
    --sf-head:  "Avant Garde For Salesforce", "Salesforce Sans", "Poppins", sans-serif;

    /* 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. Absolute because this stylesheet is served from cloud.breezesoftware.com onto
       a page on sharethis.co — a relative path resolves against neither host that
       has the file; the host is the wind mirror, same as the other two sheets. */
    --sf-sky: url("https://wind-m3yb.vercel.app/vendor/df-sky.jpg");

    /* 1200px rather than the microsite's 820: the gallery holds a masonry grid, and
       at 820 a desktop viewer gets only two columns. This now sets the grid's width
       directly rather than a panel's. */
    --sf-measure: 1200px;
    --sf-panel-w: min(var(--sf-measure), calc(100vw - 24px));
}

@media (max-width: 899px) { :root { --sf-measure: 620px; } }
@media (max-width: 599px) { :root { --sf-measure: 520px; } }

/* 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. iOS Safari has never honoured background-attachment:fixed — WebKit paints it
   as `scroll` — and this is the one page that really is long: on a phone the artwork
   scrolled away with the first screenful and the rest of the gallery sat on the flat
   html blue. 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.

   The same placement as microsite.css and survey.css, so the four pages share one sky:
   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) sits at the
   horizontal middle; 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 */
    color: var(--sf-navy);
    font-family: var(--sf-sans);
}

/* The panel ------------------------------------------------- */
.gallery-page {
    position: relative;
    background: transparent;
    /* body is an inline-styled flex column; this fills it without restating 100vh. */
    flex: 1 0 auto;
    width: 100%;
    max-width: var(--sf-panel-w);
    margin: 0 auto;
    box-sizing: border-box;
    display: flex;
    flex-direction: column;
    /* Was `calc(var(--sf-gap-bot) + var(--sf-arc))` to clear the arc. With no panel
       there is no arc to clear, and leaving it stranded ~170px of empty artwork under
       the last row. */
    padding: 0 1.2rem 18px;
}

/* No panel. The pale ::before that used to sit behind the whole page was removed
   2026-08-20 -- the cards read better straight on the artwork, and the wave field is
   the point of the background. It is in git history if it is ever wanted back.

   What went with it: --sf-gap-bot, --sf-arc, --sf-radius, --sf-panel and
   --sf-arc-shape were only ever consumed by that rule. --sf-measure stays, because
   --sf-panel-w still sets the grid's max width. */

.gallery-page > * {
    position: relative;
    z-index: 1;
}

/* Header: the logo, on the artwork --------------------------
   No plate behind it. The mark is Salesforce cyan and the page under it is the deep
   blue/violet top of the gradient, which separates them well enough; a pale plate
   here would put back a version of the shape that was just removed. The theme pads
   .header 64/80, which left ~144px of dead air under a logo-only header — pull it
   in. */
.header {
    background: transparent;
    position: relative;
    text-align: center;
    padding: 22px 0 6px !important;
}

.gallery-header {
    display: block;
    width: auto !important;
    max-width: 100%;
    margin: 0 auto;
    padding: 0;
    background: transparent;
    border: none;
    border-radius: 0;
    box-shadow: none;
    text-align: center;
}

.event-logo {
    height: 92px;
    display: block;
    margin: 0 auto;
}

/* Type, for whenever the title/description are switched on --- */
.event-title h1 {
    color: var(--sf-navy);
    font-family: var(--sf-head);
    font-weight: 800;
    font-size: clamp(22px, 5vw, 32px);
    letter-spacing: -0.02em;
    margin-top: 14px;
}

.event-title h2,
.event-title h2 a {
    color: var(--sf-navy);
    font-family: var(--sf-sans);
    font-weight: 600;
    font-size: 15px;
    letter-spacing: .10em;
    text-transform: uppercase;
}

/* Search: the quiz's Next pill, holding a field ------------
   Breeze's gallery search landed 2026-09-08 (gallery-settings > "Enable Gallery
   Search"). Its markup is one form in .gallery-header, after the empty .event-title:

     form.gallery-search[role=search]
       input[type=search].gallery-search__input   (aria-label, placeholder from settings)
       button[type=submit].gallery-search__button (label from settings, "Search")
       a.gallery-search__reset                    (not rendered on this event; styled
                                                   anyway so it cannot arrive unskinned)

   The settings page paints it from its own colour/font fields — Space Mono, #151516
   ink, square corners — as an inline <style> in the head. That block comes BEFORE
   this sheet and its rules are single-class, so equal specificity here wins on
   source order; the !importants below are for the theme's own gallery.css, which
   sits between the two and has been seen both with and without .gallery-search
   rules on the same day (the CDN copy and the origin copy differ).

   The drawing is the quiz's Next button with the label swapped for a field: ONE
   white pill (the form), the input borderless inside it, and the submit as the
   navy --sf-cta disc on the right — the same disc the microsite's download button
   and the quiz's arrow chip use — carrying a magnifier instead of an arrow, because
   a search field with an arrow reads as "next". Shadow, radius, face and field
   colours are survey.css's values: pill `0 10px 26px rgba(11,42,99,.18)`, placeholder
   #8FA2C0, focus ring `0 0 0 3px rgba(1,118,211,.18)`.

   The button's visible text is hidden (transparent, 0 size) rather than removed:
   the label is still its accessible name, and it is the settings page's text, so
   changing it there changes nothing here. The glyph is a data URI — no request. */
:root {
    --sf-cta: #2B3A93;   /* the disc: survey.css's arrow chip, microsite.css's download */
    --sf-search-glyph: 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%3Ccircle cx='10.5' cy='10.5' r='6.5'/%3E%3Cpath d='M15.5 15.5L20 20'/%3E%3C/svg%3E");
}

.gallery-search {
    display: flex !important;
    align-items: center;
    gap: 6px;
    box-sizing: border-box;
    /* A fixed measure, not fit-content: the field flexes to fill it, so the pill is
       the same drawing on a phone (where it takes the width it has) and on a desktop
       (where 380px sits under a 92px logo the way the Next pill sits under a tile). */
    width: min(100%, 380px);
    margin: 16px auto 8px;
    padding: 5px 5px 5px 20px;
    background: #FFFFFF;
    border-radius: 999px;
    box-shadow: 0 10px 26px rgba(11, 42, 99, .18);
    transition: box-shadow .18s ease;
}

.gallery-search:focus-within {
    box-shadow: 0 10px 26px rgba(11, 42, 99, .18), 0 0 0 3px rgba(1, 118, 211, .18);
}

.gallery-search__input {
    flex: 1 1 auto;
    min-width: 0;
    width: auto;
    margin: 0;
    padding: 7px 4px 7px 0;
    -webkit-appearance: none;
    appearance: none;
    background: transparent !important;
    border: none !important;
    border-radius: 0;
    outline: none;
    box-shadow: none !important;
    color: var(--sf-navy) !important;
    font-family: var(--sf-sans) !important;
    font-weight: 600;
    /* 16px, and never under: iPadOS zooms a focused field with smaller text. */
    font-size: 16px !important;
    line-height: 1.3;
}

.gallery-search__input::placeholder {
    color: #8FA2C0 !important;   /* survey.css's field placeholder */
    opacity: 1 !important;
    font-weight: 500;
}

.gallery-search__button {
    flex: 0 0 auto;
    width: 36px;
    height: 36px;
    margin: 0;
    padding: 0 !important;
    border: none !important;
    border-radius: 50% !important;
    background: var(--sf-cta) var(--sf-search-glyph) center / 18px auto no-repeat !important;
    /* The label stays in the tree as the button's name; it just does not paint. */
    color: transparent !important;
    font-size: 0 !important;
    line-height: 0;
    cursor: pointer;
    box-shadow: none;
    transition: transform .18s ease, box-shadow .18s ease;
}

.gallery-search__button:hover { box-shadow: 0 6px 16px rgba(43, 58, 147, .35); }
.gallery-search__button:active { transform: scale(.94); }
.gallery-search__button:focus-visible { outline: 3px solid rgba(1, 118, 211, .5); outline-offset: 2px; }

/* Not rendered on this event today. If it appears it lands in the pill, after the
   disc — a small navy link is the least it can be without a comp. */
.gallery-search__reset {
    flex: 0 0 auto;
    margin: 0 8px 0 2px;
    color: var(--sf-blue) !important;
    font-family: var(--sf-sans) !important;
    font-size: 13px;
    font-weight: 600;
    text-decoration: none;
    white-space: nowrap;
}

.gallery-search__reset:hover { text-decoration: underline; }

/* The empty-gallery message — also the search's "no matches" state, since Breeze
   uses the one element for both. White with the footer's shadow, not navy: the pale
   panel it used to sit on went on 2026-08-20 and navy on the violet artwork was
   near-invisible; the search made the state reachable on demand. */
.placeholder {
    font-family: var(--sf-sans);
    color: rgba(255, 255, 255, .92);
    font-size: 20px;
    font-weight: 600;
    text-shadow: 0 1px 10px rgba(2, 20, 96, .55);
    padding: 24px 0;
}

/* Scroll arrows: hidden ------------------------------------- */
.arrow,
.arrow-up,
.arrow-down {
    display: none !important;
}

/* The grid takes the panel's measure ------------------------
   `.gallery > .container` is Bootstrap's, so it caps itself around 1140px and adds
   its own gutters. Inside a panel that already sets the measure and its own padding,
   that is a second measure fighting the first. */
.gallery { padding-top: 6px !important; }

.gallery .container {
    max-width: 100% !important;
    padding-left: 0 !important;
    padding-right: 0 !important;
}

/* Photo tiles: the WHOLE sheet, front AND back ---------------
   What Breeze stores for a session is the WEB SHEET off api/card.js — since
   2026-08-28 that is the ROW sheet, front BESIDE back on a TRANSPARENT ground
   (card.js sheet(), dir 'row'; the pale field came off the file that day). The tile
   shows that sheet whole, and paints the pale #EAF5FE plate under it itself. Both faces of
   the card are on the gallery, which is what every other Breeze event does and
   what a viewer expects before deciding which card to open.

   THIS IS ALSO THE SHARPNESS FIX, and the reason is arithmetic rather than
   taste. Every tile's <img> is the stored file at `?width=360` — baked into the
   server-rendered markup, initial page and /load-more alike, with no setting on
   either gallery-settings page to raise it and no JS slot to rewrite it. 360 px
   is spent on the WHOLE sheet no matter what the tile then shows, so cropping
   to one panel throws most of those pixels away and stretches the rest:

     cropped to the front   760/1950 of 360 =  139 px  across a 315 px track
     the whole sheet             all of 360 =  360 px  across a 315 px track

   That 139 px was the "blurry gallery" — a 2.3x upscale before the screen's own
   2x. Showing the sheet whole is the most resolution this page can carry; past
   that the ceiling is Breeze's and only the microsite (which serves the stored
   file at its own ~1600 px) goes beyond it.

   NOTHING IS ANCHORED IN PERCENTAGES ANY MORE, so this section no longer has a
   geometry contract with card.js's sheet(): SHEET_PAD, SHEET_GAP, B and the
   750x1050 trim can all move without touching this file. Nor is there a legacy
   case. The old crop needed one rule per sheet shape, keyed on Breeze's inline
   `--h`, because each shape put the front in a different place; drawn whole,
   every shape is just its own aspect ratio and the theme already handles that.
   Measured off the live gallery 2026-09-01 — five shapes, no rule for any of
   them (Breeze stores at 1600 px on the long edge, whatever card.js rendered):

     --h: 211   1599x936    row sheet, landscape back
     --h: 213   1599x945    row sheet, landscape back
     --h: 246   1599x1092   row sheet, portrait back
     --h: 251   1600x1115   row sheet, portrait back
     --h: 731   1600x3250   col sheet, stacked          (2026-08-27, one day)

   The pairs are the giveaway. 211/213 and 246/251 are the SAME sheet off two
   card.js builds — one measuring the row off the panels' trim, one off their
   bleed boxes — a ~1% difference that a percentage crop has to care about and
   an aspect ratio does not. That is the argument for this section: sheet
   geometry is now card.js's business alone.

   SIZING IS THE THEME'S, DELIBERATELY UNTOUCHED. mm-masonry.css turns Breeze's
   inline --w/--h into `grid-row-end: span calc(--h * --col-width / --w + --gap)`
   over 1px rows, and the item's own `margin-bottom: --gap` comes back off that
   span — so the item's content box lands at exactly the image's natural height
   for its track, and `object-fit` has nothing to crop. Overriding
   --img-proportional-height (which this file used to do, to force the front's
   5:7) is what breaks that identity, so it is left alone. */

.mm-masonry {
    /* The theme's minmax(col, 1fr) stretches tracks to fill the row, but its span
       math above is computed from --col-width, not from the width the track ends
       up at — so a stretched track makes every item that much shorter than its
       image and object-fit:cover eats the sheet's edges. Fixed tracks, centred,
       keep the two in step. */
    grid-template-columns: repeat(auto-fill, var(--_col-width-px));
    justify-content: center;

    /* 20, up from the theme's 12, and it is the SHADOW that sets the number. A tile
       is now an OPAQUE pale plate (the sheet's own field) rather than a card-shaped
       crop with sky showing between the tiles, so a neighbour's plate paints over
       this one's shadow instead of letting it fall on the artwork. At gap 12 under
       the old `0 10px 26px` the shadow reached ~23px, well past the gap, and every
       row cut the row above it off in a hard dark band. Raise the gap, shorten the
       shadow (below), and the lift lands entirely in the gap.

       Safe to retune because ONE variable drives all three consumers in
       mm-masonry.css — `column-gap`, the item's `margin-bottom`, and the `+ --gap`
       in the row span — so they move together and the span/margin identity that
       keeps object-fit honest is preserved. Verified: still sub-pixel (<=0.5px)
       across all tiles at 1440/820/620, and the column count is unchanged at
       3/2/2. Change it here, never on the item. */
    --gap: 20;
}

.mm-masonry__item {
    background: #EAF5FE;   /* card.js's SHEET, so any rounding seam is the field */
    border: none;
    padding: 0;
    border-radius: 14px;
    /* Reaches ~11px below the plate, inside the 20px gap above — so it reads as a
       lift off the artwork and never touches the next row. The old
       `0 10px 26px .22` was drawn for a tall card-shaped tile with sky all round
       it; on a short wide plate in a packed grid it only pooled. */
    box-shadow: 0 4px 14px rgba(11, 42, 99, .20);
    overflow: hidden;
    transition: transform .18s ease;
}

.mm-masonry__item:hover { transform: translateY(-3px); }

.mm-masonry__img {
    /* `contain`, not the theme's `cover`: the box and the image agree on height
       (see above), so the two are the same picture — but if a sub-pixel ever
       parts them, contain leaves a hairline of the pale field and cover would
       shave the card's trim. Losing a hairline of field is the safe direction. */
    object-fit: contain;
    border-radius: 0;
    box-shadow: none;
}

/* Footer: OUTSIDE the panel, on the artwork, so white -------
   It is a sibling of .gallery-page, not a child, so it sits below the arc on the
   artwork's ridges — purple where the microsite's footer sits on pale. White with a
   soft shadow, so it holds over both the light crest of a ridge and the shadow
   between them: the same treatment the quiz's footer gets, for the same reason. */
.footer {
    background: transparent;
    border-top: none;
    margin-top: 0;
    padding: 14px 10px 18px;
    text-align: center;
}

.footer-content {
    color: rgba(255, 255, 255, .88);
    font-family: var(--sf-sans);
    font-size: 14px;
    letter-spacing: .03em;
    text-shadow: 0 1px 10px rgba(2, 20, 96, .55);
}

.footer-content a {
    color: #FFFFFF;
    font-weight: 700;
    text-decoration: none;
}

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

/* Session filter stays hidden (matches current setup) -------- */
.session-filter,
.session-filter a { color: transparent !important; }

@media (max-width: 560px) {
    .event-logo { height: 76px; }
    .header { padding: 16px 0 4px !important; }
}

@media (prefers-reduced-motion: reduce) {
    .mm-masonry__item { transition: none; }
}
