/* Feature list beside a piece of media.

   COMP GEOMETRY — design-preview-v2.pdf, 1440 design px, measured from ink
   bounds at 144dpi (2x) and solved against the real font with kerning.

   COLUMNS
     A  p3  copy box 143.5–518.0 (374.5)   gap 133   media 651.0–1332.5 (681.5)
     B  p3  copy box 123.5–498.0 (374.5)   gap 135   media 633.0–1314.0 (681.0)
     C  p1  copy measure 107–516 (409.0)   gap 204.5 media 720.0–1330.5 (610.5)

   A and B are the same block, 1189 wide, placed differently inside the 1224
   page column: A is flush right with 35 spare on the left, B is centred with
   17 a side. That is drift, not two intentions, so the block here fills the
   1224 column instead — media 681.5 flush to the page grid, gap 133, and the
   copy takes the remaining 409.5. The dividers then run 409.5 rather than the
   drawn 374.5, and 409.5 is exactly instance C's copy measure.

   TYPE  every size lands on Jay's 1.2 scale from 14.4 (see the comps note)
     heading   35.83 / leading 36.0 (1.0) / -0.04em / Inter Medium
               Solved, not read: cap-R 26.5 gives 36.4 uncorrected, and the two
               lines ink 365.5 and 165.5 wide. 35.83 at -0.04em reproduces them
               at 364.9 and 165.1 — both within 0.6px, and the cap agrees once
               the ~0.44px antialias spread comes off. Identical to
               tabs.module's .panel-heading, which was read off the source file.
     item text 17.28 / baseline pitch 21.0 (1.215) / -0.04em / Inter MEDIUM
               Seven lines across four items solve to 17.28 at -0.039em within
               0.5px each. Weight is from ink area, which separates the cuts
               where width cannot: the comp's "Board packets, meetings," inks
               3477 units against Regular 2870, Medium 3448, SemiBold 4024.
     body copy 17.28 / pitch 20.5 (1.20) / Inter REGULAR
               Same test the other way: instance C's lead inks 4913 against
               Regular 4868 and Medium 5861. So the list is tracked UI text and
               the paragraph is not — different roles, one size.
     eyebrow   17.28 / cap 13.0 / Bold, title case, tracking 0 — the comps'
               eyebrow everywhere, not the deck's tracked caps at 12–14.

   GAPS  measured between line boxes, reconstructed from baselines and font
   metrics rather than read off the raster.
     eyebrow → heading   22.4 (C).  24 here, which is what section-header
                         measured across four instances (19.1–24.7).
     heading → list      30.0 (A)
     heading → copy      35.4 (C)
     copy    → button    41.4 (C)
     item padding-block  24.2 / 24.2 / 27.7 top, 26.8 / 25.8 / 27.3 bottom.
                         26 both sides reproduces every rule within 2.5px and
                         lands item 4 exactly; the comp is itself 3.5
                         inconsistent between items 2 and 3.
     button height       46.5 (C) — .btn.primary is 46 and is left alone.

   COLOUR  sampled, every one already a token
     heading    #006A52 Evergreen   --ink-accent    6.32:1 on Warm White
     icon       #006A52 Evergreen   --ink-accent
     item text  #5E6863 Slate       --ink-secondary 5.53:1 on Warm White
     rule       #D6DCD8 Stone       --line          (instance B exactly;
                instance A draws #CECECE, a plain grey — treated as drift)
     eyebrow    #006A52 Evergreen   --ink-accent
     button     #00A254 Vivid Green --action

   The media is a plain box at --rad with no shadow or border in any instance;
   its corner measures 12 across an 8–9px curve. Aspect is the image's own —
   A is 681.5 x 509.5, which is 4:3, but nothing depends on it. */

/* Keyframes live at the top level on purpose. CSS nesting only admits
   conditional at-rules — @media, @supports — inside a style rule; a
   @keyframes written in there is dropped silently, and the animation simply
   never runs with no error anywhere. tabs.module, magic-moment and
   logo-ticker all carry the same note, and this module still walked into it:
   animation-name computed fine, getAnimations() stayed empty, and the fill
   sat at scaleX(0) forever — which, because the fill IS the timer, meant the
   list never advanced either. */
@keyframes feature-fill {
	from { transform: scaleX(0); }
	to   { transform: scaleX(1); }
}

