/**
 * SageNet Custom Functionality — CSS
 * Part of the SageNet child theme (moved here from the now-retired
 * sagenet-custom-functionality plugin, 2026-08-05). Loaded by
 * inc/custom-functionality.php on the homepage (Industry Stack) and on
 * any page whose content contains the Process Steps, Sticky Sub-Nav,
 * Why SageNet Graphic, or Accordion Image Swap markup — see that
 * file's sagenet_custom_functionality_page_needs_assets() for the
 * actual condition.
 */

/* ====================================================================
   1. INDUSTRY STACK — Sticky Scroll Effect (homepage)
   --------------------------------------------------------------------
   Markup expectations:
   - A row with class "industry-stack-heading" (the "Powering What's
     Possible for Multi-Site Enterprises" H2 + orange underline),
     directly above the stack in source order.
   - A parent row with class "industry-stack" wrapping 3 child rows.
   - Each child row has class "industry-row" PLUS one modifier class
     identifying which industry it is: industry-row--retail,
     industry-row--qsr, industry-row--cstore (in that source order).
   Behavior (revised 2026-07-23 per client, then corrected same day):
   the heading pins to the true top of the viewport for exactly the
   duration of the retail/qsr/cstore handoff sequence — NOT
   permanently. Each industry row pins just BELOW the heading (not at
   the viewport's true top) while the next row scrolls up and covers
   it, same handoff mechanic as before, only the reference line moved
   down by the heading's height. Once the last row (cstore) has fully
   arrived and taken over covering everything beneath the heading, the
   heading unpins too and scrolls away together with it as one unit —
   it must never keep sticking past that point over page content
   below the stack. The last row itself is never pinned — it scrolls
   past normally, same as before. See assets/js section 1 for the pin
   logic (heading is pinned via GSAP now, not CSS position:sticky —
   see the note below on why that changed); this CSS just sets up the
   stacking box model.
   ==================================================================== */

/* CORRECTED 2026-07-23: originally implemented as CSS position:sticky
   with no bottom boundary, which stuck to the top of the viewport
   forever — including over page content well past the end of the
   stack, which is wrong (Laura: "it needs to disappear and keep
   scrolling once the last row has scrolled up"). Plain sticky has no
   clean way to say "unstick exactly when this OTHER element (the last
   pinned row) reaches this line" — its stick/unstick range is only
   ever bounded by ITS OWN parent's box. Switched to a GSAP
   ScrollTrigger pin instead (assets/js section 1, same mechanism
   already used for the industry rows), which supports a separate
   endTrigger element — lets the heading's pin end precisely at the
   scroll position where the LAST row (cstore — never itself pinned)
   arrives and takes over covering everything beneath the heading.
   (First attempt used qsr, the last PINNED row, as that anchor since
   it's the same line — that's what broke retail/qsr's pinning too,
   not just the heading's, most likely from pointing at an element
   that's simultaneously another pin's own trigger. cstore has no pin
   of its own, so it's a clean, independent anchor for the same
   instant.) This is now just the plain box the row sits in; GSAP
   applies position:fixed directly when pinning, same as it does for
   .industry-row.
   The heading's own ScrollTrigger pin (assets/js section 1) wraps it
   in a hand-sized spacer div (".industry-stack-heading-spacer",
   created by JS, not present in Kadence/this stylesheet) instead of
   using either of GSAP's own pinSpacing options — pinSpacing:false
   let its height briefly vanish from the document the instant it
   pinned, compounding with retail/qsr/cstore's own collapses and
   breaking their thresholds ("retail reappearing/sliding out from
   behind qsr, kludgy"); pinSpacing:true way overcorrected, sizing the
   spacer to the ENTIRE retail+qsr scroll distance instead of just the
   heading's own height, which broke everything ("nothing sticking at
   all"). See assets/js section 1 for the full explanation — nothing
   to change here in CSS, just documenting why a wrapper div you didn't
   put there shows up around this row in devtools.
   z-index must still beat every .industry-row below (highest is
   cstore at 3) so the heading never visually gets covered while it's
   pinned. */
.industry-stack-heading,
.industry-stack-heading .kt-row-column-wrap {
	overflow: visible;
}

