/* ==========================================================================
   Sully's — Component styles (custom only)

   This file ONLY contains styles for things that don't exist in Elementor's
   widget set, or for visual effects (gradient text wipe) that can't be set
   via widget controls.

   Anything that overrides Elementor widget defaults (heading typography,
   form field styling, button styling, etc.) is intentionally removed —
   set those from the widget's Style tab in the editor.
   ========================================================================== */


/* ---- Custom cursor — 23px ring (no fill), doubles to 46px on link hover ----
   2026-05-06: per producer feedback the ring needs to read as a clear bright
   yellow. Two changes: (1) dropped mix-blend-mode: difference (it was
   inverting the yellow into a grey-cyan on the dark themes); (2) bumped the
   colour to a saturated yellow (#FFE600) with a 2.5px stroke. A faint dark
   drop-shadow keeps it legible on cream-bg sections where yellow-on-cream
   would otherwise wash out. */
.sullys-cursor {
    position: fixed;
    top: 0; left: 0;
    width: 0; height: 0;
    pointer-events: none;
    z-index: var(--z-cursor);
    transform: translate3d(-100px, -100px, 0);
    will-change: transform;
}
.sullys-cursor-dot {
    width: 23px; height: 23px;
    border-radius: 50%;
    background: transparent;                 /* always hollow */
    border: 2.5px solid #fff2af;             /* bright yellow */
    filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.45));
    transform: translate(-50%, -50%);
    transition: width 0.3s var(--ease-out-expo),
                height 0.3s var(--ease-out-expo),
                opacity 0.2s;
    transform-origin: center;
    box-sizing: border-box;
}
.sullys-cursor.is-expanded .sullys-cursor-dot {
    width: 46px; height: 46px;
}
.sullys-cursor.is-hidden .sullys-cursor-dot { opacity: 0; }


/* ---- Colour switcher (pill-shaped toggle) ----
   Track + knob match the figma SVG (44x19 px).

   Default state (blue theme):
     - track fill   = navy (--bg-page)
     - track border = sky  (--fg-primary)
     - knob fill    = sky  (--fg-primary)
     - knob position: RIGHT

   Toggled state (red theme):
     - track fill   = maroon (--bg-page in red theme)
     - track border = peach  (--accent-hot in red theme)
     - knob fill    = peach  (--accent-hot in red theme)
     - knob position: LEFT

   Sizing math (border-box, 1px border, padding 0):
     outer 44 x 19  →  padding-box 42 x 17
     knob 13 x 13 positioned at top: 2px, left: 2px (flush against padding-box)
     translation range: padding-box width (42) − knob (13) − margins (2 + 2) = 25px
*/
.sullys-theme-switch {
    box-sizing: border-box;
    display: inline-block;
    width: 44px; height: 19px;
    padding: 0;
    border-radius: 999px;
    border: 1px solid var(--fg-primary);
    background: var(--bg-page);
    position: relative;
    cursor: none;
    user-select: none;
    transition: background 0.3s var(--ease-out-cubic),
                border-color 0.3s var(--ease-out-cubic);
}
.sullys-theme-switch::before {
    content: "";
    position: absolute;
    top: 2px;
    left: 2px;
    width: 13px; height: 13px;
    border-radius: 50%;
    background: var(--fg-primary);
    transition: transform 0.35s var(--ease-out-expo),
                background 0.35s var(--ease-out-expo);
}

/* Default — blue theme: knob on RIGHT */
body.theme-blue .sullys-theme-switch::before {
    transform: translateX(25px);
}

/* Red theme — knob on LEFT, border + knob become peach (--accent-hot) */
body.theme-red .sullys-theme-switch {
    border-color: var(--accent-hot);
}
body.theme-red .sullys-theme-switch::before {
    background: var(--accent-hot);
    transform: translateX(0);
}

/* Override Hello Elementor parent theme's `button:hover { background: #c36 }`
   which would otherwise paint our custom buttons pink on hover/focus.
   Specificity has to match `button:hover` (one type + one pseudo) — the
   class + pseudo combination here wins on specificity AND keeps the
   themed colours stable across hover/focus states. */
.sullys-theme-switch:hover,
.sullys-theme-switch:focus,
.sullys-theme-switch:active {
    background-color: var(--bg-page);
    color: inherit;
    text-decoration: none;
    outline: none;
}
body.theme-red .sullys-theme-switch:hover,
body.theme-red .sullys-theme-switch:focus,
body.theme-red .sullys-theme-switch:active {
    background-color: var(--bg-page);
    border-color: var(--accent-hot);
}

/* Same fix for the mobile menu hamburger and close button — both real
   <button> elements that would inherit the parent theme's pink hover. */
