/* Icon tabs.
   Comp (p1 "Role by Role"): bar 1022 x 61.5 at x108, pure white on the Warm
   White ground with a Stone hairline and ~10px padding. Active tab is a
   Community Mist pill 185 x 39.5 sitting 10 left / 11 top inside the bar. */

/* 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. That is exactly what happened here. */
@keyframes tabPanelIn {
	from {
		opacity: 0;
		transform: translate3d(calc(var(--dir, 1) * var(--panelShift)), 0, 0);
	}
	to { opacity: 1; transform: none; }
}

@keyframes tabPanelInMedia {
	from {
		opacity: 0;
		transform: translate3d(calc(var(--dir, 1) * var(--panelShiftMedia)), 0, 0) scale(.97);
	}
	to { opacity: 1; transform: none; }
}

@keyframes tabPanelOut {
	from { opacity: 1; transform: none; }
	to {
		opacity: 0;
		transform: translate3d(calc(var(--dir, 1) * var(--panelShift) * -.55), 0, 0);
	}
}

@keyframes tabPanelFade {
	from { opacity: 0; }
	to { opacity: 1; }
}

section.tabs {

	.tab-bar {
		position: relative;
		display: flex;
		align-items: center;
		gap: 4px;
		/* The comp's bar hugs its tabs (1022 of a 1296 track) rather than
		   filling the width. max-content until it runs out of room, then it
		   caps and scrolls. */
		width: max-content;
		max-width: 100%;
		padding: 10px;
		background: var(--surface-raised);
		border: 1px solid var(--line);
		border-radius: var(--rad);
		overflow-x: auto;
		scrollbar-width: none;
		-webkit-overflow-scrolling: touch;

		&::-webkit-scrollbar { display: none; }
	}

	/* Both alignments are in the comps. Page 1's strip sits on the 108 content
	   inset — labels from x154.8 — and page 2's two strips centre on the page:
	   its first label is at x308.1, where a left strip of that width would put
	   it at 154.8 and a centred one at 314.

	   Auto margins, because the bar is already width:max-content and hugs its
	   tabs. Nothing INSIDE the bar moves, so .tab-indicator's measured offsets
	   are untouched and the travelling indicator needs no help. Below 900 the
	   bar meets max-width:100%, fills the track and scrolls, where an auto
	   margin is a no-op — which is why the field says so. */
	&.bar-centred .tab-bar {
		margin-inline: auto;
	}

	/* Sits behind the tabs and travels between them. Placed without a
	   transition the first time so it appears where it belongs, then the
	   moving class is added — otherwise it slides in from 0,0 on load. */
	.tab-indicator {
		position: absolute;
		top: 10px;
		left: 0;
		height: 39.5px;
		/* concentric inside the bar's 12 across its 10px padding */
		border-radius: max(0px, calc(var(--rad) - 10px));
		background: var(--surface-panel);
		transform: translateX(0);
		pointer-events: none;
	}

	&.tabs-moving .tab-indicator {
		transition: transform .34s var(--ease), width .34s var(--ease);
	}

	/* Comp pill breaks down as 13.5 left padding, 15.75 icon, 8.38 gap,
	   130.5 label, 16.88 right padding = 185. The asymmetric padding is the
	   icon's own side bearing, not a design choice, so 16 both sides is
	   right. The label's 130.5 is -0.03em tracking — the same value the
	   display headings use, and it measured 130.4 here. */
	.tab {
		position: relative;   /* above the indicator */
		z-index: 1;
		flex: none;
		display: inline-flex;
		align-items: center;
		gap: 8px;
		min-height: 39.5px;
		padding: 0 16px;
		border: none;
		border-radius: max(0px, calc(var(--rad) - 10px));
		background: transparent;
		font-family: var(--inter);
		font-size: 1.728rem;
		font-weight: var(--display);
		line-height: 1.15;
		letter-spacing: -.03em;
		color: var(--ink-secondary);
		white-space: nowrap;
		cursor: pointer;
		transition: background-color .15s var(--ease), color .15s var(--ease);

		i { font-size: .84em; line-height: 1; }

		/* Hover reads as "not yet selected" rather than as a second active
		   tab: the same Community Mist the indicator uses, at 55%. Over the
		   bar's white that resolves to rgb(242, 247, 245) — 31 of total
		   channel delta against the active pill's 56, so a hovered tab lands
		   at a bit over half the selected one's weight. The indicator is one shared element travelling between tabs,
		   so this has to be the tab's own background — it paints above the
		   indicator, which is why the active tab is excluded rather than
		   left to stack two mists on top of each other.

		   Gated on a real pointer. On a touch device :hover latches after a
		   tap and the tab would stay lit while a different one is selected,
		   which on this strip is actively misleading. */
		@media (hover: hover) {
			&:hover:not(.is-active) {
				background-color: color-mix(in srgb, var(--surface-panel) 55%, transparent);
				color: var(--ink-deep);
			}
		}

		&.is-active {
			color: var(--ink-accent);
		}

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

	/* Panel motion.

	   The content enters FROM the direction you travelled — pick a tab to the
	   right and it arrives from the right, and the outgoing panel leaves to
	   the left. That is what makes this read as movement rather than as a
	   fade: it answers the thing you just did. --dir is set to 1 or -1 by
	   module.js at the moment of the swap.

	   The sequence is choreographed against the indicator, which travels in
	   .34s. The old panel clears in .13s, then the new one arrives in pieces —
	   heading, media, then each item — so the last of it lands just after the
	   indicator settles. The indicator leads and the content follows.

	   Distances are local rather than the theme's --cascadeShiftUI, which at
	   10px is right for a menu opening under its trigger and far too timid
	   for a panel changing across the width of the page. */
	--panelShift: 36px;
	--panelShiftMedia: 52px;
	--panelIn: .45s;
	--panelStep: .06s;
	/* The list starts after the heading and the media, rather than racing
	   them. Kept as an offset so the loop below needs no arithmetic — the
	   preview resolver only substitutes, it does not evaluate. */
	--panelLead: .1s;
	--panelOut: .13s;




	.panel {
		display: grid;
		grid-template-columns: 1fr;
		gap: 40px;
		margin-top: 56px;

		&[hidden] { display: none; }

		/* The panel is focusable so keyboard users land in its content after
		   the tab strip; it needs a visible state when they do. */
		&:focus-visible {
			outline: 2px solid var(--focus);
			outline-offset: 8px;
			border-radius: var(--rad);
		}

		/* Equal halves. The comp sets the copy column at 420 against a wider
		   media, but the two read as a split and an even one holds better as
		   the media asset changes size. */
		@media (min-width: 900px) {
			grid-template-columns: repeat(2, minmax(0, 1fr));
			gap: 64px;
			align-items: center;
		}

		/* both, so each element holds its start frame until its delay is up
		   rather than flashing at rest first. */
		&.is-entering {
			.panel-heading { animation: tabPanelIn var(--panelIn) var(--easeOut) both; }
			.panel-media   { animation: tabPanelInMedia var(--panelIn) var(--easeOut) both; animation-delay: var(--panelStep); }

			/* Delays are generated rather than driven by a --i on the markup,
			   the same way the header's dropdown cascade is. An item list
			   this short never needs more than a handful. */
			.items li { animation: tabPanelIn var(--panelIn) var(--easeOut) both; }

			/* The step comes from --i on the <li>, written by module.html.

			   This was a HubL loop emitting eight nth-child rules. HubSpot does
			   not run HubL in a module.css, so it rendered as one rule reading
			   `li:nth-child() { animation-delay: calc(var(--panelStep) *  + …) }`
			   — an empty An+B and a calc with a dangling operator, discarded by
			   the parser. Every item animated in together, on top of the panel
			   heading rather than after it. */
			.items li { animation-delay: calc(var(--panelStep) * var(--i, 0) + var(--panelLead)); }
			.tab-column { animation: tabPanelIn var(--panelIn) var(--easeOut) both;
			              animation-delay: calc(var(--panelStep) * var(--i, 0) + var(--panelLead)); }
		}

		&.is-leaving {
			animation: tabPanelOut var(--panelOut) var(--ease) both;
			pointer-events: none;
		}
	}

	/* Reduced motion keeps the swap legible without any travel: a short
	   cross-fade, no stagger, nothing sliding. Outside .panel rather than in
	   it — a media query nested inside a rule that then restates the same
	   selector is what tangled this block the first time. */
	@media (prefers-reduced-motion: reduce) {
		.panel.is-entering {
			.panel-heading,
			.panel-media,
			.items li {
				animation: tabPanelFade .18s linear both;
				animation-delay: 0s;
			}
		}

		.panel.is-leaving { animation: tabPanelFade .1s linear reverse both; }
	}

	/* The copy sits 37.5 in from the tab bar's left edge in the comp — bar at
	   108, copy at 145.5 — rather than lining up with it. The media column is
	   left alone.

	   This IS an intentional departure from the site's left-aligned copy,
	   which otherwise lands on --pageGutter. It was removed on 2026-08-26 on
	   the reasoning that a panel indenting past every other paragraph reads as
	   a mistake, and restored the same day: it is what the comp draws, and the
	   indent is doing work — it sets the panel's copy against the tab bar
	   above it rather than letting the two edges merge into one line.

	   Behind 900px on purpose. Stacked, there is no tab bar beside it to be
	   set against, and the indent would just be a margin. */
	.panel-copy {
		@media (min-width: 900px) {
			padding-inline-start: 37.5px;
		}
	}

	.panel-heading {
		/* Read off the source file: Inter Medium 35.83, leading 35.83 (so
		   line-height 1.0), tracking -40 = -0.04em. */
		font-size: clamp(2.8rem, 2.6vw, 3.584rem);
		line-height: 1;
		font-weight: var(--display);
		letter-spacing: -.04em;
		color: var(--ink-accent);
		margin: 0 0 28px;
	}

	.items {
		display: flex;
		flex-direction: column;
		/* Half the list's 22px rhythm — the other half is padding on the next
		   item, so the separator sits centred in the same gap the list always
		   had rather than adding to it. */
		gap: 11px;
		list-style: none;
		margin: 0;
		padding: 0;

		li + li {
			border-top: 1px solid var(--line);
			padding-top: 11px;
		}

		/* Two columns, two rows, everything placed explicitly.

		   The arrow is put in row 1 with the heading and centred there, so it
		   aligns against the HEADING rather than against the item. It used to
		   sit in a track that spanned heading and copy together, where
		   align-items could only reach the top of the pair — which read as
		   the arrow floating high no matter what value it was given.

		   15.5 is the arrow's measured ink width in the comp; with the 12px
		   column gap the copy lands 27.5 from the arrow, as it does there. */
		li {
			display: grid;
			grid-template-columns: 15.5px minmax(0, 1fr);
			grid-template-rows: auto auto;
			column-gap: 12px;
			row-gap: 6px;

			> i {
				grid-column: 1;
				grid-row: 1;
				align-self: center;
				color: var(--ink-accent);
				font-size: 1.5rem;
				line-height: 1;
			}

			.item-heading { grid-column: 2; grid-row: 1; }
			.item-copy    { grid-column: 2; grid-row: 2; }
		}
	}

	.item-heading {
		font-size: 2.074rem;
		line-height: 1.15;
		font-weight: var(--data);
		color: var(--ink-deep);
		margin: 0;
	}

	/* 14 / 1.25, both measured rather than taken from the scale. The comp's
	   three lines ink 211, 273 and 110 wide, and 14px reproduces them at
	   211, 274 and 110 — 14.4 runs every one of them 3% wide. The baselines
	   sit 17.5 apart, which is 1.25 at that size.

	   Tighter than the 1.4-1.6 the rest of the theme's copy uses, and tighter
	   than the deck's 143-150% for UI body. It is what the comp draws: a
	   deliberate tight cluster under each item heading rather than a
	   paragraph. Worth Jay confirming — see the deltas list. */
	.item-copy {
		font-size: 1.4rem;
		line-height: 1.25;
		color: var(--ink-secondary);
		margin: 0;   /* the grid's row-gap does this now */
	}

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

		img {
			display: block;
			width: 100%;
			height: auto;
		}

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

			/* The 1px lane the portrait Lottie below also gets, for the same
			   reason: the card inside ships chrome-less on purpose, so the
			   frame around it is the container's to draw.

			   AN OUTLINE RATHER THAN A BORDER, and not as a matter of taste.
			   aspect-ratio resolves against the BORDER box, so a 1px border
			   takes its 2px out of the CONTENT box and leaves that box off the
			   ratio: 380 x 740.5 outside became 378 x 738.5 inside, which is
			   0.5119 against the 0.5132 the phone is drawn at. The card fits
			   itself to the smaller of the two axes, so it came up 736.6 tall
			   in a 738.5 box and sat with about a pixel of slack above and
			   below it — visible as a gap at the top and bottom of the
			   handset while the sides stayed tight.

			   An outline takes no layout, so both boxes keep the ratio and
			   both scale factors resolve to 380/340 exactly. offset -1px pulls
			   it inside the box, where it follows the radius set below.

			   NOT AN INSET BOX-SHADOW, which is the other way to draw a
			   hairline without disturbing layout: inset shadows paint above
			   the background but BELOW child content, and the iframe covers
			   the box, so it would never be seen. Outlines paint above. */
			outline: 1px solid var(--line);
			outline-offset: -1px;

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

			/* A PHONE GETS A PHONE'S FRAME — the same rule the portrait
			   Lottie below gets, arrived at from the other direction.

			   data-demo-shape is written by module.js from a message the card
			   posts on load; it is the only thing out here that knows whether
			   the iframe holds a handset or the desktop UI.

			   WHAT THIS REPLACES. Above 900 the panel is two equal columns, so
			   at a 1024 viewport the media box is 403 x 302. crop=1 fits the
			   card's 720 x 540 stage into that at 0.559, which drew the device
			   190px wide — the sim's own UI text far under a readable size,
			   floating in a box twice its width. That is the thumbnail the
			   crop was introduced to prevent, reappearing at a width nobody
			   checked.

			   380 IS THE THRESHOLD, NOT A TASTE. ui-demo.html shows the whole
			   device only while its own window is under 400 and otherwise
			   keeps the landscape crop, so 380 is the largest frame that still
			   gets an uncropped phone — the biggest the device can be drawn
			   without going back to a crop floating in a portrait box. It does
			   not move unless that threshold moves with it.

			   Unlike the Lottie cap below this is a live DOM scaled by a
			   transform, so at 380 against a native 340 nothing is resampled
			   and the type stays sharp. Centred, because at every width the
			   phone is the only thing in its half. */
			&[data-demo-shape="phone"] {
				aspect-ratio: 39 / 76;
				width: min(380px, 100%);
				margin-inline: auto;

				/* THE CORNER FOLLOWS THE HANDSET, NOT THE PANEL.

				   --rad is the radius every other media box gets, and against
				   a device whose own corner is far rounder it reads as a
				   rectangle with a phone loose inside it. The container is
				   exactly the device's size here, so the two corners should
				   be one corner.

				   TWO PERCENTAGES, NOT ONE LENGTH. The device is 46px round
				   at its native 340 wide and scales with the frame, so the
				   radius has to scale too — but a single percentage resolves
				   per AXIS, giving 13.5% of a 380 width and 13.5% of a 741
				   height, which is an ellipse. Splitting it puts the same
				   absolute value on both axes at every width:

				     horizontal   46 / 340            = 13.5294%
				     vertical     46 / 340 x 39 / 76  =  6.9437%

				   The second only works because the box's ratio is pinned to
				   39/76 directly above — height is a known multiple of width,
				   so a percentage of one converts into a percentage of the
				   other. Change that ratio and this has to be recomputed with
				   it. At 380 both resolve to 51.4px. */
				border-radius: 13.5294% / 6.9437%;
			}
		}

		/* THE FRAME IS THE CONTAINER'S JOB, NOT THE ANIMATION'S.

		   The Lottie files ship square and chrome-less on purpose — a card
		   that drew its own 16px corners and border would carry them into
		   every slot it was ever placed in, at whatever size that slot
		   happened to be, and the radius would scale with the artwork. Drawn
		   here instead, the border stays 1px and the corners stay --rad
		   however wide the panel runs.

		   background is set because the animation's own ground is part of
		   the artwork: while the JSON is still loading the box would
		   otherwise be a transparent hole with a shadow under it. */
		&.is-lottie {
			aspect-ratio: var(--ratio, 4 / 3);
			background: var(--surface-raised, #fff);
			border: 1px solid var(--line);
			box-shadow: var(--shadowRaised);

			.media-lottie,
			svg {
				display: block;
				width: 100%;
				height: 100%;
			}

			/* A PHONE IS NEVER ENLARGED PAST THE SIZE IT WAS DRAWN.

			   js/lottie-player.js publishes the animation's own --ratio,
			   --lottie-w and --lottie-h once it loads, and adds .is-portrait
			   when it is taller than it is wide.

			   Left to the landscape rule a 340x540 device resolves 813px tall
			   in a 512px panel — it stops reading as a phone and starts
			   reading as a website. So it takes its native width, centred,
			   and only ever shrinks: min() lets a narrow column cut it down
			   but nothing scales it up. aspect-ratio then follows from
			   --ratio, so the height needs no rule of its own. */
			&.is-portrait {
				width: min(var(--lottie-w, 340px), 100%);
				margin-inline: auto;
			}
		}
	}

	@media (max-width: 899px) {
		.tab-bar {
			/* Fades at the edges so a scrollable strip reads as scrollable
			   rather than as a list that happens to be cut off. */
			mask-image: linear-gradient(to right, transparent 0, #000 16px,
			            #000 calc(100% - 16px), transparent 100%);
		}

		.panel { margin-top: 36px; gap: 28px; }

		.panel-heading { margin-bottom: 20px; }
	}

	/* --- Icon columns --------------------------------------------------
	   The Icon Pair Columns fields, set like the item list above them.

	   The COLUMN GRID is this block's own — auto-fit tracks so 2, 3 or 4
	   columns resolve and a narrow panel stacks. Everything INSIDE a column
	   is deliberately the same as an `.items li`: same 15.5px icon track,
	   same 12px column gap and 6px row gap, same icon centred on the heading
	   row rather than on the pair, and the same two type roles. A reader
	   should not be able to tell that the two blocks are different fields.

	   The one difference is the body element. An item's copy is a <p>; a
	   column's is a richtext <div>, so the size lands on its paragraphs. */
	/* TWO COLUMNS, and the numbers are the whole point.

	   The panel's copy column is 543 wide at 1440 — the panel is 1224, split
	   with the media, and .panel-copy gives 37.5 of that back as its indent.
	   That is the space these have to live in, and it is much narrower than
	   the full-width block the Icon Pair Columns module gets.

	   At a 240 minimum and a 40 gutter, two tracks need 520 of 543. It fits,
	   but only just, and anything that trims the panel — a longer indent, a
	   wider media column, a narrower window — drops it to one and the pair
	   stacks. 200 and 24 need 424, which holds two columns down to a
	   considerably narrower panel while still stacking on a phone.

	   auto-fit rather than a fixed 2: the field takes up to four, and three or
	   four in 543 would be unreadable, so they wrap to two rows instead of
	   being crushed. */
	.tab-columns {
		display: grid;
		grid-template-columns: repeat(auto-fit, minmax(min(100%, 200px), 1fr));
		gap: 28px 24px;
		margin-top: 40px;
	}

	.tab-column {
		display: grid;
		grid-template-columns: 15.5px minmax(0, 1fr);
		grid-template-rows: auto auto;
		column-gap: 12px;
		row-gap: 6px;
		align-content: start;

		.col-icon    { grid-column: 1; grid-row: 1; align-self: center; }
		.col-heading { grid-column: 2; grid-row: 1; }
		.col-body    { grid-column: 2; grid-row: 2; }

		/* No icon: the heading takes the whole width rather than leaving a
		   15.5px hole, matching how an item with no arrow would read. */
		&:not(.has-icon) {
			grid-template-columns: minmax(0, 1fr);

			.col-heading,
			.col-body { grid-column: 1; }
		}
	}

	.col-icon {
		color: var(--ink-accent);
		font-size: 1.5rem;
		line-height: 1;
	}

	/* .item-heading's weight and colour, but SMALLER — 1.5rem against its
	   2.074. Deliberate divergence, ruled 2026-08-26: these headings are
	   written in caps, and caps at 2.074 shout next to the sentence-case item
	   headings in the same panel. Do not "restore" parity with .item-heading;
	   the two are the same role set at two sizes for that reason.

	   No tracking added. Caps at this size usually want some — the theme's
	   eyebrow runs caps at .18em — but tracking would make these read as
	   eyebrows rather than headings, and it has not been asked for. Flagged
	   rather than applied. */
	.col-heading {
		font-size: 1.5rem;
		line-height: 1.15;
		font-weight: var(--data);
		color: var(--ink-deep);
		margin: 0;
	}

	/* Same as .item-copy — 14 / 1.25, the comp's tight cluster rather than the
	   theme's 1.4-1.6 paragraph. See the note on .item-copy above; the same
	   caveat applies, and the two should move together if Jay changes it. */
	.col-body {
		font-size: 1.4rem;
		line-height: 1.25;
		color: var(--ink-secondary);

		> p {
			margin: 0 0 6px;
			font-size: inherit;
			line-height: inherit;
		}

		> p:last-child { margin-bottom: 0; }
	}
}