.industry-stack-heading {
	position: relative;
	z-index: 10;
	/* CONFIRMED LIVE (2026-07-23) as the cause of "retail scrolling up
	   underneath the headline" — this row had NO background at all
	   (fully transparent). Z-index alone doesn't hide anything;
	   z-index only controls paint ORDER, and a transparent element
	   still lets whatever's behind it (retail, mid-transition back to
	   normal flow after its own pin ends — see assets/js section 1)
	   show straight through. retail's own "unpin" moment no longer
	   coincides with leaving the viewport now that the pin line moved
	   down by the heading's height (it did in the original, no-heading
	   version, which is why this was never an issue before) — retail
	   stays visible sliding through the strip directly under the
	   heading for a while after qsr takes over. Once this row is
	   actually opaque, that no longer matters: heading + qsr together
	   fully mask retail's entire remaining height for its whole
	   lingering-visible window, confirmed live with zero gaps.
	   Matches the page's own base background (confirmed via devtools:
	   rgb(235, 235, 235)) rather than a Kadence global palette color —
	   if the design ever uses a named/global color for this instead,
	   swap it in here. */
	background-color: #ebebeb;
}

/* Kadence clips overflow by default, which would hide the pinned
   rows once GSAP takes them out of normal document flow. */
.industry-stack,
.industry-stack > .kt-row-column-wrap {
	overflow: visible !important;
}

/* Rows are sized to their actual content (the two-column grid
   already stretches both columns to match the taller one, usually
   the photo) — NOT forced to min-height:100vh. That forced height
   used to leave a stretch of empty background below the real content
   before the next row could arrive, since the pin duration is tied
   to the row's own height (see JS section 1). If a row ever needs to
   look taller, add height to its actual content (e.g. a taller image
   or more copy), not a min-height override here — the pin timing
   will follow automatically. */
.industry-row {
	position: relative;
	will-change: transform;
	/* FOUND LIVE VIA DEVTOOLS (2026-07-23) — the actual root cause of
	   every remaining glitch in this section (heading releasing at the
	   wrong point, retail/qsr handoff going "kludgy"): Kadence applies
	   its own global block-gap spacing to each row as margin-bottom
	   (confirmed 48px on this site). This whole section's math assumes
	   each row's bottom edge and the next row's top edge are the SAME
	   line — retail's "fully covered" instant IS qsr's "arrived"
	   instant, and qsr's end IS the heading's release point. A real
	   48px gap between rows meant those were never actually the same
	   scroll position, which is what was throwing off both the row
	   handoff AND (once added) the heading's endTrigger math, even
	   though each one's own trigger math was individually correct.
	   First fix (same day) zeroed this out entirely to force that
	   shared-line invariant — that fixed the handoff, but per Laura
	   it also left retail/qsr/cstore visibly bumping edge-to-edge with
	   no breathing room between them.
	   REVISED 2026-07-23 (later same day): restored real spacing via
	   --industry-row-gap below, but the shared-line invariant above
	   still has to hold for the handoff to work — so this is no longer
	   a bare margin, it's a value the JS pin math reads back (assets/js
	   section 1: `getComputedStyle(row).marginBottom`) and subtracts
	   from each row's "end" threshold, delaying its unpin by exactly
	   this many px so it releases when the NEXT row's top (which sits
	   this same distance further down, past the gap) reaches the pin
	   line — not when its own bottom does. Change the value here only;
	   the JS side stays correct automatically since it reads this same
	   property live. !important because Kadence's own block-gap rule
	   is likely inline or otherwise higher-specificity. */
	--industry-row-gap: 48px;
	margin-bottom: var(--industry-row-gap) !important;
}

/* Stacking order so each row slides OVER the previous one as it
   scrolls up. Must match source order (retail, then qsr, then
   cstore) — if rows are ever reordered in Kadence, update these too. */
.industry-row--retail { z-index: 1; }
.industry-row--qsr    { z-index: 2; }
.industry-row--cstore { z-index: 3; }
.industry-row--finance { z-index: 4; }
.industry-row--energy    { z-index: 5; }


/* ====================================================================
   2. PROCESS STEPS — Sequential Left-to-Right Reveal
   --------------------------------------------------------------------
   Lives on the Managed Digital page (/managed-digital/), NOT the
   homepage.
   Markup expectations:
   - A row with id="process" containing 5 columns, each with class
     "process-card" (Plan, Deploy, Operate, Optimize, Scale, in that
     source order).
   - A connector image nested INSIDE columns 1-4 (not a 6th/7th/8th/9th
     sibling column — that would break the 5-column grid and Kadence's
     mobile stacking). Each is a Kadence Image (Adv) block (real .webp
     asset, e.g. process-arrow.webp — not an icon-library icon) with
     class "process-arrow".
   - IMPORTANT: Kadence's Image (Adv) block has no Position: Absolute
     control in its Advanced tab (unlike Row/Column blocks) — there's
     nothing to set on the block itself for positioning. Absolute
     positioning + vertical centering is done entirely in THIS
     stylesheet via the .process-card / .process-arrow rules below, so
     don't set manual margin offsets on the block to nudge it into
     place — that fights the CSS below and won't transfer to the next
     arrow (or the next cloned page) since it's tuned to one specific
     card's rendered height. Just add the "process-arrow" class in
     Advanced > CSS Classes and leave Margin at its default (blank).
   - Kadence's Image block Advanced tab has no responsive visibility
     toggle, so hiding it below the desktop breakpoint is handled here
     via the media query below instead — that's the other reason this
     needs the "process-arrow" CSS class in the first place.
   Behavior: cards slide in from the left, arrows pop in with a scale
   effect between each pair of cards, all scrubbed directly to scroll
   position (not a fixed-duration autoplay) — scrolling down plays the
   sequence forward, scrolling back up plays it in reverse. See
   assets/js section 2 for the actual animation; this CSS only
   handles sizing/overflow/responsive visibility.
   ==================================================================== */