.sullys-mobile-toggle:hover,
.sullys-mobile-toggle:focus,
.sullys-mobile-toggle:active,
.sullys-mobile-close:hover,
.sullys-mobile-close:focus,
.sullys-mobile-close:active {
    background-color: transparent;
    color: var(--fg-primary);
    text-decoration: none;
    outline: none;
}


/* ---- Gradient text-wipe hover ----
   Effect-only: typography (font, size, transform, etc.) is set via the
   Elementor widget. This rule contributes ONLY the peach-wipe gradient
   that swaps in on hover — something widget controls can't do.

   Rest-state colour:
     The gradient's right half uses `currentColor`, so whatever colour you
     set on the heading widget in Elementor's Style tab is the rest-state
     colour you see. If no colour is set, it defaults to white via the
     non-!important `color` declaration below.

   Visible text:
     `-webkit-text-fill-color: transparent` makes the actual text fill
     transparent so the gradient shows through. We deliberately do NOT
     set `color: transparent !important` — that would clobber the
     widget's Style-tab colour and break `currentColor`.

   Usage in Elementor: add the CSS class `sullys-link-hover` (for plain
   anchors) or `sullys-nav-link` / `sullys-big-nav` (for headings) to the
   widget's Advanced > CSS Classes field. Then set the colour you want
   for the rest state in the widget's Style tab — Elementor's setting
   wins on specificity over our default white.

   Three render shapes are supported:
     1. Element itself has the class — e.g. <a class="sullys-link-hover">
     2. Elementor Heading widget — wrapper has the class, text lives in
        <div class="elementor-heading-title"><a>…</a></div>
     3. Elementor Nav Menu widget — wrapper has the class, items render
        as <a class="elementor-item">…</a> inside a <ul>. Active page
        gets .elementor-item-active natively (Elementor Pro). */
