/* ==========================================================================
   Divigi — features-reel.css
   Pinned horizontal reel for the features section, after the hero.

   Same pattern as the reel in the "white" project: the section locks to the
   viewport, the track slides sideways as you scroll, then it releases.

   Everything here is behind .fr-on, which features-reel.js only adds when the
   viewport is wide enough and the user has not asked for reduced motion. So
   below 992px, or with reduced motion, the original three-column grid is what
   renders — untouched.
   ========================================================================== */

/* Transparent — the black comes from .hero-stage, which also holds the particle
   canvas. An opaque background here would paint over the field, and no z-index
   can fix that from inside a later sibling. See the stage rules in the template.
   The image area between hero and features needs the same. */
.features-area,
.image-area {
	background-color: transparent;
	position: relative;
	z-index: 1;
}

/* Cards above the field. */
.features-area .container { position: relative; z-index: 2; }

/* ---- background wordmark ----------------------------------------------------
   The element and all its styling are the ORIGINAL .divigi-bg-text from before
   this section was reworked — same markup, same full-width geometry, same
   ::before shine on the shineSweep keyframes. It lives in the bundle already.

   Only two things need overriding. The original was color:#000 at opacity .5,
   which was legible because this section used to have a light background; the
   section is black now, so black-on-black is invisible. Specificity is enough
   without !important: the bundle rule is :where(.pt-front) .divigi-bg-text, and
   :where() contributes nothing, so one extra class beats it.
   Both selectors are needed, not one: the script relocates the element out of
   .features-area and into .hero-stage so it can sit behind the particle field,
   and it stays in the section on mobile and with the script disabled.
   -------------------------------------------------------------------------- */
.features-area .divigi-bg-text,
.hero-stage .divigi-bg-text {
	color: #111111;   /* dark, but lifted off pure black so it reads unlit */
	opacity: 1;
	/* The original -30px nudge was tuned against the old layout. It now just
	   offsets the text from the centre the layer works hard to establish. */
	margin-top: 0;
}

/* Vertical on phones, with the shine running bottom to top.

   The theme's own rule already turns .divigi-bg-text on its side below 767px;
   this keeps that but centres it (the original pushed it to left:70%) and sizes
   it from the viewport HEIGHT, which is the dimension it now runs along.

   Every declaration in the theme's rule is !important, so these have to be too.
   The script stands down here — see fitWord, which detects the vertical writing
   mode and clears its own inline sizing rather than fighting for the width of
   something that no longer runs horizontally. */
@media (max-width: 767px) {
	.hero-stage .divigi-bg-text,
	.features-area .divigi-bg-text {
		writing-mode: vertical-rl !important;
		text-orientation: mixed !important;
		white-space: nowrap !important;
		text-align: center !important;
		/* Centred in the space BELOW the sticky header, not in the whole viewport.
		   The header is a constant 117px tall at every phone size, so centring on
		   50% always buried the top of the wordmark 49px under it while leaving
		   66px spare at the bottom. Shifting the centre down by half the header
		   height splits the remaining space evenly instead. */
		top: calc(50% + var(--fr-nav, 117px) / 2) !important;
		left: 50% !important;
		width: auto !important;
		height: auto !important;
		line-height: 1 !important;
		/* 31vh, not 34: at 34 the six glyphs ran 894px against an 844px screen
		   and the ends were cropped. */
		/* 25vh, so there is real margin top and bottom once the header is taken
		   out of the available height — at 27 it fitted, but only just. */
		font-size: 25vh !important;
		/* rotate(180deg) so it reads bottom-to-top: D at the foot, the last I at
		   the head. vertical-rl alone runs the other way. */
		transform: translate(-50%, -50%) rotate(180deg) !important;
	}

	/* Horizontal band (0deg points up), swept along the vertical axis. The
	   diagonal sweep the horizontal wordmark uses reads as a smear once the text
	   is on its side. */
	.hero-stage .divigi-bg-text::before,
	.features-area .divigi-bg-text::before {
		/* background-IMAGE, not the `background` shorthand. The shorthand resets
		   every background-* property it does not mention — including
		   background-clip, which the base rule sets to `text` and which is the
		   only reason the gradient is confined to the letterforms. Overriding with
		   the shorthand silently switched the clip back to border-box, so the
		   sweep stopped landing on the glyphs at all and nothing shone. The clip
		   is re-asserted below in case anything else resets it. */
		background-image: linear-gradient(0deg,
			rgba(255, 255, 255, 0)   42%,
			rgba(255, 255, 255, .34) 50%,
			rgba(255, 255, 255, 0)   58%) !important;
		background-size: 100% 300% !important;
		background-repeat: no-repeat !important;
		-webkit-background-clip: text !important;
		background-clip: text !important;
		-webkit-text-fill-color: transparent !important;
		animation: divigi-shine-up 6s linear infinite !important;
	}
}