/* Same overflow issue as the industry stack — cards start off-screen
   (opacity:0, translated left) before GSAP animates them in, and
   Kadence's default overflow would clip that offset position. */
#process,
#process .kt-row-column-wrap,
#process .kt-row-layout-inner {
	overflow: visible;
}

/* Anchor point for the absolutely-positioned .process-arrow below.
   Kadence's column/Section wrapper doesn't set position:relative by
   default, so without this the arrow would position itself relative
   to some further-up ancestor instead of its own card. */
.process-card {
	position: relative;
}

/* Absolutely positioned, anchored to a FIXED distance from the top of
   the card (not top:50%) so it lines up with the title text, not the
   vertical center of the whole card — card height varies with how
   much body copy each step has, but the badge+title stack at the top
   is the same size on every card, so a fixed offset stays aligned
   with the title everywhere while top:50% would drift depending on
   body copy length. right:0 + translateX 50% puts the arrow's
   horizontal center exactly on the seam between this column and the
   next one.
   IMPORTANT (found via devtools 2026-07-08): don't set "Align: Right"
   on the arrow's Image (Adv) block. That adds WordPress/Kadence's
   built-in ".alignright" class to the inner <figure>, which carries
   its OWN float:right/position:relative/margin rule from Kadence's
   base styles — stacking a second positioning system on top of this
   one and made every number below impossible to reason about until
   it was removed. ("Animate on Scroll" was a red herring — confirmed
   off, not the cause.) Once Align:Right is off, this rule is the ONLY
   thing positioning the arrow.
   top: 70px / right: 20px / width: 80px confirmed via devtools
   (2026-07-08) to already render correctly once Align:Right is
   removed — don't jump to a bigger change without checking these
   first. */
.process-arrow {
	position: absolute;
	top: 135px;
	right: -50px;
	width: 80px;
	transform: translateY(-50%);
	z-index: 2;
}

.process-arrow img {
	display: block;
	max-width: 100%;
	height: auto;
}

/* Card title underline. Moved here from the Customizer's Additional
   CSS (2026-07-08) to keep all SageNet custom CSS in this one plugin
   instead of split across two places — see
   sagenet-custom-code-architecture-preference in project notes.
   The original rule put border-bottom directly on
   .kt-blocks-info-box-title itself. That title is a block-level
   heading that stretches to fill the card's full width, so the border
   rendered edge-to-edge instead of the short, fixed-width line in the
   design. Fix: put the border on a ::after pseudo-element with its
   own explicit width instead of on the title element — that decouples
   the line's width from the title's box width (and from how long the
   title text is) entirely. Centered (left:50%/translateX(-50%)) to
   match the title text's center alignment — was left:0, which sat
   flush left instead of under the centered title.
   Width bumped 40px -> 64px per feedback (too short vs. the design
   comp) — still a guess, adjust to match. */
#process .kt-blocks-info-box-title {
	position: relative;
	padding-bottom: 10px;
}

#process .kt-blocks-info-box-title::after {
	content: '';
	position: absolute;
	left: 50%;
	bottom: 0;
	width: 140px;
	transform: translateX(-50%);
	border-bottom: 2px solid #d2d2d2;
}

/* Hide on tablet AND mobile per Laura's request (was mobile-only at
   767px). 1024px is Kadence's own tablet breakpoint, so this lines up
   with where Kadence's own responsive behavior kicks in. Kadence's
   Image block has no built-in responsive visibility control in its
   Advanced tab, so this media query is the mechanism doing the
   hiding — just add the "process-arrow" class in the block's
   Advanced > CSS Classes field and this takes care of it
   automatically. */
@media (max-width: 1024px) {
	.process-arrow {
		display: none;
	}
}