.sullys-link-hover,
a.sullys-link-hover,
.sullys-nav-link,
.sullys-nav-link .elementor-heading-title,
.sullys-nav-link .elementor-heading-title a,
.sullys-nav-link .elementor-item,
.sullys-big-nav,
.sullys-big-nav .elementor-heading-title,
.sullys-big-nav .elementor-heading-title a,
.sullys-big-nav .elementor-item {
    color: #FFFFFF;                              /* default — Elementor's Style-tab colour overrides */
    background: linear-gradient(to right, var(--accent-soft, #F8A06A) 50%, currentColor 50%) !important;
    background-size: 200% 100% !important;
    background-position: 100% 0 !important;
    -webkit-background-clip: text !important;
    background-clip: text !important;
    -webkit-text-fill-color: transparent !important;
    transition: background-position 0.4s var(--ease-out-cubic) !important;
    text-decoration: none;
}
.sullys-link-hover:hover,
.sullys-link-hover.is-active,
.sullys-nav-link:hover,
.sullys-nav-link:hover .elementor-heading-title,
.sullys-nav-link:hover .elementor-heading-title a,
.sullys-nav-link.is-active,
.sullys-nav-link.is-active .elementor-heading-title,
.sullys-nav-link.is-active .elementor-heading-title a,
.sullys-nav-link .elementor-item:hover,
.sullys-nav-link .elementor-item:focus,
.sullys-nav-link .elementor-item.elementor-item-active,
.sullys-nav-link .elementor-item[aria-current="page"],
.sullys-big-nav:hover,
.sullys-big-nav:hover .elementor-heading-title,
.sullys-big-nav:hover .elementor-heading-title a,
.sullys-big-nav.is-active,
.sullys-big-nav.is-active .elementor-heading-title,
.sullys-big-nav.is-active .elementor-heading-title a,
.sullys-big-nav .elementor-item:hover,
.sullys-big-nav .elementor-item:focus,
.sullys-big-nav .elementor-item.elementor-item-active,
.sullys-big-nav .elementor-item[aria-current="page"] {
    background-position: 0% 0 !important;
}

/* Elementor's Nav Menu widget paints its own pointer-line / background
   pseudo-elements on .elementor-item by default, plus its own colour
   transitions. With sullys-nav-link applied, our gradient-clipped text
   is the visual — defeat any Elementor "pointer" hover effect that
   would otherwise paint a coloured background or underline at the same
   time. The widget's Pointer setting should be "None" but this guards
   against forgotten settings. */
.sullys-nav-link .elementor-item::before,
.sullys-nav-link .elementor-item::after,
.sullys-big-nav  .elementor-item::before,
.sullys-big-nav  .elementor-item::after {
    display: none !important;
}


/* ---- Display typography presets (opt-in utility classes) ----
   The brand display look = Birdie display font, uppercase, tight line-height.
   You can replicate this on any Elementor Heading widget via Style → Typography
   instead — these classes are just convenience presets so a heading can be
   themed with one class instead of five settings.

   Usage: add `sullys-display-xl` / `sullys-display-lg` / `sullys-display-md`
   to a heading widget's Advanced > CSS Classes field.

   When a class is on an Elementor widget wrapper, the rule reaches the
   inner .elementor-heading-title element where the actual text lives. */
.sullys-display-xl,
.sullys-display-xl .elementor-heading-title {
    font-family: var(--font-primary);
    font-size: var(--type-display-xl);
    line-height: var(--lh-tight);
    text-transform: uppercase;
    letter-spacing: var(--tracking-tight);
}
.sullys-display-lg,
.sullys-display-lg .elementor-heading-title {
    font-family: var(--font-primary);
    font-size: var(--type-display-lg);
    line-height: var(--lh-tight);
    text-transform: uppercase;
    letter-spacing: var(--tracking-normal);
}
.sullys-display-md,
.sullys-display-md .elementor-heading-title {
    font-family: var(--font-body);
    font-size: var(--type-display-md);
    line-height: var(--lh-heading);
    letter-spacing: var(--tracking-tight);
}


/* ---- Reveal-line inner wrapper (for mask slide-up headings) ----
   Used by Sullys.scrollReveal in main.js — Splitting.js produces .line spans
   and our JS wraps each in .reveal-line-inner so the mask + translateY works.
   Both selectors below cover the case of `.reveal-heading` landing on an
   Elementor widget wrapper (where the actual text is in .elementor-heading-title). */
.reveal-heading .line,
.reveal-heading .elementor-heading-title .line {
    overflow: hidden;
    display: block;
}
.reveal-line-inner {
    display: inline-block;
    will-change: transform;
}
/* NOTE: <br> elements that the user typed in Elementor's heading text are
   removed from the DOM in JS *after* Splitting computes lines (see
   wrapWordsIntoLines in main.js). Hiding them via CSS prematurely would
   cause Splitting to measure all words on one line — losing the manual
   line break entirely. */


/* ---- Cursor-following accordion (Cutler-style) ----
   Custom widget — no Elementor equivalent.

   This block holds ONLY structural rules — the things that have no
   equivalent in Elementor's widget controls (positioning, flex / grid
   layout, the cursor-follow transform, the smooth-expand animation).

   ALL visual properties (sizes, colours, borders, radii, shadows,
   typography, padding, divider) are controlled from the Events
   Accordion widget's Style tab. Elementor writes those as inline CSS
   scoped to the widget instance, so anything set there wins on
   specificity over (and adds to) the rules below.

   Markup pattern:
     <div class="sullys-accordion" data-sullys-accordion>
       <img class="sullys-accordion-image" data-accordion-image />
       <a class="sullys-accordion-item" data-accordion-item data-image="…">
         <span class="event-left">date + title</span>
         <span class="event-right">CTA + description (description hidden until hover)</span>
       </a>
     </div>
*/
.sullys-accordion {
    position: relative;
    cursor: none;
}
.sullys-accordion-image {
    position: absolute;
    top: 0; left: 0;
    pointer-events: none;
    transform: translate(-50%, -50%);
    will-change: transform, opacity;
    z-index: 5;
}

/* Mobile / touch devices: text-only accordion, click-to-expand.
   Per Louisa's request, the cursor-follow image is hidden on mobile —
   each item is a simple tap target that expands its description on tap.
   A "Book Now" link is rendered inside the expanded panel so visitors
   can reach the booking page directly without a second navigation step.

   - Image: hidden entirely (no sticky thumbnail above the list).
   - Items: stacked single-column layout, everything left-aligned. The
            two-column desktop layout (title-left + cta-right) becomes
            title on top, CTA / description on the next line, both flush
            to the left edge.
   - Activation: driven by tap (see main.js touch path). Only one item
                 expands at a time; tapping the same item again collapses
                 it. The previous IntersectionObserver scroll-to-activate
                 logic is intentionally retired on mobile. */
@media (max-width: 768px), (hover: none) {
    .sullys-accordion {
        cursor: auto;
    }
    .sullys-accordion-image {
        display: none !important;
    }

    /* Single column, left-aligned items */
    .sullys-accordion-item {
        flex-direction: column;
        align-items: flex-start;
        gap: var(--space-2);
    }
    .sullys-accordion-item .event-left,
    .sullys-accordion-item .event-right {
        width: 100%;
        justify-content: flex-start;
        text-align: left;
    }

    /* Description-mode column overrides (was flex-end / right-aligned for desktop) */
    .sullys-accordion--describe .sullys-accordion-item .event-right {
        flex-direction: column;
        align-items: flex-start;
        text-align: left;
    }
    .sullys-accordion--describe .sullys-accordion-item .event-cta-wrap,
    .sullys-accordion--describe .sullys-accordion-item .event-description-wrap {
        width: 100%;
        text-align: left;
    }
    .sullys-accordion-item .event-description {
        max-width: 100% !important;        /* fill the row instead of capping at ~38ch */
    }

    /* The "VIEW MORE" CTA already signals the row is expandable, so no
       extra +/- indicator is needed beside the title on mobile. */

    /* The Book Now link should sit on its own line inside the expanded
       description, flush with the left edge on mobile (not the right). */
    .sullys-accordion--describe .sullys-accordion-item .event-description-inner {
        align-items: flex-start;
    }
    /* Mobile reveal of .event-book-link is declared further down the
       file (AFTER the base `display: none` rule) so the cascade order
       favours the mobile rule when this media query matches. Keeping
       it here originally meant the later base rule won and hid the
       link at every breakpoint. */
}
.sullys-accordion-item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    position: relative;
    text-decoration: none;
    gap: var(--space-4);
    /* Divider line above each item — colour + width come from the widget's
       Style → Items → Divider line controls (which set CSS vars on the
       .sullys-accordion wrapper). Defaults match the previous semi-
       transparent white look. */
    border-top:
        var(--sullys-accordion-divider-w, 1px)
        solid
        var(--sullys-accordion-divider-color, rgba(255,255,255,0.18));
}
.sullys-accordion-item .event-left,
.sullys-accordion-item .event-right {
    display: flex;
    align-items: center;
    gap: var(--space-4);
    z-index: 1;
}
.sullys-accordion-item .event-right {
    flex-shrink: 1;
    min-width: 0;
    justify-content: flex-end;
    text-align: right;
}

/* --- Description-on-hover (Cutler pattern), smoothly animated ---
   We swap the right-column from "CTA visible" to "description visible"
   using the modern grid-template-rows fr trick:

       .wrap { display: grid; grid-template-rows: 1fr; }   ← expanded
       .wrap { display: grid; grid-template-rows: 0fr; }   ← collapsed
       .wrap > * { overflow: hidden; min-height: 0; }

   The 1fr ↔ 0fr transition interpolates smoothly because both are fr
   units — unlike `height: auto` (which can't be transitioned at all)
   or `max-height` (which animates linearly through unused space and
   feels stuttery). The item's overall height grows / shrinks along
   with the description's natural size, so the accordion item itself
   gets taller on hover, just like Cutler.

   Each *-wrap is a thin layout container; the inner .event-cta and
   .event-description spans remain the user-styled targets so the
   widget's typography / colour controls work cleanly. */
.sullys-accordion--describe .sullys-accordion-item .event-right {
    flex-direction: column;
    align-items: flex-end;
    text-align: right;
    flex-shrink: 0;
}
.sullys-accordion--describe .sullys-accordion-item .event-cta-wrap,
.sullys-accordion--describe .sullys-accordion-item .event-description-wrap {
    display: grid;
    transition:
        grid-template-rows 0.5s var(--ease-out-cubic),
        opacity            0.3s var(--ease-out-cubic);
}
.sullys-accordion--describe .sullys-accordion-item .event-cta-wrap > *,
.sullys-accordion--describe .sullys-accordion-item .event-description-wrap > * {
    overflow: hidden;
    min-height: 0;
}

/* Inner stack inside the description wrap: holds the description text +
   the optional Book Now link in a vertical column. Kept as a single
   direct child of .event-description-wrap so the grid-row 0fr↔1fr
   collapse animation has exactly one row to interpolate.

   Desktop: aligns to flex-end so the inner contents continue to sit
   flush with the right edge of the column (matches existing right-align
   layout). Mobile media-query (further down) flips this to flex-start. */
.sullys-accordion--describe .sullys-accordion-item .event-description-inner {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
}

/* Default: CTA expanded, description collapsed */
.sullys-accordion--describe .sullys-accordion-item .event-cta-wrap {
    grid-template-rows: 1fr;
    opacity: 1;
}
.sullys-accordion--describe .sullys-accordion-item .event-description-wrap {
    grid-template-rows: 0fr;
    opacity: 0;
    pointer-events: none;
}

/* Active state — driven by JS class .is-active (NOT :hover), so the same
   visual state can be triggered by mouse hover on desktop AND by scroll
   position on mobile. CTA collapses, description expands → item grows. */
.sullys-accordion--describe .sullys-accordion-item.is-active .event-cta-wrap {
    grid-template-rows: 0fr;
    opacity: 0;
}
.sullys-accordion--describe .sullys-accordion-item.is-active .event-description-wrap {
    grid-template-rows: 1fr;
    opacity: 1;
    transition:
        grid-template-rows 0.5s var(--ease-out-cubic),
        opacity            0.3s 0.1s var(--ease-out-cubic);
}

.sullys-accordion--describe .sullys-accordion-item .event-description {
    display: block;
}

/* "Book Now" link — rendered inside the expanded description-wrap.
   MOBILE-ONLY by design (per Louisa's request): the desktop layout
   uses the cursor-follow image + whole-row click, so the duplicate
   text link would feel redundant. We hide it at desktop sizes by
   default and the mobile media query (further down) flips it back to
   inline-block.

   The element is still rendered in the markup at every breakpoint so
   the Elementor Style → "Book Now" link controls (typography, colour,
   spacing) keep editing the same node — content editors can preview
   their settings on desktop by toggling DevTools to a mobile width.

   Style: thin underline + tight tracking by default; the widget's
   typography / colour controls override these from the Style tab. */
.sullys-accordion-item .event-book-link {
    display: none;                         /* hidden on desktop — see mobile @media for inline-block reveal */
    margin-top: 0.85rem;
    padding: 0.35em 0;
    text-decoration: none;
    border-bottom: 1px solid currentColor;
    line-height: 1.2;
    transition: opacity 0.2s ease;
    pointer-events: auto;                  /* always tappable, even via collapsed wrap (animations don't block real <a>) */
    position: relative;
    z-index: 2;
}
.sullys-accordion-item .event-book-link:hover,
.sullys-accordion-item .event-book-link:focus {
    opacity: 0.75;
    text-decoration: none;
}

/* Mobile reveal of the Book Now link. Declared AFTER the base
   `display: none` so it wins the cascade at equal specificity when
   the media query matches. (The earlier mobile media query block
   higher up in this file would have lost to the base rule because
   CSS resolves equal-specificity rules in source order.) */
@media (max-width: 768px), (hover: none) {
    .sullys-accordion-item .event-book-link {
        display: inline-block;
        align-self: flex-start;
    }
}

/* Last-item trailing line — off by default. When the widget's "Show divider
   below the last event" toggle is on, the wrapper gets the
   sullys-accordion-show-last-yes prefix class and the last item picks up a
   bottom border using the same colour + width vars as the top borders. */
.sullys-accordion .sullys-accordion-item:last-child {
    border-bottom: 0;
}
.sullys-accordion-show-last-yes .sullys-accordion .sullys-accordion-item:last-child,
.sullys-accordion-show-last-yes.sullys-accordion .sullys-accordion-item:last-child {
    border-bottom:
        var(--sullys-accordion-divider-w, 1px)
        solid
        var(--sullys-accordion-divider-color, rgba(255,255,255,0.18));
}


/* ---- Postcard stack (custom widget — drop-in entrance via GSAP) ----
   No Elementor equivalent. Each card is absolutely positioned from the
   stack's centre using CSS custom properties set inline by the widget:
     --card-w, --card-h   → card box dimensions
     --card-x, --card-y   → offset from centre (px)
     --card-r             → resting rotation (deg)
     --card-z             → stack order (z-index)
   Stamp uses --stamp-x / --stamp-y / --stamp-r for the same scheme.
   The visual identity (paper colour, shadow) is layered via Elementor
   controls in the widget. */

.sullys-postcards-block {
    position: relative;
    width: 100%;
}

.sullys-postcard-stack {
    position: relative;
    /* width/height are set by the widget's Layout controls */
    margin-inline: auto;
}

.sullys-postcard {
    position: absolute;
    top: 50%;
    left: 50%;
    width: var(--card-w, 320px);
    height: var(--card-h, 220px);
    margin: 0;
    /* Baseline visual identity — the widget's Style controls override these
       per-instance via Elementor selectors. The static footer fallback in
       parts/footer-block.php relies on these defaults. */
    background-color: #FFFCE9;
    border-radius: 2px;
    box-shadow: 0 20px 40px rgba(0,0,0,0.25);
    /* Resting state — JS overrides for animated cards */
    transform: translate(-50%, -50%) translate(var(--card-x, 0px), var(--card-y, 0px)) rotate(var(--card-r, 0deg));
    transform-origin: center center;
    z-index: var(--card-z, 1);
    overflow: hidden;
    will-change: transform, opacity;
}

.sullys-postcard > img {
    display: block;
    width: 100%;
    height: 100%;
    /* object-fit / object-position are set inline per card */
}

/* When the stack is animated, cards start invisible — GSAP fades them in
   on scroll-trigger. This avoids a flash of the stack at its resting
   position before the drop animation runs. Static stacks (animate=off,
   no data-sullys-postcards attribute) are not affected. */
.sullys-postcard-stack[data-sullys-postcards] .sullys-postcard {
    opacity: 0;
    visibility: hidden;
}

/* Editor canvas: motion scripts are skipped (per is-elementor-editor scope),
   so the GSAP fade-in never runs. Show cards at their resting position so
   designers can see the stack while configuring it. */
body.is-elementor-editor .sullys-postcard-stack[data-sullys-postcards] .sullys-postcard {
    opacity: 1;
    visibility: visible;
}

.sullys-stamp {
    position: absolute;
    top: 50%;
    left: 50%;
    /* width/height come from the Stamp size control */
    transform: translate(-50%, -50%) translate(var(--stamp-x, 0px), var(--stamp-y, 0px)) rotate(var(--stamp-r, 0deg));
    transform-origin: center center;
    pointer-events: none;
}
.sullys-stamp > img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: contain;
}