/* THE SWAP READS AS TRAVEL, NOT A DISSOLVE. The list runs down the page and
   advances downward, so the stack moves with it: the arriving shot rises from
   below while the leaving one clears upward, and both directions reverse when
   an author arrows back up. Fade alone gave no sense of which way the list had
   just gone, which on a six-second auto-advance is the only cue there is.

   The shift is 24 — small enough that a capped phone shot does not appear to
   leave its column, large enough to register at a glance. Out is quicker than
   in (220 against 380) so the arriving shot is never behind the departing one
   at full opacity, and the pair never reads as a cross-dissolve. */
@keyframes feature-media-in-next {
	from { opacity: 0; transform: translateY(24px); }
	to   { opacity: 1; transform: none; }
}

@keyframes feature-media-out-next {
	from { opacity: 1; transform: none; }
	to   { opacity: 0; transform: translateY(-24px); }
}

@keyframes feature-media-in-prev {
	from { opacity: 0; transform: translateY(-24px); }
	to   { opacity: 1; transform: none; }
}

@keyframes feature-media-out-prev {
	from { opacity: 1; transform: none; }
	to   { opacity: 0; transform: translateY(24px); }
}

section.feature-media {
	/* The comps draw these bands full-bleed with square corners. The
	   theme-wide `section { border-radius: var(--rad) }` was written for inset
	   cards and would clip a Declaring band's corners against the page ground. */
	border-radius: 0;

	/* What hover promotes to, for BOTH lists — the plain one darkens its
	   border, the rotating one darkens its track, and they share the pair.
	   Custom properties because it swaps as one on the Declaring ground: an
	   override there is two values, not four nested hover rules re-declared at
	   a higher specificity. Held at the root rather than on .is-rotating so
	   .surface-declaring outranks it on class count alone. */
	--hover-ink: var(--ink-deep);
	--hover-line: var(--ink-secondary);

	.layout {
		display: grid;
		grid-template-columns: minmax(0, 1fr);
		gap: 40px;

		/* 1024, not the 900 tabs.module uses: this column carries a 35.84
		   heading as well as the list, and below 1024 the copy track falls
		   under 290 and the heading starts wrapping every other word. */
		@media (min-width: 1024px) {
			/* 409.5 : 133 : 681.5 = the 1224 page column. The gap is a
			   percentage of the grid container so it holds its share as the
			   page narrows, capped at the drawn 133. */
			grid-template-columns: minmax(0, 409.5fr) minmax(0, 681.5fr);
			column-gap: clamp(48px, 10.866%, 133px);
			/* Copy 407.7 tall against media 509.5, centres 8 apart. */
			align-items: center;
		}
	}

	.copy { min-width: 0; }

	/* Mirror. Source order keeps the copy first, so the reading order and the
	   tab order are the same whichever side the media is drawn on. */
	@media (min-width: 1024px) {
		.copy  { grid-column: 1; }
		.media { grid-column: 2; }

		&.media-left {
			.layout {
				grid-template-columns: minmax(0, 681.5fr) minmax(0, 409.5fr);
			}

			.copy  { grid-column: 2; }
			.media { grid-column: 1; }
		}
	}

	/* No comp instance runs this module without media. If an author leaves it
	   empty the copy takes the page rather than sitting in a third of it with
	   an empty track beside it, held to the measure section-header took off the
	   comps (676.5–719). */
	&.no-media {
		.layout {
			@media (min-width: 1024px) {
				grid-template-columns: minmax(0, 1fr);
			}
		}

		.copy { max-width: 700px; }
	}

	/* Type comes from the theme-wide eyebrow rule (_typography.css); only the
	   measured gap is stated. Colour is NOT set here: .surface-declaring gives
	   the eyebrow its own approved Lime, and a rule at this depth would
	   outrank it. */
	.eyebrow {
		margin: 0 0 24px;
	}

	.copy-heading {
		/* The 35.84 rung. clamp caps there and 2.6vw only bites below 1378. */
		font-size: clamp(2.8rem, 2.6vw, 3.584rem);
		line-height: 1;
		/* Measured as static Inter Medium; --display is the rung above, which
		   is where every role in this theme sits because Inter Variable renders
		   lighter than the cut the comps were set in. Same value
		   tabs.module's .panel-heading carries. */
		font-weight: var(--display);
		letter-spacing: -.04em;
		margin: 0 0 30px;
	}

	/* Bold in the heading field sets weight only. No instance of this section
	   shows a second colour in the heading, so inventing one would be a
	   treatment nobody designed — see the hand-back for the Lime option on a
	   declaring ground. */
	.rich-heading {
		--emphasis-weight: var(--data);
		--emphasis-color: inherit;
	}

	.body {
		margin: 0 0 30px;
		font-size: clamp(1.7rem, 1.2vw, 1.8rem);
		/* 20.5 baseline pitch at 17.28. Tighter than the theme's 1.6, which is
		   what the centred section headers use — this is an in-column cluster,
		   the same call tabs.module made for its item copy. */
		line-height: 1.2;
		font-weight: var(--book);

		p {
			font-size: inherit;
			line-height: inherit;
			font-weight: inherit;
			margin: 0;

			/* Comp sets consecutive paragraphs 9.1 apart, which is .53em. */
			& + p { margin-top: .5em; }
		}

		strong, b { font-weight: var(--data); }

		/* An anchor in the theme inherits its colour and drops its underline,
		   which would leave a link in running copy with nothing to identify it.
		   The underline is the identifier — colour alone never is. */
		a {
			text-decoration: underline;
			text-decoration-thickness: 1px;
			text-underline-offset: .18em;

			&:hover { text-decoration-thickness: 2px; }

			&:focus-visible {
				outline: 2px solid var(--focus);
				outline-offset: 3px;
				border-radius: var(--radFocus);
				text-decoration-thickness: 2px;
			}
		}
	}

	.features {
		list-style: none;
		margin: 0;
		padding: 0;

		/* The item's BOX — its padding and its rule — is the li. The two-column
		   grid is .feature inside it. Split because the rotating list makes
		   .feature a <button> and hangs a progress track beside it: the track
		   has to span the item's full width, where the button is only the
		   content. In the plain list .feature is a <div> and the pair renders
		   identically to the single box this used to be.

		   The rule is a border on the item, not a divider element, so any
		   number of items keeps it. The comp draws one after every item except
		   the last. */
		li {
			padding-block: 26px;
			border-bottom: 1px solid var(--line);
			transition: border-color 160ms var(--ease);

			&:first-child { padding-top: 0; }

			&:last-child {
				padding-bottom: 0;
				border-bottom: none;
			}
		}

		/* Two columns, two rows, everything placed explicitly — the icon is put
		   in row 1 and centred there so it aligns against the FIRST line rather
		   than against the whole item. 15.5 is the arrow's measured ink width;
		   with the 12px gap the text lands 27.5 from the icon's left edge, as
		   it does in the comp. */
		.feature {
			display: grid;
			grid-template-columns: 15.5px minmax(0, 1fr);
			column-gap: 12px;
			row-gap: 6px;

			/* An author who clears the icon field gets no icon and no track,
			   rather than a 27.5px indent with nothing in it. */
			&.no-icon {
				grid-template-columns: 0 minmax(0, 1fr);
				column-gap: 0;
			}
		}

		.feature-icon {
			grid-column: 1;
			grid-row: 1;
			align-self: center;
			font-size: 1.5rem;
			line-height: 1;
		}

		.feature-heading,
		.feature-text {
			grid-column: 2;
			margin: 0;
			transition: color 160ms var(--ease);
		}
	}

	/* HOVER ON THE PLAIN LIST. The rows here are <div>s and nothing happens
	   when you click one — this is feedback, not an affordance, so the cursor
	   stays an arrow and no focus or active state comes with it. Worth knowing
	   that it borrows the ROTATING list's exact treatment (ink to Deep Green,
	   rule from Stone to Slate): in a section that also ships a version where
	   the identical response means "this row is a control", the two now look
	   the same and mean different things. Not resolved — flag it for Jay if
	   the two ever land on one page; the alternative is a weaker cue here,
	   which reads as a bug rather than a distinction.

	   The li is the hover target, not .feature. .feature is only the text
	   lines — 21px of a 73px row — so hovering it would light the row from a
	   third of its height. The rotating list solves that with a full-row
	   ::after on its button; a plain row needs no overlay because the li IS
	   the box. Ink 12.97:1 on the comps' #F2EFE7 and 14.31:1 on Warm White,
	   both AAA, so this only ever darkens away from the resting 5.03:1.

	   The last item draws no rule (266), so it hovers on ink alone. Same
	   asymmetry the comp draws at rest, not one this introduces.

	   (hover: hover) keeps it off touch, where :hover latches after a tap and
	   would leave a row lit with nothing to un-light it. */
	@media (hover: hover) {
		&:not(.is-rotating) .features li:hover {
			border-bottom-color: var(--hover-line);

			.feature-heading,
			.feature-text { color: var(--hover-ink); }
		}
	}

	/* The 20.74 rung, as tabs.module measured it for the same icon-plus-heading
	   list. No instance of THIS section uses an item heading — it is here for
	   the sections that pair one with the text. */
	.feature-heading {
		font-size: 2.074rem;
		line-height: 1.15;
		font-weight: var(--data);
	}

	.feature-text {
		font-size: clamp(1.7rem, 1.2vw, 1.728rem);
		line-height: 1.215;
		/* UI body, so --medium: measured as static Inter Medium, and this is
		   the top of that band. */
		font-weight: var(--medium);
		letter-spacing: -.04em;
	}

	.actions {
		display: flex;
		flex-wrap: wrap;
		gap: 16px;
		margin-top: 40px;
	}

	/* --- Light ground ----------------------------------------------------
	   Scoped so Declaring keeps the pairings its surface class carries.
	     Evergreen heading  6.32:1 on Warm White · 5.74:1 on the comps' #F2EFE7
	     Slate item text    5.53:1 on Warm White · 5.03:1 on #F2EFE7
	   both AA at any size. */
	&.surface-working,
	&.surface-warm {
		.eyebrow       { color: var(--ink-accent); }
		.copy-heading  { color: var(--ink-accent); }
		.body          { color: var(--ink-secondary); }
		.body a        { color: var(--ink-accent); }
		.feature-icon  { color: var(--ink-accent); }
		.feature-heading { color: var(--ink-deep); }
		.feature-text  { color: var(--ink-secondary); }
	}

	/* --- Declaring -------------------------------------------------------
	   .surface-declaring supplies Warm White type and the Lime eyebrow; only
	   what it does not cover is set here.
	     Pure white heading and text  14.94:1
	     Vivid Green icon              4.48:1 — non-text UI, floor is 3:1, and
	       it is the accent the comps use on Deep Green (the "Better For Every
	       Role" eyebrow on p1 samples #00A254 exactly).
	     Evergreen is 2.26:1 on Deep Green, so the focus ring inverts and the
	       secondary button takes the on-dark outline treatment. */
	&.surface-declaring {
		.copy-heading,
		.feature-heading,
		.feature-text,
		.body { color: var(--ink-inverse); }

		.feature-icon { color: var(--action); }

		/* Stone would be 10.73:1 here against the light instance's 1.33:1 —
		   eight times the weight for the same hairline. Mixed from the type
		   colour instead so it stays a whisper on either ground. */
		.features li { border-bottom-color: color-mix(in srgb, var(--ink-inverse) 22%, transparent); }

		/* Same whisper for the rotating list, where the track draws the rule
		   instead of the border. The FILL stays full strength — it is the
		   state, not the furniture — and takes the on-dark accent, since Deep
		   Green on Deep Green would be no bar at all. */
		.feature-track { background-color: color-mix(in srgb, var(--ink-inverse) 22%, transparent); }
		.feature-fill  { background-color: var(--action); }

		.body a:focus-visible,
		.actions .btn:focus-visible { outline-color: var(--focus-inverse); }

		.actions .btn.secondary {
			color: var(--ink-inverse);
			border-color: var(--ink-inverse);

			&:hover {
				border-color: var(--ink-inverse);
				background-color: color-mix(in srgb, var(--ink-inverse) 12%, transparent);
			}
		}
	}

	.media {
		margin: 0;
		border-radius: var(--rad);
		overflow: hidden;

		/* A CAP ON HEIGHT, NOT ON WIDTH. The comps only ever hang landscape
		   product shots here — the media box measures 681.5 x 509.5, so a 4:3
		   shot fills the column at 511 tall and the tallest art in the set
		   (1915 x 1620) reaches 576. A phone screenshot is the case the comps
		   never drew: 875 x 1701 at the same 681.5 column is 1325 tall, twice
		   the copy beside it, and in a rotating stack it sets the height of
		   every other panel with it.

		   600 clears every landscape shot in the set, so this binds on
		   portrait art and nothing else. The vh term is for a short laptop
		   window, where 600px of phone would still crowd the fold.

		   width:auto with BOTH maxima is what keeps the ratio: the replaced
		   element shrinks until it satisfies whichever limit binds first, so
		   a capped phone shot narrows to ~309 rather than squashing. */
		img {
			display: block;
			width: auto;
			max-width: 100%;
			height: auto;
			max-height: min(var(--mediaMax, 600px), 80vh);
			margin-inline: auto;
			/* The figure's radius clipped the shot while it filled the box.
			   A capped shot no longer touches the sides, so it has to carry
			   the radius itself or it comes back square-cornered. */
			border-radius: inherit;
		}

		&.is-embed {
			aspect-ratio: var(--ratio, 16 / 10);

			iframe {
				display: block;
				width: 100%;
				height: 100%;
				border: 0;
			}
		}
	}

	/* ROTATING LIST — only when every feature carries its own media. See
	   module.html for why it is all-or-nothing.

	   The interval lives here rather than in a field. Authors set content on
	   this theme, not behaviour, and a per-instance timing knob is the kind of
	   thing that drifts apart across five instances of one section. */
	&.is-rotating {
		/* Was 6s; halved on Sean's call (2026-08-26) — the dwell read as a
		   stall. The fill is the timer, so this one value is the whole
		   change. */
		--interval: 3s;

		.features li {
			position: relative;
			/* The track draws the rule now, so the border would double it. */
			border-bottom: none;
		}

		/* Every item gets a track, the LAST one included, where the plain list
		   drops that rule to match the comp. The comp only ever shows item one
		   part-filled, so it cannot say what the last item does — and a
		   progress affordance that vanishes on the final item is a bug, not a
		   design. Flagged for Jay. */
		.features li:last-child { padding-bottom: 26px; }

		/* .feature is a <button> here. Stripped back to the box the <div>
		   was, so the two modes render identically apart from the cursor. */
		.feature {
			inline-size: 100%;
			appearance: none;
			background: none;
			border: 0;
			padding: 0;
			font: inherit;
			color: inherit;
			text-align: start;
			cursor: pointer;

			/* THE WHOLE ROW IS THE CONTROL. The button's own box is only its
			   text lines — the 26px above and below belong to the li — so bare,
			   most of a 73px row would neither hover nor click. The overlay
			   anchors to the li (the button stays unpositioned, so the
			   positioned li is its containing block) and the min() holds it to
			   at least 44px for the rows that come up short of that on their
			   own: the first item, which drops its top padding, and every row
			   once 767px cuts the padding to 20. This replaces the mobile-only
			   44px pseudo that used to live at that breakpoint. */
			&::after {
				content: "";
				position: absolute;
				inset-block: min(0px, calc((100% - 44px) / 2));
				inset-inline: 0;
			}

			&:focus-visible {
				outline: 2px solid var(--focus);
				outline-offset: 4px;
				border-radius: var(--radFocus);
			}
		}

		/* HOVER. The rows are buttons, but until now only the cursor said so.
		   The item's ink steps up to Deep Green (14.31:1, AAA) and its rule
		   wakes from Stone to Slate — the same darken-on-hover tabs.module
		   gives its strip, and gated on a real pointer for the same reason:
		   on touch, :hover latches after a tap and the row would stay lit
		   while a different item is selected. The ACTIVE item is excluded —
		   its state is the filling rule, and a second treatment on top would
		   muddle which signal is which. */
		@media (hover: hover) {
			.features li:not(.is-active) .feature:hover {
				.feature-heading,
				.feature-text { color: var(--hover-ink); }
			}

			.features li:not(.is-active) .feature:hover + .feature-track {
				background-color: var(--hover-line);
			}
		}

		.feature-track {
			position: absolute;
			inset-inline: 0;
			inset-block-end: 0;
			block-size: 1px;
			background-color: var(--line);
			overflow: hidden;
			transition: background-color 160ms var(--ease);
		}

		.feature-fill {
			display: block;
			block-size: 100%;
			inline-size: 100%;
			background-color: var(--ink-deep);
			transform: scaleX(0);
			transform-origin: left;
		}

		/* THE FILL IS THE TIMER. The advance runs off this animation's
		   animationend rather than off a setTimeout kept alongside it, so the
		   bar and the swap cannot drift, and pausing the animation pauses the
		   rotation itself — one mechanism instead of two held in step. */
		.features li.is-active .feature-fill {
			animation: feature-fill var(--interval) linear forwards;
		}

		&.is-paused .features li.is-active .feature-fill {
			animation-play-state: paused;
		}

		/* Reading the list holds it. Pure CSS and no state to keep in step —
		   because the fill is the timer, pausing it here pauses the advance
		   too. Scoped to the list rather than the section so resting the
		   pointer on the shot does not freeze it indefinitely. */
		.features:hover li.is-active .feature-fill,
		.features:focus-within li.is-active .feature-fill {
			animation-play-state: paused;
		}

		/* Hidden until the script is running: a stop button that cannot stop
		   anything is worse than no button. */
		.rotate-toggle { display: none; }

		&.js-ready .rotate-toggle {
			display: inline-flex;
			align-items: center;
			gap: 8px;
			margin-top: 24px;
			padding: 6px 14px 6px 10px;
			appearance: none;
			background: none;
			border: 1px solid var(--line);
			border-radius: 999px;
			font-size: 1.44rem;
			line-height: 1.2;
			color: var(--ink-secondary);
			cursor: pointer;

			&:hover { border-color: var(--ink-secondary); }

			&:focus-visible {
				outline: 2px solid var(--focus);
				outline-offset: 2px;
			}
		}

		.rotate-icon {
			display: grid;
			font-size: 1.2rem;

			/* Both glyphs stacked in one cell so the swap never reflows the
			   button — the same device as accordion.module's toggle. */
			.tog-pause, .tog-play { grid-area: 1 / 1; }
			.tog-play  { opacity: 0; }
		}

		&.is-paused .rotate-icon {
			.tog-pause { opacity: 0; }
			.tog-play  { opacity: 1; }
		}

		/* THE FRAME. A fixed-ratio window holding the section's backdrop, with
		   the panels stacked absolutely inside it. Fixed ratio because the
		   shots differ in shape — an app window is 4:3, a phone is 0.51 — and
		   a frame that resized to each one would pump the whole column on
		   every advance. 4/3 is the comp's own media box (681.5 x 509.5) and
		   near enough the backdrop art (1360 x 1000) to crop invisibly.

		   overflow:hidden is load-bearing, not tidiness: it is what cuts the
		   bottom off a phone and the corner off an app window. */
		.media-stack {
			position: relative;
			aspect-ratio: var(--frameRatio, 4 / 3);
			border-radius: var(--rad);
			overflow: hidden;
			background-color: var(--surface-recessed, #e8ebe4);
			background-image: var(--backdrop, none);
			background-repeat: no-repeat;
			background-position: center;
			background-size: cover;

			> .panel {
				position: absolute;
				inset: 0;
			}

			.panel:focus-visible {
				outline: 2px solid var(--focus);
				outline-offset: -4px;
				border-radius: var(--rad);
			}
		}

		/* THE GLASS EDGE. The cut-outs are trimmed to their own rounded
		   rectangle, so a padded box at the image's bounds sits exactly on its
		   edge — that trim is what makes a CSS frame possible at all. The
		   padding IS the glass: the shot covers the interior, so the blur and
		   the tint only ever show in the ring around it.

		   RADII. Chosen on review (2026-08-26): 24 on the art, 28 on the
		   ring's outer edge — the pair connect-cards picked first, adopted
		   here the same day so the two glass frames read as one treatment.
		   Not the rad + glass derivation this ring originally shipped with:
		   the curves converge 4px toward the corner rather than running
		   parallel, and at these sizes that reads as one rounded object. The
		   CSS radius now overshoots the art's own baked ~12px trim, so the
		   corner is this rule's business rather than the export's. */
		.shot {
			--glass: 7px;
			--shotRad: 24px;

			position: absolute;
			padding: var(--glass);
			border: 1px solid color-mix(in srgb, #fff 55%, transparent);
			border-radius: calc(var(--shotRad) + 4px);
			background-color: color-mix(in srgb, #fff 14%, transparent);
			backdrop-filter: blur(12px) saturate(1.15);
			box-shadow: 0 20px 44px -20px rgb(0 0 0 / 0.35);

			img {
				display: block;
				width: 100%;
				height: auto;
				border-radius: var(--shotRad);
			}

			&.is-embed {
				aspect-ratio: var(--ratio, 16 / 10);

				iframe {
					display: block;
					width: 100%;
					height: 100%;
					border: 0;
					border-radius: var(--shotRad);
				}
			}
		}

		/* No backdrop-filter (older Firefox, and Safari with it switched off)
		   would leave a nearly clear ring reading as a stray outline, so the
		   tint carries the edge on its own instead. */
		@supports not (backdrop-filter: blur(1px)) {
			.shot { background-color: color-mix(in srgb, #fff 34%, transparent); }
		}

		/* AN APP WINDOW is pushed into the top-left corner and allowed to run
		   out of the frame bottom-right — the comp crops it rather than
		   shrinking it, so it reads as a window onto something larger. */
		.shot.is-ui {
			left: var(--uiX, 6%);
			top: var(--uiY, 9%);
			width: var(--uiW, 104%);
		}

		/* A PHONE is centred and hangs out of the bottom. Width rather than
		   height so the cut is a consequence of the frame, not a second
		   number to keep in step with it.

		   60% of the frame runs the shot to ~152% of the frame's height, so
		   about 42% of the phone is below the cut. That is the point: the
		   device is the subject, and the fold it disappears into is what makes
		   the frame read as a window rather than a thumbnail box. TOP-anchored
		   rather than centred so growing it never lifts the phone toward the
		   backdrop's top edge — the gap above stays 11% of the frame at every
		   width, and every extra pixel goes out of the bottom. */
		.shot.is-phone {
			left: 50%;
			translate: -50% 0;
			top: var(--phoneY, 11%);
			width: var(--phoneW, 60%);
		}

		/* The leaving panel is un-hidden for the length of its exit — module.js
		   hides it again once the animation is done. Without that it would go
		   display:none on the same frame the swap happens and there would be
		   nothing to animate out. It is aria-hidden while it sits there, so
		   the overlap is never two panels to a screen reader. */
		.panel.is-entering .shot {
			animation: feature-media-in-next 380ms var(--ease) both;
		}

		.panel.is-leaving {
			pointer-events: none;

			.shot { animation: feature-media-out-next 220ms var(--ease) both; }
		}

		.media-stack.is-back {
			.panel.is-entering .shot { animation-name: feature-media-in-prev; }
			.panel.is-leaving  .shot { animation-name: feature-media-out-prev; }
		}
	}

	&.surface-declaring {
		/* Hover on the dark ground. The type is already Warm White with
		   nowhere brighter to go, so the ink holds and the rule carries the
		   cue alone, stepping its mix up from the resting 22%. Two classes
		   beats the one the resting pair sits on at the root, so this wins
		   without naming .is-rotating — which it must not, now that the plain
		   list reads the same pair. */
		--hover-ink: var(--ink-inverse);
		--hover-line: color-mix(in srgb, var(--ink-inverse) 55%, transparent);
	}

	&.surface-declaring.is-rotating {
		.rotate-toggle {
			color: var(--ink-inverse);
			border-color: color-mix(in srgb, var(--ink-inverse) 40%, transparent);
		}
	}

	/* No motion means no advancing on its own — module.js reads the same query
	   and never starts the timer, so the list is click-only. The active item
	   still shows a filled rule: the state has to survive without the
	   animation that would otherwise have drawn it. */
	@media (prefers-reduced-motion: reduce) {
		&.is-rotating .features li.is-active .feature-fill {
			animation: none;
			transform: scaleX(1);
		}

		/* No travel and no fade — module.js also skips the leaving state
		   entirely under this query, so the swap is a straight cut. */
		&.is-rotating .panel.is-entering .shot,
		&.is-rotating .panel.is-leaving .shot { animation: none; }

		/* Nothing advances, so there is nothing to stop. 2.2.2 asks for the
		   control only where content moves on its own. */
		&.is-rotating.js-ready .rotate-toggle { display: none; }
	}

	@media (max-width: 1023px) {
		.copy-heading { margin-bottom: 24px; }
		.body { margin-bottom: 24px; }
		.features li { padding-block: 20px; }
		.actions { margin-top: 32px; }
	}

	@media (max-width: 767px) {
		/* The 44px tap target that used to be grown here now comes from the
		   full-row overlay on .feature itself — see THE WHOLE ROW IS THE
		   CONTROL above, which anchors to the li and carries the min(). */

		/* The frame is the container's full width down here, so the same
		   percentages leave an app window the size of a thumbnail. The phone
		   needs no bump any more — 60% is now the base everywhere, past what
		   this breakpoint used to have to reach for on its own. */
		.shot.is-ui { --uiW: 112%; --uiX: 5%; }
	}

	@media (max-width: 599px) {
		.layout { gap: 28px; }
	}
}