/* ====================================================================
   3. STICKY SUB-NAV — Scrollspy
   --------------------------------------------------------------------
   Lives on Managed Digital (id="sticky-nav" row, built by Laura in
   Kadence — 6 buttons/links: Overview, Expertise, Process, Why,
   Markets, Resources). Each link's href is an anchor (#overview,
   #expertise, #process, #why, #markets, #resources) pointing at a
   matching section id elsewhere on the page.
   Behavior: the row sticks to the top of the viewport as you scroll
   (pure CSS, position:sticky — no JS needed for the stick itself). JS
   section 3 watches scroll position and toggles ".is-active" on
   whichever link's target section is currently in view; the rule
   below turns that link's text orange.
   KNOWN RISK: position:sticky silently breaks (element just scrolls
   away like normal, no error in console) if ANY ancestor between
   #sticky-nav and <body> has overflow:hidden/auto/scroll, a
   transform, or a filter — those create a new containing block that
   sticky can't escape. Kadence wraps rows in .kt-row-column-wrap /
   .kt-row-layout-inner, which is why those get the same
   overflow:visible override already used for Industry Stack/Process
   Steps below. If sticky still doesn't hold after this, the next
   place to check is the theme's own wrapper (.entry-content, #page,
   etc.) for overflow or transform rules.
   NOT handled here: #sticky-nav's own background color. Once pinned,
   it needs an opaque background or page content will show through
   behind it — that's a design choice, set it on the row itself in
   Kadence (Row > Background), not in this stylesheet.
   ==================================================================== */

#sticky-nav,
#sticky-nav .kt-row-column-wrap,
#sticky-nav .kt-row-layout-inner {
	overflow: visible;
}

#sticky-nav {
	position: sticky;
	top: 0;
	z-index: 100;
}

/* Text-only active state per Laura's call (no underline/background).
   Uses Kadence's Global Palette 1 (confirmed by Laura as the brand
   orange) via its CSS custom property rather than a hardcoded hex, so
   this automatically stays correct if the palette color is ever
   adjusted in the Customizer. Kadence exposes global palette colors
   as --global-palette1 through --global-palette9 — I'm confident in
   that variable name from Kadence's standard setup, but I can't see
   this live site's rendered CSS myself, so double-check in devtools
   that #f7941d-style placeholder color is gone and this actually
   resolves to the right orange. If nothing shows (var not defined),
   the fallback keeps the old placeholder guess visible so it's
   obvious something's off rather than failing silently to no color
   at all. */
#sticky-nav a.is-active {
	color: var(--global-palette1, #f7941d);
}

/* Anchor-jump landing offset: without this, clicking a nav link (or
   loading the page with a #hash in the URL) scrolls the target
   section's top flush to the very top of the viewport — which is now
   HIDDEN under the pinned #sticky-nav row. scroll-margin-top pushes
   the landing spot down by the nav's real height instead.
   --sticky-nav-height is set by JS section 3 from the row's actual
   rendered height (not hardcoded), so this stays correct even if the
   row wraps to two lines on a smaller screen — the 60px fallback only
   matters for the brief moment before that JS runs.
   scroll-behavior:smooth makes the native href jump animate instead
   of snapping instantly. Note this applies to ANY same-page hash link
   while this stylesheet is loaded, not just the sticky-nav ones — fine
   here since nothing else on this page currently uses hash anchors,
   but worth knowing if that ever changes. */
html {
	scroll-behavior: smooth;
}

#overview,
#expertise,
#process,
#why,
#markets,
#resources {
	scroll-margin-top: var(--sticky-nav-height, 60px);
}