/* Theme-aware stamp swap.
   The widget renders both .sullys-stamp--default and .sullys-stamp--alt
   only when an alternate stamp is configured; CSS picks the right one
   based on the active theme class on <body>. */
.sullys-stamp--alt { display: none; }
body.theme-red .sullys-stamp--default { display: none; }
body.theme-red .sullys-stamp--alt     { display: block; }


/* ---- Story Cards widget (Sullys_Widget_Story_Cards) ----
   Postcard-stack-style scrapbook layout. Each card is absolutely
   positioned at X / Y from the container's centre, with its own width,
   height, rotation, and per-card caption beneath the image. On mobile
   the container collapses to a flex column and captions become absolute
   overlays on the image (image keeps its rotation).

   Inline custom-properties on the card (set by the widget):
     --card-w / --card-h          card box dimensions in px
     --card-x / --card-y          offset from centre in px
     --card-r                     image rotation in deg
     --card-z                     stack order
     --text-w                     caption width (% of card)
     --text-align                 left / center / right
   Container vars (set by widget):
     --story-cards-h              desktop container height
     --story-cards-mobile-gap     vertical gap between cards on mobile
     --text-gap                   space between image and caption (desktop)
     --mobile-text-bg             optional translucent backdrop on mobile
     --mobile-text-pad            inner padding for mobile overlay caption */