/* Percentages here are not offsets — with a background 3x the box, 0% aligns the
   image's top edge to the box and 100% its bottom edge, so the whole usable
   range IS 0..100%. The first version swept 210% to -110%, which is outside it:
   the highlight sat off the glyphs for the entire cycle and nothing ever shone.

   Direction is measured, not reasoned about: the element is rotated 180deg, so
   element space and screen space run opposite ways. 0% -> 100% was verified as
   top-to-bottom on screen (band at y=33, then 356, then 774), so it is reversed
   here to give bottom-to-top. */
@keyframes divigi-shine-up {
	from { background-position: 50% 100%; }
	to   { background-position: 50% 0%; }
}

/* Dim the sweep. The bundle's shineSweep peaks at rgba(255,255,255,.8), which
   blew the letters to near-white as it passed — too bright now the base colour
   has been lifted. Only the gradient is restated; background-size, clip and the
   animation all stay as the base rule set them.

   background-IMAGE, not the `background` shorthand: the shorthand would reset
   background-clip, and `clip: text` is the only reason the sweep is confined to
   the letterforms rather than painting a rectangle. */
.hero-stage .divigi-bg-text::before,
.features-area .divigi-bg-text::before {
	background-image: linear-gradient(60deg,
		rgba(255, 255, 255, 0)    40%,
		rgba(255, 255, 255, .34)  50%,
		rgba(255, 255, 255, 0)    60%);
}

/* Card copy on a light stage.

   The theme sets .feature-box .title and .text to var(--white) with no dark-mode
   qualifier, because this section was always on black. Now that the stage
   follows the theme, white-on-white left the cards invisible in light mode. */