/* ====================================================================
   4. WHY SAGENET GRAPHIC — Monitor Reveal (one-time slide-out)
   --------------------------------------------------------------------
   *** ON HOLD as of 2026-07-08 — read this before touching anything
   below. *** Laura is tabling this section for now rather than
   abandoning it: the 7 cropped card images weren't good enough yet
   (some plain rectangular crops looked clipped/messy, a background-
   removal pass fixed 6 of 7 but not snack-shelf-display — see
   [[sagenet-custom-functionality-plugin]] memory for the full blow-
   by-blow), and she's now considering a different layout concept
   entirely (monitor slides in from the right as the focal point,
   other images pop up on/around it afterward, rather than this
   version's "everything slides out from behind a centered monitor").
   She's waiting on better cutout images from her designer before
   deciding. In the meantime she's replacing this section in Kadence
   with a single plain static image (the original composite mockup,
   non-animated) — NOT removing the "why-sagenet-graphic" class
   markup necessarily, so don't assume the class is gone from the
   page. All the code below is left in place, working, and ready to
   resume from — nothing here needs fixing or removing on its own,
   this note is just so a future session (or Laura returning to this)
   doesn't mistake "paused" for "broken" or start rebuilding from
   scratch. When picked back up: confirm the layout concept (this
   slide-out-from-monitor version, or the new "monitor arrives first"
   idea) before touching the animation, and get final cutout images
   before touching the CSS positioning rules below.

   First lives on the Managed Digital page's "Why SageNet" section
   (the digital-signage use-case graphic: menu boards, kiosks, gas
   pump, retail display, billboard around the SageVision monitor).
   Built to be REUSED on the other 2 service-page graphics Laura
   mentioned (2026-07-08) — same monitor + surrounding-images idea,
   different images each time.

   REVISED 2026-07-08 after Laura flagged that manually dialing in
   Kadence's Position: Absolute top/left/right/bottom fields per card
   (trial-and-error against the preview) would be a nightmare to set
   up and to keep matching the design. All positioning now lives HERE
   in CSS instead, as percentages measured directly from Laura's
   original composite mockup — Kadence's Position: Absolute control
   is NOT used at all for this effect anymore.

   WHAT LAURA/CONTRACTORS ACTUALLY DO IN KADENCE (this is now the
   whole job):
   1. One Kadence COLUMN. Give it the class "why-sagenet-graphic".
   2. Inside that column, add 8 plain Kadence Image blocks — no
      Position: Absolute, no Align, no manual margin, leave every
      Kadence positioning control at its default. Order doesn't
      matter, CSS below handles stacking via position:absolute.
        - The monitor image: class "wsg-monitor"
        - Each of the 7 card images: class "wsg-card" PLUS one more
          class matching that image's filename slug (see the list
          below) — e.g. the QSR menu board image gets
          class="wsg-card wsg-card--qsr-menu-board".
   That's the entire markup contract. No numbers to enter, nothing to
   eyeball against the preview — every card's exact position/size is
   already set below, measured from the original mockup (1536x1024
   canvas) and converted to percentages so it scales with the column's
   actual rendered width instead of being locked to one pixel size.

   Percentages were measured from where each card sits in Laura's
   original composite image, not guessed — should already land very
   close to the design without adjustment. If any card still looks
   off once live, tweak that ONE card's left/top/width/height below;
   don't reintroduce per-block Kadence positioning.

   HOW THE ANIMATION STILL WORKS: JS section 4 doesn't know or care
   that these positions are now hardcoded in CSS instead of set in
   Kadence — it reads each card's REAL rendered position (wherever
   this CSS puts it) vs. the monitor's, and animates it in from that
   direction automatically. Nothing in JS section 4 needed to change
   for this rework.

   REUSING ON THE NEXT 2 SERVICE PAGES: same recipe — one column
   classed "why-sagenet-graphic", a "wsg-monitor" image, and this
   page's own card images each classed "wsg-card wsg-card--<slug>".
   The slugs/percentages for THIS page's 7 cards are below; a future
   page's different images need their own new .wsg-card--<slug> rules
   added here (with that page's own measured percentages) — copy this
   block as the template, don't reuse these exact numbers for
   different images.

   MOBILE / TABLET (<=1024px): deliberately NOT hidden the way
   .process-arrow is (see section 2) — these are real content images
   (menu boards, kiosks, etc.), not a decorative connector, so hiding
   them on mobile would drop actual content. Instead, JS section 4
   skips the slide animation below 1024px entirely and every .wsg-card
   still renders at its normal CSS position/opacity, just without the
   scroll-triggered motion. If Laura wants a different (e.g. stacked)
   mobile layout instead of the same absolute layout shrunk down,
   that needs its own mobile-specific CSS rules added here — ask
   before assuming which stacking order/spacing she wants.
   ==================================================================== */

.why-sagenet-graphic,
.why-sagenet-graphic .kt-row-column-wrap,
.why-sagenet-graphic .kt-row-layout-inner {
	overflow: visible;
}

/* aspect-ratio matches the original 1536x1024 mockup canvas so every
   percentage value below maps back to the same proportions regardless
   of how wide this column actually renders — resize the column in
   Kadence and everything scales together instead of drifting apart. */
.why-sagenet-graphic {
	position: relative;
	width: 100%;
	aspect-ratio: 1536 / 1024;
}