.sullys-story-cards {
    position: relative;
    height: var(--story-cards-h, 720px);
    margin-inline: auto;
    /* width set by widget */
}

.sullys-story-card {
    position: absolute;
    top: 50%;
    left: 50%;
    width: var(--card-w, 320px);
    height: var(--card-h, 220px);
    margin: 0;
    z-index: var(--card-z, 1);
    /* Resting state: centred via -50% / -50% then offset by per-card X / Y.
       JS overrides this transform when animation is active. */
    transform:
        translate(-50%, -50%)
        translate(var(--card-x, 0px), var(--card-y, 0px));
    will-change: transform, opacity;
}

/* The image frame — paper background, tilted to the per-card rotation.
   Frame visual identity (bg, padding, radius, shadow) all come from
   widget Style controls. */
.sullys-story-card-image {
    margin: 0;
    width: 100%;
    height: 100%;
    overflow: hidden;
    transform: rotate(var(--card-r, 0deg));
    transform-origin: center center;
    will-change: transform;
}
.sullys-story-card-image img {
    display: block;
    width: 100%;
    height: 100%;
}

/* Desktop caption — sits below the card box, width % of card, alignment
   per-card. It lives outside the card's height (which equals image
   height) by absolute-positioning it just below.

   Block position is driven by --text-block-x / --text-block-right /
   --text-block-shift, which the widget's text_align control sets via
   Elementor selectors_dictionary. Defaults below mean: text-align: left,
   block at left edge, no horizontal shift. The widget's emitted CSS
   overrides these per-card and per-breakpoint, so mobile-only alignments
   land correctly. */