.light .features-area .feature-box .title { color: #121212; }

/* The card icons are white line art — they were drawn for a black section, so
   they need inverting once the section is white. */
.light .features-area .feature-box .thumb img { filter: invert(1); }
.light .features-area .feature-box .text  { color: #555555; }

/* Light theme.

   The wordmark is a near-black watermark on a black stage; on a white one it has
   to become a near-white watermark instead, or it reads as solid type rather
   than a ground. The sweep flips with it: a white highlight over pale grey is
   invisible, so on light it is a dark band. Same mechanic, inverted.

   background-IMAGE again, not the shorthand — it would reset background-clip,
   and `clip: text` is what keeps the sweep inside the letterforms. */
.light .hero-stage .divigi-bg-text,
.light .features-area .divigi-bg-text {
	color: #ededed;
}

.light .hero-stage .divigi-bg-text::before,
.light .features-area .divigi-bg-text::before {
	background-image: linear-gradient(60deg,
		rgba(0, 0, 0, 0)   40%,
		rgba(0, 0, 0, .20) 50%,
		rgba(0, 0, 0, 0)   60%);
}

@media (max-width: 767px) {
	.light .hero-stage .divigi-bg-text::before,
	.light .features-area .divigi-bg-text::before {
		background-image: linear-gradient(0deg,
			rgba(0, 0, 0, 0)   42%,
			rgba(0, 0, 0, .20) 50%,
			rgba(0, 0, 0, 0)   58%) !important;
	}
}

/* Wrapper the script builds around the wordmark. No z-index — it is deliberately
   auto so that paint order falls back to DOM order, which is what places this
   behind the canvas. Adding a z-index here would defeat the whole arrangement. */
.fr-word-layer {
	position: absolute;
	left: 0;
	/* Sits over the features section. .hero-area is height:100vh and .image-area
	   collapses to nothing, so the section starts exactly one screen down.

	   In CSS, not measured, and that matters more than it looks. This used to be
	   assigned from a refreshInit handler on the assumption that ScrollTrigger had
	   already reverted its pins by then. It has not — refreshInit fires BEFORE the
	   revert, so the handler was reading pinned coordinates and writing them back
	   as a natural offset. It only ever looked right on first load, when nothing
	   was pinned yet; after a zoom the wordmark sat off by exactly the change in
	   window height (180px at 125%, -225px at 80%). 100vh cannot go stale. */
	top: 100vh;
	width: 100%;
	/* One screen tall, in CSS rather than measured in JS.
	   The wordmark centres against this box, so the box has to equal the viewport
	   — it pins flush to the top of the screen, which is the only frame of
	   reference a reader has. It used to be assigned window.innerHeight from the
	   resize handler, which meant that after a zoom the text was centring against
	   the PREVIOUS window height and sat visibly high or low until something
	   refreshed. 100vh re-resolves itself on every zoom, with nothing to go
	   stale. */
	height: 100vh;
	pointer-events: none;
}

/* Not behind a width media query.

   Every rule here is scoped to .fr-on, and the script only adds that class when
   the reel actually initialises — so the class IS the gate, and a second one by
   width just stopped the effect reaching phones. */

/* The track is wider than this box, and without clipping the next panel shows
   in the gutter beside the container on a wide screen — two cards at once,
   which is exactly what one-panel-per-screen is meant to prevent. It is not
   visible at 1600px because the container nearly fills the window there. */
.fr-on .features-wrapper-box { position: relative; overflow: hidden; }

/* The pips anchor here instead, one level up, so the clip above cannot eat
   them — they sit above the box's top edge. */
.fr-on .features-area-inner { position: relative; }

/* The track. Grid becomes a single flex row wider than the screen; the
   script slides it by exactly its overflow. */
/* The section is exactly one screen tall, so its cards sit on the centre line
   of the SCREEN. It was naturally shorter than the viewport (802 against 900)
   and pins flush to the top, so the cards rode high — and worse the taller the
   window, which is what zooming out does.

   In CSS rather than measured in JS on purpose: 100vh re-resolves itself on
   every zoom, where a measured pixel value is only as good as the last resize
   handler that ran. align-content centres the rows without making .container
   a flex item, which would disturb its own width rules. */
.features-area.fr-on {
	box-sizing: border-box;
	min-height: 100vh;
	display: grid;
	align-content: center;
	/* minmax(0, 1fr), not the default auto.
	   An auto column is sized by its content's min-content width, and the track
	   inside is three panels wide — so on a phone the .container grew to 455px
	   inside a 390px section and everything below it inherited the overflow. A 0
	   minimum lets the column be the section's width and leaves the track to
	   overflow and be clipped, which is the whole mechanic. */
	grid-template-columns: minmax(0, 1fr);
}

.fr-on .features-wrapper {
	display: flex;
	grid-template-columns: none;   /* cancels the 3x300px grid */
	/* NOT max-content. A percentage flex-basis resolves against the flex
	   container, so with max-content the panels had nothing definite to
	   measure against and came out 1620px against a 1456px box. At 100% the
	   container is the box's width, each panel resolves to exactly that, and
	   flex-shrink:0 lets them overflow to the right — which is what the track
	   needs anyway. No JS measurement involved, so a zoom cannot stale it. */
	width: 100%;
	gap: clamp(30px, 3.2vw, 64px);
	justify-content: flex-start;
	align-items: stretch;
	will-change: transform;
}

/* ONE PANEL PER SCREEN, the same as .reel-panel in the "white" project.
   Each card owns a full container width, so exactly one is readable at a
   time and the reel advances card by card rather than sliding a row past.
   Track length is therefore panels x container, and the pin lasts that
   long — three cards, roughly three screens of scroll. */
.fr-on .feature-box {
	/* --fr-panel is the measured width of .features-wrapper-box, set by the
	   script. A plain 100% resolves against the flex container, which is
	   width:max-content and therefore indeterminate — panels came out wider
	   than the box and each card drifted left of the previous one. The 100%
	   stays as the fallback for the moment before the script measures. */
	flex: 0 0 100%;
	width: 100%;
	display: flex;
	flex-direction: column;
	justify-content: center;
	/* Narrows the card without narrowing the PANEL. The panel has to stay a
	   full container wide or more than one card shows at a time; centring a
	   constrained column inside it is what actually reduces the width. */
	align-items: center;
	/* The section owns the height now; panels just fill it. */
	min-height: 0;
}

.fr-on .feature-box > * { width: 100%; max-width: 540px; }

/* Wrapper the script builds around the track, purely to carry the entrance
   transform.

   width:100%, NOT max-content. It sits between the box and the track, so a
   max-content width here made the track's own 100% resolve against something
   indeterminate — reintroducing exactly the bug the track's width was changed to
   fix, and blowing the panels up to 1620px on a 390px phone. At 100% the chain
   stays definite: box -> shift -> track -> panel, every step the box's width.
   The panels still overflow to the right; .features-wrapper-box clips them. */
.fr-on .fr-track-shift { width: 100%; }

/* No gap: a gap between full-width panels shows as a blank gutter mid-slide. */
.fr-on .features-wrapper { gap: 0; }

/* Superseded by the 540px cap on all panel children above. */

/* These are 60x60 icons, not photographs. An earlier version styled the
   thumb like a photo — overflow, radius, and width:100% on the image — which
   blew a 60px icon up to the full 680px panel width. Icons keep their own
   size; only the hover nudge is added. */
.fr-on .feature-box .thumb img {
	width: auto;
	max-width: 64px;
	height: auto;
	display: block;
	transition: transform .5s cubic-bezier(.22, 1, .36, 1);
}

.fr-on .feature-box:hover .thumb img { transform: scale(1.08); }

/* .features-area already sets overflow:hidden, which is what crops the
   panels that have not arrived yet — no extra clipping needed. */

/* ---- progress pips ----------------------------------------------------
   Short dashes at the top, one per panel, lighting up as you pass them —
   the same indicator "white" uses. A filled bar was the wrong read: it
   suggests a continuous load, where these say "card 2 of 3".
   ---------------------------------------------------------------------- */
.fr-progress {
	position: absolute;
	top: clamp(34px, 6vh, 74px);
	right: 0;
	z-index: 6;
	display: flex;
	gap: 8px;
	align-items: center;
}

.fr-pip {
	width: 22px;
	height: 2px;
	background: rgba(18, 18, 18, .18);
	transition: background .5s cubic-bezier(.22, 1, .36, 1);
}

.dark .fr-pip { background: rgba(255, 255, 255, .18); }
.fr-pip.on    { background: #ebf212; }


/* Small screens: tighter pips, and real space between the cards.

   The gap is 0 on desktop because a gutter between full-width panels reads as a
   dead zone mid-slide. On a phone the opposite is true — the cards butt up
   against each other and the next one is already arriving before the current one
   has left, so a gap is what separates them. Centring is unaffected: the track
   slides by scrollWidth minus the box, so the gap is carried in that measurement
   and each card still lands dead centre. */
@media (max-width: 767px) {
	.fr-pip { width: 14px; }
	.fr-progress { gap: 6px; }
	.fr-on .features-wrapper { gap: 12vw; }
}

/* ==========================================================================
   Counter circles — one row on phones
   ==========================================================================
   The theme drops these to a 2x2 grid under 767px and a single column under
   575px. Asked for as a single horizontal row instead.

   Four 260px circles cannot fit a phone, so they size themselves from the
   viewport rather than keeping a fixed width: flex-basis 0 with aspect-ratio
   keeps them circular and equal, and the type scales with them. The overlap the
   desktop layout uses is kept, in proportion, because it is what makes them read
   as one linked group rather than four separate bubbles.

   Two classes deep, so no !important is needed — the theme's rules are written
   as :where(.pt-front) ..., and :where() contributes no specificity. */
@media (max-width: 767px) {
	.counter-area .counter-wrapper {
		display: flex;
		flex-wrap: nowrap;
		justify-content: center;
		align-items: center;
	}

	.counter-area .counter-item {
		flex: 1 1 0;
		width: auto;
		height: auto;
		max-width: 27vw;
		aspect-ratio: 1 / 1;
		padding: 0 6px;
	}

	/* Proportional overlap, matching the desktop treatment. */
	.counter-area .counter-wrapper > *:not(:first-child) {
		margin-inline-start: -3vw;
	}

	.counter-area .counter-item .number {
		font-size: clamp(15px, 5.4vw, 32px);
		line-height: 1.1;
		margin-bottom: 2px;
	}

	.counter-area .counter-item .text {
		font-size: clamp(8px, 2.5vw, 12px);
		line-height: 1.25;
	}

	/* The theme stacks these on two lines via <br>; at this size the break is
	   what keeps them inside the circle. */
	.counter-area .counter-item .text br { display: block; }
}

/* ==========================================================================
   Stack mode — desktop
   ==========================================================================
   The cards arrive one at a time from the right and STAY, so the section ends
   with all three side by side. Nothing slides past and nothing leaves, which is
   why none of the carousel rules below apply here.

   Full-bleed to match the wordmark: the box breaks out of the .container it is
   nested in with the standard 100vw + negative-half-margin pair, so the row of
   cards spans the viewport exactly as DIVIGI does behind it.
   ========================================================================== */
.features-area.fr-stack .features-wrapper-box {
	width: 100vw;
	margin-left: calc(50% - 50vw);
}

.features-area.fr-stack .features-wrapper {
	gap: clamp(18px, 2.4vw, 52px);
	padding-inline: clamp(20px, 3.6vw, 90px);
	align-items: stretch;
}

/* Equal thirds. flex-basis 0 with min-width 0 makes them share the row evenly
   regardless of how long each card's text is — with the default auto basis the
   longest copy would claim more of the row. */
.features-area.fr-stack .feature-box {
	flex: 1 1 0;
	width: auto;
	min-width: 0;
	max-width: none;
	align-items: flex-start;
	text-align: left;
}

/* The 540px cap is a carousel rule — there each card owned the full container
   and needed reining in. A third of the viewport is already narrow enough. */
.features-area.fr-stack .feature-box > * {
	width: 100%;
	max-width: none;
}

.features-area.fr-stack .feature-box .content { max-width: none; }

/* Overflow has to stay clipped: the cards start a full viewport to the right,
   and without this they would widen the page while they wait. */
.features-area.fr-stack .features-wrapper-box { overflow: hidden; }