/* Both the monitor and every card are position:absolute now (monitor
   included — it's no longer "normal flow" like the original version
   of this section had it), since everything's percentage-placed
   against the same .why-sagenet-graphic box. object-fit:contain on
   the actual <img> keeps each cropped photo's own aspect ratio
   intact inside its box instead of stretching to fill it — the
   percentages below were measured off the same crop rectangles as
   the images themselves, so this should sit flush with little to no
   letterboxing.

   STACKING ORDER (2026-07-09, final call): cards in front (z-index 2),
   monitor behind (z-index 1), connector dots bottommost (z-index 0).
   EARLIER same-day attempt had cards deliberately overlapping the
   monitor for a denser look — Laura's live check found that overlap
   looked bad because these are plain rectangular photos with white
   backgrounds, not transparent cutouts, so an overlapping edge showed
   a visible white corner cutting across the monitor. Rather than wait
   on transparent source images (an earlier, still-open ask — see the
   ON-HOLD history above), the LAYOUT below was redesigned instead so
   nothing needs to overlap anything: monitor anchored top-left as the
   dominant focal element, 7 cards fanned out toward the bottom-right
   at their own distinct angle from the monitor so none of them are
   physically touching. z-index order is kept as-is (cards technically
   above monitor) mostly for safety against float rounding at odd
   viewport widths — with real gaps everywhere it no longer matters
   which layer is "on top" of which in practice. */
.wsg-monitor,
.wsg-card {
	position: absolute;
}

/* !important on sizing/fit here on purpose (2026-07-08): Kadence's own
   Image block ships its own crop/fit behavior (the block's "Image
   Size"/crop-ratio setting), which was fighting these rules and
   showing as photos looking clipped/cropped wrong compared to Laura's
   original mockup. Rather than requiring every card's block settings
   to be manually checked/reset in Kadence, these !importants just
   guarantee OUR sizing always wins regardless of whatever crop
   setting a block happens to have — one less thing to configure per
   card. If a photo still looks clipped after this, the more likely
   cause is that CARD's percentages not matching its actual image
   crop (see the per-card rules below), not the block's own settings.

   REMOVED 2026-07-08 (Laura): border-radius + box-shadow used to be
   here to recreate a rounded "card" look. Pulled per Laura — with
   non-transparent rectangular photos, the shadow read as a messy
   white box sitting behind each image rather than a clean card, and
   she's moving toward transparent cutouts instead of this rectangular
   card treatment anyway (see the WHY SAGENET GRAPHIC — ON HOLD note
   at the top of this section). If a rounded-card look is wanted again
   later, re-add border-radius + box-shadow here, but pair it with
   actual transparent-background source images this time, not
   rectangular photos — that combination is what caused the messy
   white-box look in the first place, not the border-radius/shadow
   values themselves. */
.wsg-monitor img,
.wsg-card img {
	display: block !important;
	width: 100% !important;
	height: 100% !important;
	object-fit: contain !important;
}

.wsg-monitor {
	/* REPOSITIONED 2026-07-09 (third pass): moved from center-of-graphic
	   to top-left anchor per Laura — "what if the monitor was in the
	   upper left corner and we had them all sprouting out to the bottom
	   right instead... this way the monitor can be the main focus."
	   Sized as the single biggest element in the graphic on purpose
	   since it's the actual product (SageVision) and everything else
	   exists to orbit it, not compete with it for space.
	   BUMPED ~8% (fourth pass, same day) per Laura's live-look feedback
	   ("make the monitor a little larger... everything just slightly
	   larger") — from 40/48% to 43/51%. All 7 cards below were bumped
	   ~8% too and the whole layout re-solved together (see the cards'
	   comment) since growing one box can reopen an overlap or a trail-
	   crossing with any other.
	   NOTE: still can't fix source-image blur with CSS sizing — if the
	   monitor screenshot looks soft, that needs a higher-res source
	   export. */
	left: 2%;
	top: 4%;
	width: 43%;
	height: 51%;
	z-index: 1;
}

/* SAFETY NET (added 2026-07-08 after the retail card rendered full-
   bleed over the whole graphic on first live look): a bare .wsg-card
   with no matching .wsg-card--<slug> modifier (missing/mistyped class
   in Kadence) has no top/left/width/height of its own. Combined with
   the img above being width:100%/height:100%, an unconstrained
   position:absolute box with a 100%-sized child can get computed by
   the browser as filling the nearest positioned ancestor
   (.why-sagenet-graphic) — which is exactly what "covers the entire
   space" looks like. This fallback box (small, roughly centered) means
   a missing/mistyped modifier class now fails SMALL and obviously
   wrong instead of failing by swallowing the whole section — much
   easier to spot in Kadence's block list ("why is this one tiny and
   dead center") and there's zero chance of it hiding the other cards
   behind it. Every card should have a matching modifier rule below
   overriding these; if one doesn't, that's the bug to go fix on that
   block's CSS Classes field, not a reason to change these numbers. */
.wsg-card {
	z-index: 2;
	top: 40%;
	left: 40%;
	width: 20%;
	height: 20%;
	will-change: transform, opacity;
}