.sullys-story-card-text {
    position: absolute;
    top: calc(100% + var(--text-gap, 1.25rem));
    left: var(--text-block-x, 0);
    right: var(--text-block-right, auto);
    width: var(--text-w, 100%);
    margin: 0;
    text-align: var(--text-align, left);
    transform: translateX(var(--text-block-shift, 0%));
    white-space: pre-line;
}

/* "Align all caption tops" mode — wrapper gets .sullys-story-cards--aligned.

   Strategy:
   1. Each card's BOX height is extended to the tallest card's height
      (--max-card-h), with the IMAGE inside staying at its own --card-h
      anchored to the top of the box. Empty space sits between image and
      box bottom on shorter cards.
   2. Caption top is overridden to subtract the card's own --card-y, which
      cancels out the vertical shift the card translate applies. Result:
      caption absolute Y depends only on --max-card-h + gap (and the
      container's centre), NOT on the card's individual Y offset — so
      moving any card up or down keeps every caption locked at the same
      horizontal line.

   Math sketch:
     card.top  = centre_y + card_y − max_card_h/2
     text.top  (relative to card) = max_card_h + gap − card_y
     text.absY = card.top + text.top
               = centre_y + card_y − max_card_h/2 + max_card_h + gap − card_y
               = centre_y + max_card_h/2 + gap   ← no card_y, all captions align */
