/**
 * At a Glance — card archetypes + theme TextLink (SPEC §4 / §6).
 *
 * Geometry is fixed here, not in BB: 440px cards, 30px gap, top-aligned, three
 * designed heights (324/440/502). The ragged bottom edge that mixing those heights
 * produces IS the design — never equalize them (SPEC §2).
 *
 * The JS owns exactly one thing: --tbl-glance-offset on the viewport. Both track
 * copies read it, so there is a single source of truth for position and the two
 * copies can never drift apart (SPEC §3).
 *
 * Two Figma hexes have no palette token and are unbound wireframe drift; both are
 * exposed as module tokens here, following the Phase 02 footer precedent which mapped
 * the same #200648 to --Primary-90. Flag to Chris (SPEC §4).
 */

.tbl-at-a-glance {
	/*
	 * Round 5 (Ben) — the card scale unit. EVERY dimension inside a card is
	 * `calc(<design px> * var(--tbl-glance-u))`, so one token scales the whole
	 * card set uniformly and each value stays legible as the number Chris drew.
	 *
	 * Why a LENGTH and not a plain 0.65-1 factor: a fluid unitless factor cannot
	 * be expressed. calc() may not add a length to a number, so
	 * `zoom: clamp(0.65, calc(0.031vw + 0.529), 1)` is INVALID — and it fails
	 * silently, computing to zoom:1 with no console error (verified in Chrome,
	 * 2026-07-15). The percentage form fails the same way. Making the token a
	 * length sidesteps it: number * length is legal, so `calc(440 * var(--u))`
	 * resolves to 440px at the top of the range and 286px at the bottom.
	 *
	 * Curve: 1.0 at >=1024px, easing to 0.65 at 390px (Ben's call — hold Chris's
	 * designed 440 across every desktop width, scale only for tablet/phone).
	 * The true slope is 0.35/6.34 = 0.0552050...vw, which does not terminate; it is
	 * rounded UP to 0.05521 so the line slightly overshoots 1.0 just before 1024 and
	 * clamp() pins it at exactly 1.0 from there up. Rounding DOWN instead left the
	 * card at 439.97px at 1024 — invisible, but this way the contract is exact.
	 * At 390 the card lands at 286px, leaving ~104px of the next card visible —
	 * a 440px card used to overflow a 390px screen with no hint more existed.
	 *
	 * transform: scale() is the wrong tool here: it does not affect the layout
	 * box, so the cards would still occupy 440px and leave gaps, and the rail's
	 * measured period would desync from the flex layout. --u changes real widths.
	 */
	--tbl-glance-u: clamp(0.65px, calc(0.05521vw + 0.4347px), 1px);

	--tbl-glance-card-w: calc(440 * var(--tbl-glance-u));
	--tbl-glance-gap: calc(30 * var(--tbl-glance-u));

	/* Unbound in Figma — nearest token --Neutral-20 (#dbdbdb) is not close. */
	--tbl-glance-card-bg: rgba(230, 230, 235, 0.4);
	/* Exact match for the existing --footer-accent / --nav-bolt-color value. */
	--tbl-glance-link-on-dark: #b390e7;

	--tbl-glance-offset: 0px;

	position: relative;
	width: 100%;
}

/* ---------------------------------------------------------------- header ---- */

/*
 * The head (rule + title + link) is capped by the Header Max Width setting and
 * centred. The rail is NOT inside this wrapper — it stays full-bleed, so it fills
 * whatever width the BB row gives it (set the row to Full Width, per SPEC §9).
 */
.tbl-at-a-glance__head {
	max-width: var(--tbl-glance-header-max-width, 1518px);
	margin: 0 auto 34px;
	padding: 0 20px;
}