/* Managed Digital's 7 cards. History: original percentages (2026-07-08)
   were measured off Digital-Image (1).png (the composite mockup);
   2026-07-09's second pass pulled them inward to deliberately overlap
   the monitor for a denser look — SUPERSEDED same day (third pass,
   below) once that overlap turned out to look bad with non-transparent
   photos (white corners cutting across the monitor — see the stacking-
   order note above .wsg-monitor/.wsg-card).

   THIRD PASS (2026-07-09, current) — full redesign to "monitor top-left,
   cards cascade out toward the bottom-right, nothing touches": these
   are NOT measured off any mockup anymore, they're computed. With the
   monitor occupying a large chunk of the top-left (see .wsg-monitor)
   and 7 more boxes needing to fit in the remaining space with zero
   overlap AND without any connector dot trail (see the CONNECTOR DOTS
   section below) accidentally passing visually through some OTHER
   unrelated card on its way from the monitor — a straight monitor-to-
   card line can clip a third box that happens to sit in between, which
   isn't obvious just from checking pairwise overlaps — every card here
   was assigned its own distinct angle out from the monitor's center
   (spaced ~18° apart, sweeping from just above due-right around to
   just past due-down) and a distance chosen so its box neither touches
   a neighbor nor blocks/gets blocked by another card's straight-line
   trail to the monitor. Verified programmatically (not eyeballed) —
   zero pairwise overlaps, zero trail-crossings, everything inside the
   0-100% canvas. Card sizes vary on purpose (Laura: "you can make the
   sizes of the images vary") — billboard is the largest as the
   "closing" bottom-right anchor, the rest vary more modestly.

   If a card ever needs to move, don't just nudge one number by eye —
   the safe positions here were solved as a set; changing one card's
   box can reopen an overlap or a trail-crossing with a DIFFERENT card
   that isn't obviously related to the one you're touching.

   FOURTH PASS (2026-07-09, same day) — bumped every card ~8% per
   Laura ("I wouldn't mind everything just being slightly larger"),
   paired with the monitor's own ~8% bump above. Re-solved as a full
   set again (same angles/ordering as the third pass, sizes and
   distances increased, re-verified zero overlaps / zero
   trail-crossings) rather than just scaling the old numbers, since a
   uniform scale-up alone doesn't preserve clearance once the monitor
   itself also grew. */
.wsg-card--drivethru-menu-board      { left: 70.16%; top: 11.69%; width: 24.52%; height: 14.84%; }
.wsg-card--qsr-menu-board            { left: 54.40%; top: 28.45%; width: 18.53%; height: 13.81%; }
.wsg-card--kiosk-order-build         { left: 72.94%; top: 49.48%; width: 17.21%; height: 18.43%; }
.wsg-card--billboard-display         { left: 60.70%; top: 70.39%; width: 31.61%; height: 24.24%; }
.wsg-card--snack-shelf-display       { left: 35.66%; top: 62.75%; width: 17.70%; height: 17.05%; }
.wsg-card--gas-pump-display          { left: 24.02%; top: 81.61%; width: 16.65%; height: 17.04%; }
.wsg-card--retail-storefront-display { left: 8.75%;  top: 61.33%; width: 15.20%; height: 17.47%; }

/* CONNECTOR DOTS (added 2026-07-09, reworked same day after Laura's
   first live look) — the dotted trails from the monitor out to each
   surrounding image, matching Laura's original composite mockup
   (those existed there but got cropped out when the individual card
   images were cut from it). Built and injected by JS section 4a
   (drawConnectors()), not drawn by hand per page: it reads each
   card's REAL rendered box vs. the monitor's and works out where a
   straight line between their two centers crosses each box's edge,
   so the trail only ever occupies the visible gap — never draws over
   either image — regardless of what page-specific positions/sizes a
   future reuse of this graphic ends up with. One <svg> is generated
   per .why-sagenet-graphic wrapper (class "wsg-connectors") and
   rebuilt on load + on resize (debounced) so it stays correct if the
   layout reflows.

   REWORK 2026-07-09: first version was a single dashed <line> plus
   one big circle at each end — Laura's feedback was that it read as
   "two big dots" bunched in the corner rather than an actual dotted
   trail, and that it was static (rendered instantly, not tied to the
   scroll reveal). Replaced with a SERIES of small evenly-spaced
   circles along the gap (no <line> element at all anymore — see JS)
   and each card's trail is now a real <g class="wsg-connector"> that
   JS section 4b fades/pops in at the same scroll-timeline position as
   that card, so the dots visibly arrive together with their device
   instead of being there before the section even starts animating.

   z-index 0 — stays bottommost of all three layers (monitor is 1,
   cards are 2, see the stacking-order note above .wsg-monitor) so a
   trail still tucks in behind both the monitor and whichever card
   it's heading toward, even now that cards/monitor overlap on
   purpose. */