.sullys-story-cards--aligned .sullys-story-card {
    height: var(--max-card-h, var(--card-h, 220px));
}
.sullys-story-cards--aligned .sullys-story-card-image {
    height: var(--card-h, 220px);
}
.sullys-story-cards--aligned .sullys-story-card-text {
    top: calc(
        var(--max-card-h, var(--card-h, 220px))
        + var(--text-gap, 1.25rem)
        - var(--card-y, 0px)
    );
}

/* Animated mode — cards start invisible until JS fades them in. Editor
   canvas overrides this so designers can see + arrange the layout. */
.sullys-story-cards[data-sullys-story-cards] .sullys-story-card {
    opacity: 0;
    visibility: hidden;
}
body.is-elementor-editor .sullys-story-cards[data-sullys-story-cards] .sullys-story-card {
    opacity: 1;
    visibility: visible;
}


/* Mobile-specific overrides for Story Cards REMOVED 2026-05-02. The
   mobile composition (image-text scrapbook scenes) is now its own widget
   — "Story Stack — Mobile" (Sullys_Widget_Story_Stack) — see the block
   below. Story Cards is desktop+tablet only; pair it with the mobile
   widget by setting Story Cards → Advanced → Responsive → Hide on Mobile,
   and dropping a Story Stack widget in its place (which auto-hides
   above the mobile breakpoint via CSS). */


/* ---- Story Stack — Mobile widget (Sullys_Widget_Story_Stack) ----
   Vertical scrapbook stack of self-contained scenes. Each scene is a
   bounding box with an image and a caption that can each be positioned
   independently from the scene's centre.

   Inline custom-properties (set inline by the widget per scene):
     --scene-h         scene bounding-box height (px)
     --img-w / --img-h image dimensions (px)
     --img-x / --img-y image offset from scene centre (px or %)
     --img-r           image rotation in deg (animated by JS from 0 → r)
     --text-w          text block width (px or %)
     --text-x / --text-y text block offset from scene centre

   Auto-hides above the mobile breakpoint (641px+) so the widget can be
   dropped on every page once and never accidentally show on desktop.
   Editor canvas overrides this so designers can preview the layout
   without resizing the viewport. */
.sullys-story-stack {
    display: flex;
    flex-direction: column;
    /* gap, max-width, margin set by widget controls */
}

@media (min-width: 641px) {
    body:not(.is-elementor-editor) .sullys-story-stack {
        display: none;
    }
}

.sullys-story-scene {
    position: relative;
    width: 100%;
    height: var(--scene-h, 400px);
    will-change: transform, opacity;
}

.sullys-story-scene-image {
    position: absolute;
    top: 50%;
    left: 50%;
    width: var(--img-w, 260px);
    height: var(--img-h, 200px);
    margin: 0;
    transform:
        translate(-50%, -50%)
        translate(var(--img-x, 0px), var(--img-y, 0px))
        rotate(var(--img-r, 0deg));
    transform-origin: center center;
    overflow: hidden;
    will-change: transform;
    /* bg, padding, radius, shadow set by widget Style controls */
}
.sullys-story-scene-image img {
    display: block;
    width: 100%;
    height: 100%;
}

.sullys-story-scene-text {
    position: absolute;
    top: 50%;
    left: 50%;
    width: var(--text-w, 60%);
    margin: 0;
    transform:
        translate(-50%, -50%)
        translate(var(--text-x, 0px), var(--text-y, 0px));
    transform-origin: center center;
    z-index: 2;
    white-space: pre-line;
    /* text-align, color, typography set by widget controls */
}

/* Animated mode — scenes start invisible until JS fades them in.
   Editor canvas keeps them visible so the layout's editable. */
.sullys-story-stack[data-sullys-story-stack] .sullys-story-scene {
    opacity: 0;
    visibility: hidden;
}
body.is-elementor-editor .sullys-story-stack[data-sullys-story-stack] .sullys-story-scene {
    opacity: 1;
    visibility: visible;
}


/* ---- Big Nav widget (Sullys_Widget_Big_Nav) ----
   Layout-only baseline: a flex container that wraps inline links and
   their bullet separators. Typography, colours, gap, alignment, and
   separator size are all owned by the widget controls and emitted via
   Elementor selectors. The reused .sullys-big-nav and .sullys-bullet
   classes pick up the existing site-wide styles (gradient hover wipe,
   star colour) — this wrapper just gives them a flow container. */