/* Rule sits ABOVE the title/link. */
.tbl-at-a-glance__rule {
	border: 0;
	border-top: 1px solid var(--Neutral-20, #dbdbdb);
	margin: 0 0 18px;
}

.tbl-at-a-glance__header {
	display: flex;
	align-items: baseline;
	justify-content: space-between;
	gap: 20px;
}

/*
 * Round 5 (Ben) — fluid type: 20px down to a 13px floor.
 *
 * The calc() is simply the straight line through those two points: 20px at the
 * 1518px head cap, 13px at a 390px phone. Slope = (20-13)/(1518-390) = 0.62% of
 * the viewport per px; 10.58px is that line's intercept. clamp() pins both ends,
 * so it never exceeds 20px on a wide monitor (the head stops growing at 1518
 * anyway, so growing the type past it would only break away from the design) and
 * never drops under Ben's 13px floor on a narrow one.
 *
 * Deliberately px, not rem: every dimension in this module is px, and a lone rem
 * here would be the odd one out.
 */
.tbl-at-a-glance__title,
.tbl-at-a-glance__header-link {
	font-family: var(--body-font);
	font-size: clamp(13px, calc(0.62vw + 10.58px), 20px);
	line-height: 1.5;
	letter-spacing: 0.2px;
	margin: 0;
}

.tbl-at-a-glance__title {
	color: var(--Neutral-100, #0f0f0f);
}

.tbl-at-a-glance__header-link {
	color: var(--Neutral-100, #0f0f0f);
	text-decoration: underline;
	text-underline-offset: 4px;
	white-space: nowrap;
}

.tbl-at-a-glance__header-link:hover,
.tbl-at-a-glance__header-link:focus-visible {
	color: var(--Primary-Alt-50-Base, #af85ec);
}

/* -------------------------------------------------------------- the rail ---- */

.tbl-at-a-glance__viewport {
	display: flex;
	gap: var(--tbl-glance-gap);
	overflow: hidden;
	cursor: grab;
	user-select: none;
	-webkit-user-select: none;
	touch-action: pan-y; /* let the page scroll vertically; we own horizontal */
}

.tbl-at-a-glance__viewport.is-dragging {
	cursor: grabbing;
}

.tbl-at-a-glance__track {
	display: flex;
	gap: var(--tbl-glance-gap);
	flex: 0 0 auto;
	margin: 0;
	padding: 0;
	list-style: none;
	align-items: flex-start; /* top-aligned — this is what makes the ragged edge */
	transform: translate3d(var(--tbl-glance-offset), 0, 0);
	will-change: transform;
}

/*
 * No JS: the transform never moves, so fall back to native horizontal scrolling
 * and drop the duplicate track — otherwise every card reads twice (SPEC §8).
 */
.no-js .tbl-at-a-glance__viewport {
	overflow-x: auto;
	cursor: auto;
}

.no-js .tbl-at-a-glance__track[aria-hidden='true'] {
	display: none;
}

/* ----------------------------------------------------------------- cards ---- */

.tbl-at-a-glance__card {
	position: relative;
	flex: 0 0 auto;
	/* Explicit: the 440/30 pitch is load-bearing for the wrap, so it must not
	   depend on the global reset still setting border-box. */
	box-sizing: border-box;
	width: var(--tbl-glance-card-w);
	/* --tbl-glance-card-height is emitted per-card by frontend.php as a bare NUMBER
	   (324 | 440 | 502), not a px length, precisely so it can be multiplied by --u
	   here — length * length is invalid. Keep PHP and this line in step. */
	height: calc(var(--tbl-glance-card-height, 324) * var(--tbl-glance-u));
	padding: calc(30 * var(--tbl-glance-u)) calc(30 * var(--tbl-glance-u))
	         calc(30 * var(--tbl-glance-u)) calc(45 * var(--tbl-glance-u));
	border-radius: calc(5 * var(--tbl-glance-u));
	overflow: hidden;
	background-color: var(--tbl-glance-card-bg);
	color: var(--Neutral-100, #0f0f0f);
}

.tbl-at-a-glance__card--dark {
	background-color: var(--Primary-90, #200147);
	color: var(--White, #fff);
}

.tbl-at-a-glance__photo {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	object-fit: cover;
	pointer-events: none;
}

/* Photo cards carry white text over the image — keep it legible regardless of shot. */
.tbl-at-a-glance__card--has-photo {
	color: var(--White, #fff);
}

.tbl-at-a-glance__card--has-photo::after {
	content: '';
	position: absolute;
	inset: 0;
	background: linear-gradient(to bottom, rgba(0, 0, 0, 0.45) 0%, rgba(0, 0, 0, 0) 55%);
	pointer-events: none;
}

.tbl-at-a-glance__card-body {
	position: relative;
	z-index: 1;
	display: flex;
	flex-direction: column;
	gap: calc(10 * var(--tbl-glance-u));
	align-items: flex-start;
}

/*
 * Round 5 — the eyebrow is the one card element that does NOT scale (Ben's call).
 * At 10px it is already at its legibility floor, so scaling it uniformly with the
 * card put it at 6.5px on a 390px phone — uppercase, 2px-tracked, and unreadable.
 *
 * Written as plain px rather than max(10px, calc(10 * var(--tbl-glance-u))): --u
 * never exceeds 1px, so calc(10 * var(--u)) can never exceed 10px and that max()
 * would always resolve to 10px anyway. Same result, without implying it moves.
 */
.tbl-at-a-glance__eyebrow {
	font-family: var(--body-font);
	font-size: 10px;
	line-height: 17.78px;
	letter-spacing: 2px;
	text-transform: uppercase;
	margin: 0;
}

.tbl-at-a-glance__heading {
	--tbl-glance-line-h: calc(22 * var(--tbl-glance-u));

	font-family: var(--header-font);
	font-size: calc(20 * var(--tbl-glance-u));
	line-height: var(--tbl-glance-line-h);
	letter-spacing: calc(0.2 * var(--tbl-glance-u));
}

/*
 * §4a — the line-height MUST be declared on the <p>, not merely inherited from the
 * wrapper above. theme.css:283 has a global `p { line-height: var(--body-line-height) }`
 * (1.5 -> 30px at our 20px font), and a direct rule on an element always beats a value
 * inherited from an ancestor — specificity never enters into it, because inheritance
 * only supplies a value when NO rule matches. Without this line the wrapper computes
 * 22px, the <p> inside it computes 30px, and every span inherits 30px.
 *
 * This is why the headings were 30px for three rounds while this file said 22px, and
 * why the §13 rotating slots stepped 30px — the slots are translateY((k-1) * 100%) of
 * the line box, so they follow this value with no separate constant to chase.
 *
 * Figma geometry: 10621:4458's heading node is 66px for 3 lines = 22px exactly.
 */
.tbl-at-a-glance__heading-primary,
.tbl-at-a-glance__heading-secondary {
	margin: 0;
	line-height: var(--tbl-glance-line-h);
}

/* Author-controlled breaks: every authored line is its own row, never re-wrapped. */
.tbl-at-a-glance__heading-line {
	display: block;
}

/*
 * §6a — flat muted (Ben, round 4: "Remove the opacity gradient altogether. The
 * rotating headlines replace that.").
 *
 * Everything the gradient needed is gone: the --tbl-glance-fade-from/-to/-hold tokens,
 * the linear-gradient, background-clip:text, color:transparent, the dark/photo stop
 * overrides, and the hover/focus-within clears-the-gradient rule. The rotating heading
 * (§13) is the "there is more here" cue now.
 *
 * 40% is §4's spec for the muted line and is what Figma 10621:4458 draws. opacity
 * rather than a colour keeps it themed for free on light, dark and photo cards.
 */
.tbl-at-a-glance__heading-secondary {
	opacity: 0.4;
}

/* ------------------------------------------------------- stat / logo / clock -- */

.tbl-at-a-glance__stat {
	position: absolute;
	right: calc(45 * var(--tbl-glance-u));
	bottom: calc(26 * var(--tbl-glance-u));
	z-index: 1;
	font-family: var(--header-font);
	font-size: calc(60 * var(--tbl-glance-u));
	line-height: calc(62 * var(--tbl-glance-u));
	letter-spacing: calc(-1 * var(--tbl-glance-u));
	white-space: nowrap;
	margin: 0;
}

/* ------------------------------------------------------------ §14 logo cycler --
 * The box is positioned and sized; every logo `contain`s into it, right-aligned.
 * Figma draws the Clemson mark at 92.97x23.19 at right ~30 / bottom ~28
 * (10621:4458). The right:45/bottom:30 below is v1.1.1's existing placement, kept
 * deliberately — see the QA note in Task 4 before changing it.
 *
 * Round 5 (Ben, with real client logos finally in the field): 100x43. This BOX is
 * how the cap is expressed — because every logo `object-fit: contain`s into it,
 * the box's width/height ARE each logo's max-width/max-height, with aspect ratio
 * preserved. A wide, short mark hits 100px first and lands under 43 tall; a tall
 * one hits 43px first and lands under 100 wide. Neither ever exceeds either bound,
 * so no per-image rule is needed.
 *
 * This supersedes v1.1.1's `width: 45%` / 24px-tall box and settles the round-3
 * leftover ("does 24px suit real logos?" — no, they needed 43). 45% of the fixed
 * 440px card was 198px, so 100px is a shrink at every breakpoint: nothing reflows.
 */
.tbl-at-a-glance__logos {
	position: absolute;
	right: calc(45 * var(--tbl-glance-u));
	bottom: calc(30 * var(--tbl-glance-u));
	z-index: 1;
	width: calc(var(--tbl-glance-logo-w, 100) * var(--tbl-glance-u));
	height: calc(var(--tbl-glance-logo-h, 43) * var(--tbl-glance-u));
	pointer-events: none;
}

.tbl-at-a-glance__logo {
	width: 100%;
	height: 100%;
	object-fit: contain;
	object-position: right center;
}

.tbl-at-a-glance__logo-slot {
	position: absolute;
	inset: 0;
}

.tbl-at-a-glance__logos--cycling .tbl-at-a-glance__logo-slot {
	opacity: 0;
	/* Outgoing: leaves IMMEDIATELY. */
	transition: opacity 0.25s;
}

.tbl-at-a-glance__logos--cycling .tbl-at-a-glance__logo-slot.is-active {
	opacity: 1;
	/* Incoming: waits 125ms, so the outgoing logo is already half gone before this
	 * one appears. The asymmetry is the whole trick — a symmetric dissolve muddies
	 * two logos into each other at the midpoint (SPEC §14). */
	transition: opacity 0.25s 125ms;
}

.tbl-at-a-glance__clock {
	position: absolute;
	top: calc(50% + 38 * var(--tbl-glance-u));
	left: 50%;
	transform: translate(-50%, -50%);
	width: calc(250 * var(--tbl-glance-u));
	height: calc(250 * var(--tbl-glance-u));
	max-width: calc(100% - 90 * var(--tbl-glance-u));
	pointer-events: none;
}

/*
 * Face = the Figma export (node 10618:3957) inlined verbatim: 12 major ticks, 48
 * minor ticks and the 12 numerals. Its strokes were rewired to currentColor so it
 * themes with the card instead of carrying Figma's wireframe greys.
 */
.tbl-at-a-glance__clock-face,
.tbl-at-a-glance__clock-face svg,
.tbl-at-a-glance__clock-hands {
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
}

/* Hands are drawn here, not in the asset — they move. */
.tbl-at-a-glance__clock-hand {
	stroke: var(--Neutral-100, #0f0f0f);
	stroke-linecap: round;
}

.tbl-at-a-glance__card--dark .tbl-at-a-glance__clock-hand,
.tbl-at-a-glance__card--has-photo .tbl-at-a-glance__clock-hand {
	stroke: var(--White, #fff);
}

.tbl-at-a-glance__clock-hand--hour {
	stroke-width: 4;
}

.tbl-at-a-glance__clock-hand--minute {
	stroke-width: 4;
}

/* The one accent on the face — thin lavender second hand, per Figma. */
.tbl-at-a-glance__clock-hand--second {
	stroke: var(--Primary-Alt-50-Base, #af85ec);
	stroke-width: 1.5;
}

.tbl-at-a-glance__clock-pin {
	fill: var(--Primary-Alt-50-Base, #af85ec);
}

.tbl-at-a-glance__readout {
	position: absolute;
	left: calc(45 * var(--tbl-glance-u));
	bottom: calc(30 * var(--tbl-glance-u));
	z-index: 1;
	font-family: var(--header-font);
	/* §Round 5 legibility floor — see the note on __link below. */
	font-size: max(12px, calc(14 * var(--tbl-glance-u)));
	line-height: calc(22 * var(--tbl-glance-u));
	letter-spacing: calc(0.14 * var(--tbl-glance-u));
	font-variant-numeric: tabular-nums;
	margin: 0;
}

/*
 * §6 — the theme TextLink. The arrow-swap is gone (Ben, round 4: "Take away the arrow
 * and make the hover state match the theme text links"), superseding round 1's
 * decision 4. Mirrors tbl-intro-statement's .tb-intro__cta, which is the Figma
 * TextLink component 10411:4439 / 10411:4453.
 *
 * The underline is always on, at rest and on hover — only the colour moves.
 */
.tbl-at-a-glance__link {
	position: absolute;
	left: calc(45 * var(--tbl-glance-u));
	bottom: calc(24 * var(--tbl-glance-u));
	z-index: 2;
	display: inline-block;
	width: max-content;
	font-family: var(--header-font);
	/*
	 * Round 5 legibility floor (Ben's call): 14px scaled uniformly reached 9.1px at
	 * 390 — too small to read, and this one is an interactive target, not decoration.
	 * max() holds it at 12px from ~765px down; above that it tracks the card as
	 * normal, so desktop is untouched. Unlike the eyebrow's floor this max() is live:
	 * 14 * --u genuinely exceeds 12px for part of the range.
	 *
	 * line-height keeps scaling on purpose. It only sets the box of a single line
	 * here, and pinning it too would push the link off its bottom inset.
	 */
	font-size: max(12px, calc(14 * var(--tbl-glance-u)));
	line-height: calc(30 * var(--tbl-glance-u));
	letter-spacing: calc(0.14 * var(--tbl-glance-u));

	/* Rest colour comes from the card for free: light cards set --Neutral-100, and
	 * dark AND photo cards both already set white (§4). That is exactly §6's rest
	 * column, so there is no per-theme rest rule to write. */
	color: inherit;

	text-decoration-line: underline;
	text-decoration-style: solid;
	text-decoration-skip-ink: auto;
	text-underline-position: from-font;
	transition: var(--button-easing);
}

/* Light card: the literal theme token. */
.tbl-at-a-glance__link:hover,
.tbl-at-a-glance__link:focus-visible {
	color: var(--Primary-Alt-50-Base);
}

/*
 * Dark / photo: OUR pair, not Figma's — the TextLink component's type is
 * `color?: "On White"`, a one-member union, so there is no On-Dark variant to match
 * (§6). Do NOT use --Primary-Alt-50-Base here: against the card's white rest it is
 * fine, but #b390e7 -> #af85ec would be rgb(179,144,231) -> rgb(175,133,236), which
 * is a rounding error, not a hover.
 */
.tbl-at-a-glance__card--dark .tbl-at-a-glance__link:hover,
.tbl-at-a-glance__card--dark .tbl-at-a-glance__link:focus-visible,
.tbl-at-a-glance__card--has-photo .tbl-at-a-glance__link:hover,
.tbl-at-a-glance__card--has-photo .tbl-at-a-glance__link:focus-visible {
	color: var(--tbl-glance-link-on-dark);
}

/* ------------------------------------------------------------- fallbacks ---- */

/*
 * Reduced motion: autoplay is switched off in JS, but drag stays enabled so no card
 * is unreachable (SPEC §8). Here we only kill the decorative transitions — the
 * reference site ignores this preference entirely; we do not copy that.
 */
@media (prefers-reduced-motion: reduce) {
	.tbl-at-a-glance__link {
		transition: none;
	}

	.tbl-at-a-glance__track {
		will-change: auto;
	}

	.tbl-at-a-glance.is-entering .tbl-at-a-glance__card--enter {
		opacity: 1;
		transform: none;
		transition: none;
	}
}

/*
 * BB editor: the rail must sit still so the module can be selected and edited.
 * The JS also bails, but pin the fallback here too.
 */
.fl-builder-edit .tbl-at-a-glance__viewport {
	cursor: auto;
}

.fl-builder-edit .tbl-at-a-glance__track[aria-hidden='true'] {
	display: none;
}

/* --------------------------------------------------------------- §12 entrance --
 * Armed by JS (.is-entering), released by JS (.has-entered). NEVER make the
 * from-state the plain default: the BB editor and no-JS never reach the release
 * and every card would be permanently invisible (SPEC §12).
 *
 * There is deliberately NO perspective — the reference has none on any ancestor,
 * so rotateY/rotateX flatten into a squash off center-bottom. Adding perspective
 * would be a different, more dramatic effect than the reference shows.
 */
.tbl-at-a-glance__card--enter {
	transform-origin: center bottom;
}

.tbl-at-a-glance.is-entering .tbl-at-a-glance__card--enter {
	opacity: 0;
	transform: translateY(40%) rotateY(12deg) rotateX(45deg);
}

.tbl-at-a-glance.is-entering.has-entered .tbl-at-a-glance__card--enter {
	opacity: 1;
	transform: none;
	/* cubic-bezier(.25,.46,.45,.94) == GSAP power1.out == what the reference
	 * ACTUALLY runs. Its config says expo.inOut but puts `ease` in fromVars where
	 * GSAP ignores it. Measured, not guessed — SPEC §12. */
	transition:
		opacity 1s cubic-bezier(.25, .46, .45, .94) calc(0.05s + var(--i, 0) * 0.1s),
		transform 1s cubic-bezier(.25, .46, .45, .94) calc(0.05s + var(--i, 0) * 0.1s);
}

/* -------------------------------------------------------- §13 rotating heading --
 * Slot k=1 is live at translateY(0); k=2 sits a line below at 66%; k=3 two below
 * at 33%; everything else is parked and invisible. A counter changes; these
 * transitions do all of the animation — there is no tween anywhere (SPEC §13).
 */

/*
 * .tbl-at-a-glance__card-body is align-items:flex-start (shrink-to-fit width) —
 * correct for ordinary in-flow text, but the rotating heading's lines are ALL
 * position:absolute (below) and so contribute NO intrinsic width to that
 * calculation. Without this, the flex item collapses toward 0 width, the
 * absolutely-positioned lines get a near-zero containing block, and every line
 * wraps word-by-word instead of rendering on one line. Scoped to the rotating
 * modifier (added in frontend.php only when rotate_heading = yes) so the
 * non-rotating shrink-to-fit sizing is untouched (v1.1.1 stays byte-for-byte).
 */
.tbl-at-a-glance__heading--rotating {
	align-self: stretch;
}

.tbl-at-a-glance__heading-primary--rotating {
	position: relative;
	/* The lines are absolute, so this block would collapse and the eyebrow/stat/link
	 * would reflow up into it. Reserve the 3 visible slots. */
	min-height: calc(var(--tbl-glance-line-h, 22px) * 3);
}

.tbl-at-a-glance__heading-primary--rotating .tbl-at-a-glance__heading-line {
	position: absolute;
	left: 0;
	top: 0;
	opacity: 0;
	transform: translateY(300%);
	transition:
		transform 0.4s cubic-bezier(.25, .46, .45, .94),
		opacity 0.4s;
}

/* The counter=0 state. Also the entire no-JS / reduced-motion rendering. */
.tbl-at-a-glance__heading-primary--rotating .tbl-at-a-glance__heading-line:nth-child(1) {
	opacity: 1;
	transform: translateY(0);
}
.tbl-at-a-glance__heading-primary--rotating .tbl-at-a-glance__heading-line:nth-child(2) {
	opacity: 0.66;
	transform: translateY(100%);
}
.tbl-at-a-glance__heading-primary--rotating .tbl-at-a-glance__heading-line:nth-child(3) {
	opacity: 0.33;
	transform: translateY(200%);
}