.wsg-connectors {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	z-index: 0;
	pointer-events: none;
	overflow: visible;
}

.wsg-connectors circle {
	fill: var(--global-palette1, #f7941d);
}


/* ====================================================================
   5. ACCORDION IMAGE SWAP — Image Swap on Open
   --------------------------------------------------------------------
   Generic/reusable, like Why SageNet Graphic (section 4) — NOT specific
   to any one page. First instance is the Retail page's 5-pane
   accordion. Per Laura (2026-07-20): don't bake "retail" into these
   class names since this same recipe is going onto the other vertical
   pages (QSR, C-Store, etc.) too — each page just supplies its own
   panel copy + slugs + images, the markup contract and code below
   don't change per page.

   Retail's finalized copy is 5 panes (Deliver Consistent In-Store
   Digital Experiences, Keep Stores Connected and Operational, Simplify
   Multi-Site Technology Management, Improve Visibility Across Every
   Device and Location, Accelerate Store Openings and Rollouts — that
   source order), each with a suggested image. Clicking a pane open
   swaps which image shows in a column next to the accordion; clicking
   it closed (up arrow) leaves the image as it was — see JS section 5
   for the actual swap logic, this only handles the stacking box model.

   MARKUP CONTRACT (what to set up in Kadence, on ANY page using this):
   - One row/wrapper around BOTH the accordion and the image column,
     classed "accordion-image-swap" (this is what gates the plugin's
     assets loading on this page — see the PHP file's
     sagenet_custom_functionality_page_needs_assets()).
   - Each Kadence accordion item's Advanced > CSS Classes gets
     "panel-<slug>" — Retail's current 5, in source order:
       panel-digital-experiences
       panel-connectivity
       panel-multi-site-management
       panel-visibility
       panel-rollouts
     A different page reuses this with ITS OWN slugs matching its own
     panel copy — these 5 are Retail-specific, not part of the contract.
   - The image column gets class "accordion-image-swap-stack",
     containing one plain Kadence Image block per panel (no Position:
     Absolute, nothing to configure on the block itself), each classed
     "accordion-image-swap-image accordion-image-swap-image--<slug>"
     using the SAME slug as its matching panel above. JS has no
     hardcoded class-to-image map — it just shows whichever
     .accordion-image-swap-image's slug matches the panel that was
     opened, so adding a pane later (on any page) is just: add the
     panel class + drop in a matching image block, no code changes.

   STACKING: all 5 images share one CSS Grid cell (grid-area: 1 / 1),
   which sizes the column to fit the images without an aspect-ratio
   guess (unlike Why SageNet Graphic's percentage-canvas approach in
   section 4 — not needed here since these are simple stacked photos,
   not a multi-element composite). Only ".is-active" is opacity:1;
   JS section 5 toggles that class. object-fit:cover crops to fill the
   column's box rather than letterboxing — if these should NOT crop
   (e.g. screenshots/diagrams where cutting off edges loses content),
   switch to object-fit:contain instead, same as section 4's photos.

   DEFAULT PANE ON LOAD: JS section 5 shows whichever pane is already
   expanded when the page loads (if Kadence opens one by default),
   falling back to the FIRST image in source order (digital-experiences)
   if none is. NOT YET VERIFIED against the live accordion — if Kadence
   doesn't default any pane open, or the fallback doesn't match what
   Laura wants showing before any click, that's a JS section 5 tweak,
   not a CSS one. */

/* Kadence wraps every column's blocks in an inner ".kt-inside-inner-col"
   div, so the images are grandchildren of ".accordion-image-swap-stack",
   not direct children — CSS Grid placement (grid-area) only works on
   direct grid items, so the grid has to go on that inner wrapper, not
   the column itself. Verified against live markup on /retail/
   (2026-07-20). Scoped with the child combinator so this doesn't touch
   ".kt-inside-inner-col" elsewhere on the page (every Kadence column
   has one).

   !important is needed here: Kadence's own per-block CSS sets
   "display: flex" on this same ".kt-inside-inner-col" (that's how it
   normally stacks a column's blocks top-to-bottom), and Kadence injects
   its block CSS inline in <head> AFTER this stylesheet, so on a
   specificity tie it wins the cascade and silently reverts our grid
   back to flex — which is why the images went back to a long stacked
   column instead of overlaying. Forcing it so ours always wins here. */
.accordion-image-swap-stack > .kt-inside-inner-col {
	display: grid !important;
}

.accordion-image-swap-image {
	grid-area: 1 / 1;
	opacity: 0;
	transition: opacity 0.3s ease;
}

.accordion-image-swap-image.is-active {
	opacity: 1;
}

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