.sullys-big-nav-wrap {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    /* column-gap / row-gap / justify-content / max-width are set by widget */
    line-height: var(--lh-tight, 1.05);
}
.sullys-big-nav-wrap .sullys-bullet {
    display: inline-flex;
    align-items: center;
    /* font-size, transform (Y offset), and color come from widget controls.
       Falls back to var(--accent-hot) via the existing .sullys-bullet rule
       in pages.css if no separator colour is set. */
}

/* Image-mode separators: the <img> sizing comes from the widget's
   "Separator size" control (height + width:auto). Block layout, no
   baseline drift. */
.sullys-big-nav-wrap .sullys-bullet--image {
    /* Reset font-size so empty text doesn't add a baseline strut */
    font-size: 0;
    line-height: 1;
}
.sullys-big-nav-wrap .sullys-bullet--image img {
    display: block;
    height: 1em;        /* widget's sep_size selector overrides */
    width: auto;
    max-width: none;    /* defeat global img max-width:100% */
}

/* Theme-aware swap for image separators (mirrors the postcard stamp).
   Both variants are emitted only when an inverted image is uploaded.
   When only the default image is set, we don't render the inverted
   span at all, so the rule below is a no-op for that case.

   Specificity note: the base rule `.sullys-big-nav-wrap .sullys-bullet`
   sets display:inline-flex with specificity 0,2,0. To hide --inverted
   on the default theme we need at least matching specificity, hence
   the wrap-prefixed selector below. */
.sullys-big-nav-wrap .sullys-bullet--inverted          { display: none; }
body.theme-red .sullys-big-nav-wrap .sullys-bullet--default  { display: none; }
body.theme-red .sullys-big-nav-wrap .sullys-bullet--inverted { display: inline-flex; }

/* Hide separators that bridge a wrapped row.
   Sullys.bigNav (main.js) walks the wrap, compares offsetTop of the
   bullet's flanking items, and toggles .is-cross-row on bullets at the
   row boundary so the separator only appears between two items on the
   same line. visibility:hidden (not display:none) keeps the separator's
   width in the layout — prevents a feedback loop where hiding the
   separator lets the next item pull back up to the previous row. */
.sullys-big-nav-wrap .sullys-bullet.is-cross-row {
    visibility: hidden;
}


/* ---- Scrolling marquee banner ----
   Marquee Banner widget (Sullys_Widget_Marquee). The whole banner can be
   a clickable anchor; .sullys-marquee is either an <a> or a <div>. The
   inner .sullys-marquee-track holds repeated text + separators and is
   animated indefinitely with translateX 0 → -50%. The widget repeats the
   content N times so the visible width is always covered.
   Background, padding, gap, typography, and animation speed are all
   widget-controlled — defaults below are the existing brand look. */
.sullys-marquee {
    display: flex;
    overflow: hidden;
    background: var(--banner-bg);
    color: var(--banner-fg);
    padding: var(--space-3) 0;
    white-space: nowrap;
    text-decoration: none;        /* defeat default <a> underline when linked */
}
.sullys-marquee:hover,
.sullys-marquee:focus {
    text-decoration: none;
    color: var(--banner-fg);      /* keep colour stable across states */
}
.sullys-marquee-track {
    display: inline-flex;
    align-items: center;
    gap: var(--space-6);
    animation: sullys-marquee-scroll 40s linear infinite;
    will-change: transform;
}
@keyframes sullys-marquee-scroll {
    from { transform: translateX(0); }
    to   { transform: translateX(-50%); }
}

/* Image-mode separators in the marquee (mirrors the big-nav pattern).
   Theme-aware swap: only the active theme's variant is rendered. */
.sullys-marquee-track .sullys-bullet--image {
    font-size: 0;
    line-height: 1;
    display: inline-flex;
    align-items: center;
}
.sullys-marquee-track .sullys-bullet--image img {
    display: block;
    height: 1em;        /* widget's sep_size selector overrides */
    width: auto;
    max-width: none;
}
.sullys-marquee-track .sullys-bullet--inverted          { display: none; }
body.theme-red .sullys-marquee-track .sullys-bullet--default  { display: none; }
body.theme-red .sullys-marquee-track .sullys-bullet--inverted { display: inline-flex; }


/* ---- Theme-show / theme-hide utility classes ----
   Drop one of these classes into any Elementor widget's
   Advanced → CSS Classes field. The widget renders only on the matching
   theme; on the other theme it's hidden via display: none.

   Use case: when a Text Editor widget needs DIFFERENT inline images per
   theme (because the assets don't recolour cleanly), duplicate the
   widget — one with .sullys-only-blue, one with .sullys-only-red — and
   each will appear only on its theme. The theme switcher on the page
   flips them instantly. */
body.theme-red  .sullys-only-blue,
body.theme-blue .sullys-only-red {
    display: none !important;
}
