/* Case Study interior — Phase 6, plan 09/10/11/12/13.
   Styles the chrome plan 08 assembled: page shell, hero, view bar/toggle,
   floating CTA, next-case bar/teaser/sentinel (this plan), plus the two
   view bodies and their motion (plans 10-13). Registered at a literal
   version '1.0.0' by tbl-case-studies.php, NOT imported through
   style.css, so it is UNLAYERED — same cascade tier as theme.css,
   resolved by source order/specificity. Never wrap this file's rules in
   a CSS layer block; doing so would demote every rule below theme.css
   (Phase 11's repeated "should work but silently didn't" defect class).

   FLAGS — raw hex with no token match, declared once here and reused via
   var() everywhere below so a single edit updates every use:
     --csi-hero-bg default #dc2737 / --csi-hero-fg default #37011b  — inline postmeta defaults (plan 05)
     --csi-teaser-bg default #000939                                 — inline postmeta default (plan 05)
     --csi-badge-bg   #2653f8   — teaser badge, unbound
     --csi-toggle-off #1b1b1c   — toggle segment resting label colour, unbound
     --csi-body-color #4d5153   — FLAG, unbound (reading/visual body copy, plan 10)
     --csi-chip-bg    #e8e8e8   — FLAG, unbound (numbered-section chip, plan 10)
     visual-view page bg #F2F2F2 — unbound, no close token match
     reading-view page bg #efefef IS an exact token match, var(--Neutral-10-Base) — used as a token below, listed here only for completeness
   Toggle radii 42.02 / 38.845 and CTA radius 63.054 are Motto-measured,
   not theme tokens — recorded here, not re-flagged per rule. Nextbar bed
   rgb(207,213,215) and fill rgb(27,27,28) are Motto's, merged onto
   Chris's plain-rule geometry per 06-05's own flagged discretionary call
   — surfaced again in this plan's summary for the plan 15 design review. */

.tbl-csi {
	--csi-rail-x:        2.8125%;    /*   45 / 1600 — reading rail left edge   */
	--csi-rail-w:       32.1875%;    /*  515 / 1600 — reading rail width       */
	--csi-col-x:              42%;   /*  672 / 1600 — content column left edge */
	--csi-col-w:        49.1875%;    /*  787 / 1600 — content column width     */
	--csi-eyebrow-x:     8.4375%;    /*  135 / 1600 — visual-view left rail    */
	--csi-rail-label-x: 20.0893%;    /*  135 / 672 — a fraction of the rail TRACK, not of 1600 */
	--csi-band-inset:       1.6%;    /*  D-02 locked; Figma measures 1.71875%  */
	--csi-band-w:          96.8%;    /*  100 - 2 x 1.6                          */
	--csi-tile-w:          47.6%;    /*  (96.8 - 1.6) / 2                       */
	--csi-hero-x:        4.6875%;    /*   75 / 1600 — hero collage left        */
	--csi-hero-cw:      91.3125%;    /* 1461 / 1600 — hero collage width       */
	--csi-viewbar-h:        53px;    /*  cancelled by an equal negative margin-top     */
	--csi-nextbar-x:      4.125%;    /*   66 / 1600                             */
	--csi-nextbar-w:     91.875%;    /* 1470 / 1600                             */
	--csi-testi-x:        13.75%;    /*  220 / 1600 — visual testimonial        */
	--csi-testi-w:     74.0656%;     /* 1185.05 / 1600                          */
	--csi-body-color:    #4d5153;    /* FLAG: unbound, no token                 */
	--csi-chip-bg:       #e8e8e8;    /* FLAG: unbound                           */
	--csi-badge-bg:      #2653f8;    /* FLAG: unbound                           */
	--csi-toggle-off:    #1b1b1c;    /* FLAG: unbound                           */
	--csi-rule:           #6c6c6c;   /* Pastel #131 (Chris) — Field Notes row rules */
	--csi-media-radius:     30px;    /* FLAG: unbound — no Corner-Radius token between
	                                    XL 24 and XXL 36. Shared by the hero collage and
	                                    the teaser collage (Pastel #52) so the two
	                                    cannot drift. (The viewbar scrim shared it
	                                    too until #94 removed the scrim.) */
	--csi-photo-gap:         24px;   /* Ben, round 7: 24px between every photo,
	                                    12px stacked below 768. Supersedes D-02's
	                                    "two-up gap = band inset" for the GAP only;
	                                    the side inset still uses --csi-band-inset. */
	--csi-gap-scale:            1;   /* plan 13: 0.66 at 1023.98px, scales the  */
	                                  /* six per-slot media gaps below           */

	background: #F2F2F2;   /* visual-view page bg, RESEARCH section 2 */
	overflow-x: clip;      /* not `hidden` — `hidden` on an ancestor kills position:sticky */
}

.tbl-csi[data-cs-view="reading"] {
	background: var(--Neutral-10-Base); /* #efefef — exact token match, reading-view page bg */
	transition: var(--button-easing);
}

/* --- Hero -------------------------------------------------------------- */

/* The only true full-bleed element (RESEARCH §6.5). Every other media band
   below carries the proportional inset instead. */
.tbl-csi__hero {
	position: relative;
	background: var(--csi-hero-bg, #dc2737);
	padding: 9.875% 4.0625% 20px 4.6875%;   /* 158 65 20 75 — the 20px bottom IS Ben's gap; the band is content-driven, no aspect-ratio */
	display: flex;
	flex-direction: column;
	align-items: center;
}

.tbl-csi__hero > [data-fold-marker] {
	position: absolute;
	bottom: 0;
	left: 0;
	width: 1px;
	height: 1px;
	pointer-events: none;
}

.tbl-csi__hero-inner {
	width: 62.8125%;   /* plan 13 caps this at 1005px, capped again by the viewport, below 1024px */
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 0;
	text-align: center;
}

.tbl-csi__hero-inner > * {
	margin: 0;
}

.tbl-csi__hero-title {
	margin-top: 11px;
}

.tbl-csi__focus {
	margin-top: 54px;
}

.tbl-csi__hero-eyebrow {
	/* Pastel #27 (Chris) / #47 — per-case-study accent, set inline on
	   .tbl-csi__hero by interior-chrome.php from the new Secondary Colour
	   field. The fallback is the literal this rule used to hardcode
	   (--Neutral-100 / #0f0f0f), so an unset field is a no-op. */
	background: var(--csi-hero-accent, var(--Neutral-100));
	/* Pastel #125 (Chris) — authorable alongside the fill, from the new Secondary
	   Text Colour field. The fallback is the literal this rule used to hardcode,
	   so an unset field is a no-op. Needed because the fill is authorable and a
	   light accent leaves white type unreadable (Simpliphy's is #c4f34e). */
	color: var(--csi-hero-accent-ink, var(--White));
	height: 27px;
	display: inline-flex;
	align-items: center;
	padding: 4px 15px;
	border-radius: var(--button-border-radius);
	font-size: 10px;
	line-height: 17.78px;
	letter-spacing: 2px;
	text-transform: uppercase;
	font-weight: 300; /* deliberate — 405 is not a registered fonts.css weight and would silently fall back */
	margin: 0;
	white-space: nowrap;
}

.tbl-csi__hero-title {
	font-size: var(--h1-font-size);
	line-height: var(--h1-line-height);
	letter-spacing: -1px;
	color: var(--csi-hero-fg, #37011b);
	max-width: 1005px;
}

.tbl-csi__focus {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 4px;
}

.tbl-csi__focus-label {
	font-size: 12px;
	line-height: 16px;
	letter-spacing: 0.6px;
	color: var(--White);
	text-transform: uppercase;
}

.tbl-csi__focus-value {
	font-size: 14px;
	line-height: 16px;
	letter-spacing: 0.84px;
	font-weight: 600;
	color: var(--White);
	text-transform: uppercase;
}

/* In flow (260805-kqg) — margin-top:20px is exact where a percentage top
   could not be, since the gap depends on the content-driven hero-inner
   height. */
.tbl-csi__hero-collage {
	flex: 0 0 auto;
	align-self: stretch;
	width: calc(100% + 1px);   /* 1461 at 1600 — the frame's collage is 1px wider than the padded box */
	max-width: 1800px;
	margin: 20px auto 0;       /* auto absorbs only positive free space: left-anchored at 1600, centred once max-width binds */
	aspect-ratio: 1461 / 830;
	border-radius: var(--csi-media-radius);
	overflow: hidden;          /* clips the img to the radius; not an ancestor of the sticky bar */
}

.tbl-csi__hero-collage img {
	width: 100%;
	height: 100%;
	object-fit: cover;
	display: block;
}

/* --- Hero load animation ------------------------------------------------ */

@keyframes tbl-csi-hero-rise {
	from { opacity: 0; transform: translateY(24px); }
	to   { opacity: 1; transform: none; }
}

@keyframes tbl-csi-hero-unblur {
	from { opacity: 0; filter: blur(18px); }
	to   { opacity: 1; filter: blur(0); }
}

.tbl-csi__hero-eyebrow,
.tbl-csi__hero-title,
.tbl-csi__focus {
	animation: tbl-csi-hero-rise 700ms cubic-bezier(0.22, 1, 0.36, 1) both;
}

.tbl-csi__hero-title { animation-delay: 90ms; }
.tbl-csi__focus      { animation-delay: 180ms; }

.tbl-csi__hero-collage {
	animation: tbl-csi-hero-unblur 900ms cubic-bezier(0.22, 1, 0.36, 1) 260ms both;
}

.fl-builder-edit .tbl-csi__hero-eyebrow,
.fl-builder-edit .tbl-csi__hero-title,
.fl-builder-edit .tbl-csi__focus,
.fl-builder-edit .tbl-csi__hero-collage {
	animation: none;
}

/* --- View bar + toggle -------------------------------------------------- */

/* Rests 20px above the collage's bottom edge (Ben, round 5); sticky pulls
   it to the viewport bottom from first paint (260805-kqg). `bottom: 0px`
   is deliberate and load-bearing (260805-mzj, commit 503aa69) — it pins
   the SCROLLED position; never change it to shift the resting gap. No
   `left` — on a sticky box every inset is a constraint, not an offset;
   horizontal placement now comes from `width` + `max-width` + auto
   margins, matching the collage box exactly (260805-lvw). */
.tbl-csi__viewbar {
	position: sticky;
	/* Pastel #44 — 30px clear of the viewport floor while pinned. `bottom`
	   is the PINNED offset only; the resting position is still set by the
	   margin pair below, so the hero is untouched. The scrim's own bottom
	   inset is widened to -50px to match (see ::before), otherwise the
	   gradient's hard 0.58 edge would land 10px above the floor, over the
	   collage, every time the bar was pinned. */
	bottom: 30px;
	flex: 0 0 auto;
	align-self: stretch;
	width: 100%;
	max-width: 1500px;      /* agrees with .tbl-csi__hero-collage at every width */
	margin: calc(-1 * var(--csi-viewbar-h) - 20px) auto 20px;
	padding: 0 30px 36px;
	height: var(--csi-viewbar-h);
	display: grid;
	grid-template-columns: 1fr auto 1fr;   /* equal flanks — a true centre regardless of SCROLL / See-all widths */
	align-items: center;
	z-index: 2;
}

/* CSI-18 — the negative margin-top above cancels the bar's own height so it
   overlays the collage. On a case study with no featured image the collage
   is never rendered, so that same margin pulls the bar 53px up onto
   .tbl-csi__focus and prints text over text (Spero Financial, Simpliphy, at
   every width). With no collage there is nothing to overlay: drop the pull
   and the sticky lift. */
.tbl-csi__hero:not(:has(.tbl-csi__hero-collage)) .tbl-csi__viewbar {
	position: static;
	margin-top: 0;
}

/* TT-01/04 — the AA scrim behind the view bar is GONE (Pastel #94, Ben:
   "remove the gradient shadow altogether").

   What it was doing, so nobody re-derives it from scratch: the bar's two white
   labels sit over the hero collage, and the scrim was a bottom-up black ramp at
   alpha 0.58 — the measured floor for AA 4.5:1 white-on-anything. Without it the
   labels are white directly on the photograph, so contrast now depends on which
   collage a case study ships. If a light image turns up under the bar, the fix is
   the label colour or a per-post band, not this gradient coming back.

   Removed with it: the Pastel #28 eased stop list, the #46 bottom-corner radius
   match, and the <768 `bottom: 0` override — all of them existed only to make
   this scrim behave. */

.tbl-csi__viewbar-scroll {
	font-size: var(--h6-font-size); /* 1rem = 16px */
	line-height: 30.91px;
	text-transform: uppercase;
	color: var(--White);
	width: 64px;
	grid-column: 1;
	justify-self: start;
}

.tbl-csi__viewbar-all {
	grid-column: 3;
	justify-self: end;
	font-size: 13.86px;
	line-height: 29px;
	font-weight: 500;
	color: var(--link-color-on-dark);
	text-decoration-line: underline;
	text-decoration-thickness: 0.92px;
	text-underline-offset: 5.67px;
	text-decoration-color: var(--link-color-on-dark) !important;
	transition: var(--button-easing);
}

/* theme.css carries a sitewide unlayered `a{text-decoration:...}` rule with
   !important — same-property !important, matched at the same unlayered tier
   and beating it on source order, is required here. */
.tbl-csi__viewbar-all:hover,
.tbl-csi__viewbar-all:active,
.tbl-csi__viewbar-all:focus-visible {
	color: var(--link-hover-color-on-dark);
	text-decoration-color: var(--link-hover-color-on-dark) !important;
}

/* TOGGLE (D-05) — Chris's dimensions, Motto's mechanism. */

.tbl-csi__toggle {
	position: relative;
	display: inline-flex;
	align-items: center;
	grid-column: 2;
	justify-self: center;
	background: var(--White);
	border-radius: 42.02px;
	padding: 2px;            /* uniform inset — the white pill shows 2px around the black mask at BOTH positions */
	isolation: isolate;
}

.tbl-csi__toggle-seg {
	position: relative;
	z-index: 2;
	appearance: none;
	border: 0;
	background: transparent;
	height: 47px;
	border-radius: 38.845px;
	padding: 12.923px 30.496px 14.331px;
	font-size: 14.509px;
	line-height: 19.991px;
	color: var(--csi-toggle-off);
	white-space: nowrap;
	cursor: pointer;
	transition: var(--button-easing);
}

/* Real, visible focus ring (D-11 — the thing Motto ships with no visible
   substitute at all). The extra box-shadow keeps the ring visible when a
   segment sits over the black mask. */
.tbl-csi__toggle-seg:focus-visible {
	z-index: 4;
	outline: 2px solid var(--Black);
	outline-offset: 3px;
	box-shadow: 0 0 0 2px var(--White);
}

.tbl-csi__toggle-seg[aria-checked="true"]:focus-visible {
	color: var(--White);
}

/* The mask is a full-width #0f0f0f duplicate of the WHOLE pill, holding
   white copies of BOTH labels (RESEARCH §4.4). Active/inactive colour is
   achieved ENTIRELY by this mask sliding over — the real segments above
   never change `color`, which is what gives the hard-edged swap. Raised
   above the segment labels (CSI-16) so the active label paints white;
   `pointer-events: none` keeps the toggle clickable. */
.tbl-csi__toggle-mask {
	position: absolute;
	inset: 0;
	z-index: 3;
	display: inline-flex;
	align-items: center;
	padding: 2px;
	background: var(--Neutral-100); /* #0f0f0f — exact token match */
	color: var(--White);
	border-radius: 42.02px;
	clip-path: inset(2px calc(2px + var(--csi-mask-right, 0px)) 2px calc(2px + var(--csi-mask-left, 0px)) round 42.02px);
	transition: clip-path 280ms linear, var(--button-easing);
	pointer-events: none;
}

.tbl-csi__toggle-mask-seg {
	height: 47px;
	padding: 12.923px 30.496px 14.331px;
	font-size: 14.509px;
	line-height: 19.991px;
	white-space: nowrap;
}

/* No-JS / initial-paint fallback: with --csi-mask-left/-right unset (both
   default to 0px above), the mask would cover the FULL pill. Key the
   correct resting position off data-cs-view so visual-view-first (D-07)
   is correct before plan 11's JS ever measures a live segment width.
   Plan 11 overwrites both custom properties on load and on every resize;
   these two rules are the static shipped behaviour under the media query
   below — the same no-scripting guard 05-07 used. */
@media (scripting: none) {
	.tbl-csi[data-cs-view="visual"] .tbl-csi__toggle-mask {
		--csi-mask-left: 0px;
		--csi-mask-right: 144.99px;
	}

	.tbl-csi[data-cs-view="reading"] .tbl-csi__toggle-mask {
		--csi-mask-left: 132.99px;
		--csi-mask-right: 0px;
	}
}

/* --- Next-case tail ---------------------------------------------------
   The bar and the teaser are one screen. The wheel gate arms at max
   scroll and scrubs `--csi-next-progress` on the rule inside the bar, so
   the bar has to be ON SCREEN at max scroll — with the two as loose
   siblings the rule sat ~1,380px above the viewport at 1600x900 and the
   visitor watched a blank panel fill nothing. Exactly one viewport tall,
   teaser takes whatever the bar leaves. */
/* One screen, at every width. The gate arms at max scroll, so whatever
   the last viewport holds IS the tail — bar, rule, counter and panel have
   to be in it together.

   A pinned bar over Chris's full 1224 panel was tried and abandoned: it
   only works while the panel is much taller than the viewport, which is a
   function of WIDTH x HEIGHT, not a breakpoint. At 1600x900 it was clean;
   at 1440x900 the panel is 1102 and the pinned bar's band sat straight
   over the title. This construction has no such window — it holds at
   1600, 1440, 1024 and 390 alike.

   The deviation from the frame, stated plainly: Chris's panel is
   1600x1224 with the collage taking 830 of it, and here the panel takes
   what the bar leaves, so the collage band is shorter. Order, horizontal
   geometry and type are his. */
.tbl-csi__tail {
	position: relative;
	display: flex;
	flex-direction: column;
	box-sizing: border-box;
	height: 100vh;
	height: 100svh;
	/* The tail's top lands at the viewport top at max scroll, which is
	   where the sticky header sits. Border-box, so the clearance comes out
	   of the tail's own screen and it stays exactly one. */
	padding-top: var(--header-height, 80px);
	overflow: clip;
}

/* --- Next-case bar --------------------------------------------------- */

.tbl-csi__nextbar {
	flex: 0 0 auto;
	margin-left: var(--csi-nextbar-x);
	width: var(--csi-nextbar-w);
	min-height: 101px;
	display: flex;
	flex-direction: column;
	justify-content: flex-end;
	gap: 55px;   /* 1 (rule) + 55 = row at 56 */
}

/* Bed rgb(207,213,215) and fill rgb(27,27,28) are Motto's wheel-gate
   progress affordance, merged onto Chris's plain 1px rule per 06-05's
   own flagged discretionary call under D-03 — surfaced again here for
   the plan 15 design review. */
.tbl-csi__nextbar-rule {
	position: relative;
	/* 3px, not the frame's 1px — Chris's hairline read as decoration rather
	   than as the thing the visitor is filling (Pastel #49). */
	height: 3px;
	background: rgb(207, 213, 215);
}

.tbl-csi__nextbar-fill {
	position: absolute;
	inset: 0;
	background: rgb(27, 27, 28);
	transform: scaleX(var(--csi-next-progress, 0));
	transform-origin: 0 50%;
}

/* CSI-26 / TT-01/05 — the label and the counter met edge to edge at 390
   (measured 311.40 for both, zero gap): the row had no `gap` and at that
   width `space-between` has no free space left to distribute once the
   label wraps to two lines. The gap is unscoped on purpose — above ~768
   the row has free space and a `gap` is inert, so a media query would add
   a breakpoint without adding behaviour. */
.tbl-csi__nextbar-row {
	display: flex;
	justify-content: space-between;
	align-items: baseline;
	gap: 16px;
}

.tbl-csi__nextbar-label {
	margin: 0;
	font-size: var(--body-font-size-larger);
	line-height: 45px;
	letter-spacing: 0.2px;
	max-width: 425px;
}

.tbl-csi__nextbar-count {
	margin: 0;
	font-size: var(--body-font-size-larger);
	line-height: var(--body-line-height);
	letter-spacing: 0.2px;
	min-width: 63px;
	text-align: right;
	/* `min-width` is a floor, not a width: the row's 16px gap takes its
	   slack out of the counter first at 390, which squeezed "04 / 04" onto
	   two lines. The string is one unit — never wrap it. */
	white-space: nowrap;
}

/* --- Teaser ------------------------------------------------------------ */

/* Pastel #49 — the panel climbs as the gate fills, so how close the banner is
   to the top of the screen IS the progress readout. --csi-tail-lift is written
   by the JS (the bar's height less the rule, so the travel stops just under the
   affordance rather than covering it); --csi-next-progress is the same variable
   the rule's fill reads, now published on the tail so both can see it.
   The negative bottom margin hands the flex algorithm that much extra free
   space, which this item's flex-grow absorbs — so the panel is already
   `--csi-tail-lift` taller than its slot and clipped by the tail. Sliding up
   therefore reveals panel, never a gap. `translate` rather than `transform`:
   nothing else here writes a matrix, but keeping the two apart is the house
   rule and it composes if that changes. */

.tbl-csi__teaser {
	margin-bottom: calc(-1 * var(--csi-tail-lift, 0px));
	translate: 0 calc(var(--csi-next-progress, 0) * -1 * var(--csi-tail-lift, 0px));
	position: relative;
	flex: 1 1 auto;
	min-height: 0;
	aspect-ratio: 1600 / 1224;
	background: var(--csi-teaser-bg, #000939);
	overflow: hidden;
}

.tbl-csi__teaser-link {
	position: absolute;
	inset: 0;
	display: block;
}

/* The panel is one big anchor. Its copy sits in flow at <= 767 and the
   theme's link underline propagated straight across a 124px headline —
   Chris draws none, at any width. */
.tbl-csi .tbl-csi__teaser-link,
.tbl-csi .tbl-csi__teaser-link:hover,
.tbl-csi .tbl-csi__teaser-link:focus-visible {
	text-decoration: none;
}

.tbl-csi__teaser-link:focus-visible {
	outline: 2px solid var(--White);
	outline-offset: -4px;
}

.tbl-csi__teaser-badge {
	position: absolute;
	top: 10.7026%;   /* 131 / 1224 */
	left: 50%;
	transform: translateX(-50%);
	/* Pastel #134-#137 — the pill now carries the next case study's NAME,
	   so a fixed 140px would clip "Ventura County Coast". Width is the
	   copy plus the hero eyebrow's own 15px side padding. */
	width: fit-content;
	min-width: 140px;
	height: 22px;
	padding: 0 15px;
	display: flex;
	align-items: center;
	justify-content: center;
	white-space: nowrap;
	background: var(--csi-teaser-accent, var(--csi-badge-bg));
	border-radius: var(--button-border-radius);
	color: var(--csi-teaser-accent-ink, var(--White));
	font-size: 10px;
	line-height: 17.78px;
	letter-spacing: 2px;
	text-transform: uppercase;
	font-weight: 300;
}

.tbl-csi__teaser-title {
	position: absolute;
	top: 13.5621%;   /* 166 / 1224 */
	left: 50%;
	transform: translateX(-50%);
	width: 54.625%;   /* 874 / 1600 — Chris's teaser measure, wider than
	                     the reading column the base var carries */
	text-align: center;
	font-size: var(--h1-font-size);
	line-height: var(--h1-line-height);
	letter-spacing: -1px;
	color: var(--White);
}

/* Chris's teaser focus is the hero's construction verbatim — a PROJECT
   FOCUS label over the category, 32px for the pair (2 x 16, no gap; the
   hero's own 4px gap is not repeated here). The label was missing
   entirely: the teaser printed the bare category. */
.tbl-csi__teaser-focus {
	position: absolute;
	top: 27.7778%;   /* 340 / 1224 */
	left: 50%;
	transform: translateX(-50%);
	display: flex;
	flex-direction: column;
	align-items: center;
	text-align: center;
	color: var(--White);
}

.tbl-csi__teaser-focus-label {
	font-size: 12px;
	line-height: 16px;
	letter-spacing: 0.6px;
	text-transform: uppercase;
}

.tbl-csi__teaser-focus-value {
	font-size: 14px;
	line-height: 16px;
	letter-spacing: 0.84px;
	font-weight: 600;
	text-transform: uppercase;
}

.tbl-csi__teaser-collage {
	position: absolute;
	top: 32.1895%;   /* 394 / 1224 */
	left: var(--csi-hero-x);
	width: var(--csi-hero-cw);
	aspect-ratio: 1461 / 830;
	margin: 0;
	/* Pastel #52 — was --Corner-Radius-None. Same construction as the hero
	   collage (research-04), so it takes the same radius, and needs the same
	   `overflow: hidden` to actually clip the <img> to it. The mobile block
	   below still zeroes both: below 768 the art is full-bleed. */
	border-radius: var(--csi-media-radius);
	overflow: hidden;
}

.tbl-csi__teaser-collage img {
	width: 100%;
	height: 100%;
	object-fit: cover;
	/* Pastel #148 (Chris) — the teaser box is far wider than the source ratio
	   (1461/830 against Spero's 2560x1455), so `cover` was throwing away ~19%
	   off the top and the same off the bottom, and heads landed cropped at the
	   crown. Anchoring the top keeps the subject's headroom and holds still
	   while the box grows on scroll; the discarded band moves to the bottom,
	   which on every published study is background. */
	object-position: 50% 0;
	display: block;
}

/* --- Teaser panel at >= 768 ---------------------------------------------
   The panel is now whatever the bar leaves rather than a fixed 1224, and
   every child was absolutely positioned at a PERCENTAGE `top` resolved
   against that box — compressing it drove the title through the focus
   line, the same failure ticket 05 fixed at 390, arriving at desktop for
   the same reason. Same fix: copy in flow, collage as the flex item that
   takes the remainder. Chris's order and horizontal geometry are
   untouched; the px gaps below are his own (131 badge, 166 title, 340
   focus, 394 collage). */
@media (min-width: 768px) {
	.tbl-csi__teaser {
		display: flex;
		flex-direction: column;
	}

	/* Chris's 131/1224 is 10.7% of the panel's HEIGHT, but a percentage
	   padding resolves against WIDTH — at 1280x720 that read as 137px of
	   a 482px panel and squeezed the collage to a 25px sliver. 7.5svh
	   tracks his ratio against the panel the tail actually has (the panel
	   is ~100svh minus the bar), and holds from 720 to 1024 tall. */
	.tbl-csi__teaser-link {
		position: relative;
		inset: auto;
		flex: 0 0 auto;
		padding-top: max(24px, 7.5svh);
	}

	.tbl-csi__teaser-badge,
	.tbl-csi__teaser-title,
	.tbl-csi__teaser-focus {
		position: relative;
		top: auto;
		left: auto;
		transform: none;
	}

	.tbl-csi__teaser-badge {
		margin: 0 auto;
	}

	.tbl-csi__teaser-title {
		display: block;
		margin: 13px auto 0;   /* 166 - (131 + 22) */
	}

	.tbl-csi__teaser-focus {
		margin: 24px auto 0;
	}

	/* `order`, never a source-order change: interior-chrome.php emits the
	   collage first so the anchor stays on top and clickable. */
	.tbl-csi__teaser-collage {
		position: relative;
		order: 2;
		top: auto;
		left: auto;
		flex: 1 1 auto;
		min-height: 0;
		aspect-ratio: auto;
		margin: 54px 0 0 var(--csi-hero-x);   /* 394 - 340 */
	}
}

/* --- Wheel-gate sentinel ------------------------------------------------ */

/* Exactly one viewport tall, last in the document, so its top sits at
   max scroll and it is fully visible precisely when scrolling can go no
   further. The small-viewport-unit fallback below handles mobile browser
   chrome — the wheel gate is desktop-first but this must not overshoot
   on a phone. Invisible structure only: no background, no border, no
   content. */
.tbl-csi__sentinel {
	height: 0;
}

/* --- View panels (plan 10, panel dedup round 10) -------------------------
   Both panels stay mounted (D-04); only the active one is rendered, in
   flow, and reachable by keyboard/screen reader. The fade is a bare
   opacity crossfade matching Motto's measured text transition.

   The inactive panel is `display: none`, NOT `visibility: hidden`. Under
   visibility the whole heading outline and every line of body copy were
   rendered twice — the page carried two H1-through-H3 trees and two
   copies of the narrative, which is the single biggest SEO/GEO problem on
   this template. `display: none` collapses it to one, and also drops the
   inactive panel out of the accessibility tree outright rather than
   relying on `inert` alone.

   `display` is a discrete property, so the crossfade survives only via
   `transition-behavior: allow-discrete` (flips to `block` at the START of
   the incoming transition and to `none` at the END of the outgoing one)
   plus `@starting-style`, which gives the incoming panel an opacity-0
   first style to animate FROM — without it a panel coming out of
   `display: none` has no previous computed style and pops in. Both are
   baseline in every browser this project targets; where they are not
   supported the swap is instant, which is a fade regression only, never
   a content one.

   This IS the animation (Ben, 2026-08-26 — "I want the animation to start
   immediately, but I don't want it to be an instant change"). It is pure CSS,
   so it begins on the frame the attribute flips: there is no measurement pass
   in front of it, which is exactly what the GSAP Flip morph that used to sit
   over the top of it could not offer. See the note in tbl-cs-interior.js.

   Plan 11 additionally sets `inert` on the inactive panel — still wanted,
   because it also covers the 280ms during which the OUTGOING panel is
   still `display: block` and fading. */
.tbl-csi__views {
	position: relative;
}

.tbl-csi__view {
	display: none;
	opacity: 0;
	transition: opacity 280ms linear, display 280ms allow-discrete;
}

.tbl-csi[data-cs-view="visual"] .tbl-csi__view--visual,
.tbl-csi[data-cs-view="reading"] .tbl-csi__view--reading {
	display: block;
	opacity: 1;
}

@starting-style {
	.tbl-csi[data-cs-view="visual"] .tbl-csi__view--visual,
	.tbl-csi[data-cs-view="reading"] .tbl-csi__view--reading {
		opacity: 0;
	}
}

/* Still required even though the inactive panel ends up `display: none`:
   `allow-discrete` keeps the OUTGOING panel rendered for the full 280ms of
   its fade, and two panels in flow at once is exactly what T-06-10-02
   forbids — the wheel-gate sentinel's max-scroll never settles while both the
   ~9,500px and ~16,700px panels contribute height. This rule applies the
   instant the attribute flips, so the outgoing panel leaves flow immediately
   and only its paint lingers. It is what makes the crossfade above safe. */
.tbl-csi[data-cs-view="visual"] .tbl-csi__view--reading,
.tbl-csi[data-cs-view="reading"] .tbl-csi__view--visual {
	position: absolute;
	inset: 0 0 auto 0;
	pointer-events: none;
}

/* No-JS fallback: one complete layout, statically. Targets the panel by
   its data attribute rather than its BEM modifier class, so this rule
   cannot collide with an exact-match audit written against the class
   name elsewhere in the file (same pattern-artifact class recorded
   throughout this phase — see the plan 10 summary). */
@media (scripting: none) {
	/* `display: block` is LOAD-BEARING here since the dedup round made the
	   base state `display: none` — without it a no-JS visitor gets a blank
	   page, not a degraded one. The panel is never toggled in this mode, so
	   the transition and @starting-style above are inert by construction. */
	.tbl-csi__view--visual {
		display: block;
		position: static;
		opacity: 1;
		pointer-events: auto;
	}

	/* Attribute-prefixed for the same reason as the 768 block: at (0,2,0)
	   this lost to the (0,3,0) active-panel rule on `display`, so a root
	   attribute of "reading" would have shown the reading panel here too.
	   Unreachable in practice — the template hardcodes data-cs-view="visual"
	   and only JS ever changes it, so with scripting off it cannot be
	   "reading" — but a defensive rule that silently stops defending is
	   worse than no rule. Verified by injecting this block's own declarations
	   live with the attribute forced to "reading". */
	.tbl-csi[data-cs-view] [data-cs-view-panel="reading"] {
		display: none;
	}
}

/* --- Visual view: two-column text sections -------------------------------
   RESEARCH §2.3 records raw gaps of 390/360/378/380 across the four
   sections — drift, not design. Normalised to the grid tokens plan 09
   declared: left rail at --csi-eyebrow-x, content column at --csi-col-x
   / --csi-col-w. Those three tokens reproduce every section within 7px
   and land the right edge at 1459, matching RESEARCH §1.3. */
.tbl-csi__view--visual .tbl-csi__section--overview,
.tbl-csi__view--visual .tbl-csi__section--numbered {
	display: grid;
	grid-template-columns: var(--csi-col-x) var(--csi-col-w);
	padding-left: 0;
}

.tbl-csi__view--visual .tbl-csi__section-rail {
	padding-left: var(--csi-rail-label-x);
}

.tbl-csi__view--visual .tbl-csi__section-body {
	width: 100%;
}

.tbl-csi__view--visual .tbl-csi__section--overview .tbl-csi__section-body {
	display: flex;
	flex-direction: column;
	gap: 36px;
}

.tbl-csi__view--visual .tbl-csi__section--numbered .tbl-csi__section-body {
	display: flex;
	flex-direction: column;
	gap: 40px;
}

.tbl-csi__view--visual .tbl-csi__section--overview .tbl-csi__section-heading {
	max-width: 664px;
}

/* Section label: the rail padding fix (CSI-11) moves the whole rail to
   x=135; this resets the label itself off the full-track block width it
   inherited so the LEFT EDGE, not a stretched/centred box, lands there. */
.tbl-csi__section-label {
	display: inline-block;
	margin: 0;
	text-align: left;
	font-size: 20px;      /* FLAG: unbound — no token between --h6 16 and --h5 24 */
	line-height: 30px;    /* frame label box is 154 x 30 in BOTH 10362:4357 and 10597:4819 */
}

/* Chips abut with zero literal whitespace between the two spans (plan 06
   emitted them that way); this rule only confirms the box model carries
   no gap of its own. */
.tbl-csi__chips {
	display: inline-flex;
	gap: 0;
	margin: 0;
}

/* Numbered-section chips (CSI-12) — number pill square, label pill
   abutting, both bg #e8e8e8, radius 137.879, type 15/25 ls 0.15px. */
.tbl-csi__chip {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	box-sizing: border-box;
	height: 39.985px;
	background: var(--csi-chip-bg);
	border-radius: 137.879px;
	font-size: 15px;
	line-height: 25px;
	letter-spacing: 0.15px;
}

.tbl-csi__chip--num {
	width: 39.985px;
}

.tbl-csi__chip--label {
	padding: 0 33.091px;
}

/* Field Notes (CSI-06) — rows 95 tall, n+1 hairlines at 1px rgba(0,0,0,.25). */
/* Frame 10362:4357 sets the label (FIELD NOTES); the text node is
   "(Field Notes)". Cased here rather than in interior-narrative.php to keep
   the source strings readable. NOTE: this does NOT preserve a sentence-case
   accessible name — QA round 3 confirmed Chromium applies text-transform when
   it computes an accessible name from rendered text, so the AX tree reports
   "(FIELD NOTES)". Accepted (Ben, round 2): screen readers pronounce it as
   words, not letters. Shared class, so this covers both views. */
.tbl-csi__notes-label {
	margin: 0 0 24px;   /* Ben, round 7 */
	text-transform: uppercase;
}

.tbl-csi__notes-list {
	margin: 0;
}

/* Pastel #109 (Ben) — key never wraps, value right-aligns and wraps, both
   columns top-aligned. The 95px row is now a MIN, opened by padding rather
   than by centring in a fixed box, so a wrapped value grows the row. */
.tbl-csi__notes-row {
	box-sizing: border-box;
	display: flex;
	justify-content: space-between;
	/* Pastel #133 (Chris) — type centred between the hairlines. The
	   padding stays so a wrapped value still grows the row past 95. */
	align-items: center;
	gap: 24px;
	min-height: 95px;
	padding-block: 22px;
	border-bottom: 1px solid var(--csi-rule);
}

.tbl-csi__notes-row:first-child {
	border-top: 1px solid var(--csi-rule);
}

.tbl-csi__notes-key,
.tbl-csi__notes-value {
	margin: 0;
}

.tbl-csi__notes-key {
	flex: 0 0 auto;
	white-space: nowrap;
}

.tbl-csi__notes-value {
	flex: 1 1 auto;
	min-width: 0;
	text-align: right;
}

/* What We Did (CSI-07) — 36.333px pitch, 21.333px indent, 6.39px square
   bullet (Chris drew a square, not the browser's default disc). */
/* Item 5 — label sat flush on the list (measured 0). Frame 10362:4521 wants
   21.29px. The body -> block gap was already 40 and is left alone. */
.tbl-csi__did-label {
	margin: 0 0 21.29px;
	text-transform: uppercase;   /* frame sets (WHAT WE DID) — see notes-label */
}

.tbl-csi__did-list {
	list-style: none;
	margin: 0;
	padding: 0;
}

.tbl-csi__did-item {
	position: relative;
	margin: 0;
	padding-left: 21.333px;
	line-height: 36.333px;
}

.tbl-csi__did-item::before {
	content: "";
	position: absolute;
	left: 0;
	top: 11.19px;
	width: 6.39px;
	height: 6.39px;
	background: currentColor;
}

/* CSI-21 — the attribution is a <figure>, and reset.css (LAYERED, so its
   normal declarations lose to this unlayered file) sets
   `figure { inline-size: fit-content; margin-inline: auto }`. Nothing here
   overrode it, so the figure computed 0 x 1727.5 and the caption printed one
   character per line in BOTH views.

   Frames 10362:4550 and 10597:5241 are the same component: a 74.66-tall row,
   avatar 73.59 x 74.66 on the left, figcaption 27.73px to its right and
   vertically centred ((74.66 - 23) / 2 = 25.83 visual, (74.66 - 45) / 2 =
   14.83 reading — both exact). Gaps to the neighbours are owned by the
   blockquote's margin, so the figure's own block margin is zeroed. */
.tbl-csi__attrib {
	inline-size: auto;
	margin: 0;
	display: flex;
	align-items: center;
	gap: 27.73px;
}

.tbl-csi__attrib-avatar {
	flex: 0 0 auto;
	/* Pastel #91 — square the box and clip it so a non-square JPG still
	   renders as a perfect circle rather than an ellipse. Was 73.59 x 74.66
	   (the frame's own off-square measurement); object-fit already covers,
	   the radius had no counterpart. */
	width: 74.66px;
	height: 74.66px;
	border-radius: 50%;
	overflow: hidden;
	object-fit: cover;
}

/* CSI-21b — name and role are adjacent inline spans. The frame renders them
   as ONE continuous comma-separated line ("Raj Sarkar, CMO, CloudBees…"),
   not a stack, so the separator is all that was missing; it is emitted in
   interior-narrative.php only when both parts are non-empty. */
.tbl-csi__attrib-text {
	margin: 0;
	/* The SECOND half of CSI-21, and the reason fixing the <figure> alone
	   only moved the collapse down a level: reset.css also sets
	   `figcaption { contain: inline-size }`, which makes the element's
	   inline size independent of its contents — so as a flex item its
	   content-based width resolves to 0 and the caption still printed one
	   character per line. Traced in the CSSOM, not guessed. */
	contain: none;
}

/* Testimonial, visual view: three full-width siblings at the frame's
   asymmetric x 220 / w 1185.05 (RESEARCH §2.3 flags this inset as
   eyeballed, not a grid multiple — kept literal, not tokenised). */
.tbl-csi__view--visual .tbl-csi__section--testimonial {
	margin-left: var(--csi-testi-x);
	width: var(--csi-testi-w);
}

.tbl-csi__view--visual .tbl-csi__quote-head {
	display: flex;
	justify-content: space-between;
	align-items: baseline;
}

/* Font-size was already correct. The 52px line-height set HERE never applied:
   theme.css carries a LAYERED `blockquote:not(.tiktok-embed) > p {
   line-height: inherit !important }`, and a layered !important outranks any
   unlayered declaration — so the <p> inherited the theme's unitless 1.3 off
   the blockquote and computed 54.6 (42 x 1.3). Same shape as the reading
   view's fix below: set the line-height on the BLOCKQUOTE and let the
   forced `inherit` carry it down. Frame blockquote 10362:4548 is 312 tall
   = 6 x 52 exactly, so 52 is the design value. */
.tbl-csi__view--visual .tbl-csi__quote .tbl-csi__quote-text {
	font-size: 42px;
}

/* Frame 10362:4545/4548/4550, measured frame-box to frame-box: head bottom
   12670 -> blockquote top 12743 (73), blockquote bottom 13055 ->
   attribution top 13092 (37). The blockquote is flush at x 220 with the
   text the full 1185.05 wide — no indent and no rule bar, unlike the
   reading view. The theme's decorative blockquote::before is a 1px x 312px
   #dcdcdc bar sitting in that indent; it has no counterpart in the frame. */
/* Pastel #98 (Chris) — the 312px min-height is dropped so the blockquote is
   sized by its own text, and the attribution sits a flat 120px below it. */
.tbl-csi__view--visual .tbl-csi__quote {
	margin: 73px 0 120px;
	margin-left: 0;
	padding-inline: 0;   /* theme.css also carries a 30px padding-right that held the text 30px short of the frame's 1185.05 */
	line-height: 52px;
}

.tbl-csi__view--visual .tbl-csi__quote::before {
	content: none;
}

.tbl-csi__view--visual .tbl-csi__attrib-text {
	max-width: 855px;
	color: #000;
	font-size: var(--body-font-size-larger);
}

/* --- Media bands and two-ups (visual view only) --------------------------
   --csi-band-inset serves as BOTH the side inset and the two-up gap —
   RESEARCH §6.2 records "two-up gap = band inset" as a measured Motto
   invariant, D-02 locks it, and one shared token makes the two
   structurally incapable of diverging. */
.tbl-csi__media {
	display: flex;
	flex-direction: column;
	gap: var(--csi-photo-gap);
}

.tbl-csi__band {
	margin-inline: var(--csi-band-inset);
}

.tbl-csi__twoup {
	margin-inline: var(--csi-band-inset);
	display: grid;
	grid-template-columns: 1fr 1fr;
	gap: var(--csi-photo-gap);
}

.tbl-csi__band .tbl-csi__tile {
	aspect-ratio: 1545 / 896;
}

.tbl-csi__twoup .tbl-csi__tile {
	aspect-ratio: 760 / 892;
}

.tbl-csi__tile {
	overflow: hidden;
}

/* The Field Notes website link had NO colour rule anywhere in this file, so it
   fell through to reset.css's placeholder `a { color: oklch(0.68 0.17 228) }`
   and rendered sky blue. Design-system tokens, not literals. */
.tbl-csi__notes-link {
	color: var(--link-color);
	text-decoration-color: var(--link-color);
	transition: var(--button-easing);
}

.tbl-csi__notes-link:hover,
.tbl-csi__notes-link:focus-visible {
	color: var(--link-hover-color);
	text-decoration-color: var(--link-hover-color);
}

.tbl-csi__asset {
	width: 100%;
	height: 100%;
	object-fit: cover;
	display: block;
}

/* Per-slot vertical gaps key off the slot's own data attribute, never
   nth-of-type — plan 04 drops empty slots so nth positions shift. The
   hero is the one true full-bleed element (RESEARCH §6.5); every band
   below carries the proportional inset instead. */
.tbl-csi__view--visual .tbl-csi__media[data-cs-media-slot="0"] {
	margin-top: calc(117px * var(--csi-gap-scale));
}

.tbl-csi__view--visual .tbl-csi__media[data-cs-media-slot="1"] {
	margin-top: calc(154px * var(--csi-gap-scale));
}

.tbl-csi__view--visual .tbl-csi__media[data-cs-media-slot="2"] {
	margin-top: calc(165px * var(--csi-gap-scale));
}

.tbl-csi__view--visual .tbl-csi__media[data-cs-media-slot="3"] {
	margin-top: calc(152px * var(--csi-gap-scale));
}

.tbl-csi__view--visual .tbl-csi__media[data-cs-media-slot="4"] {
	margin-top: calc(251.34px * var(--csi-gap-scale));
}

.tbl-csi__view--visual .tbl-csi__media[data-cs-media-slot="5"] {
	margin-top: calc(25px * var(--csi-gap-scale));
}

/* The other half of the same rhythm. The media rules above carry the gap
   BEFORE each band; these carry the gap BEFORE each text section, which
   was never implemented — all five read 0 live against the frame's
   92/147/147/137/190 (260806-hby FRAME-DIFF, frame 10362:4357). Values
   measured off the frame and verified live before shipping; the three
   numbered gaps genuinely differ, so this is per-slot like the media
   side, not one token. --csi-gap-scale is load-bearing: the 1024 block
   sets it to 0.66 and every media gap already rides it. */
.tbl-csi__view--visual .tbl-csi__section--overview {
	margin-top: calc(92px * var(--csi-gap-scale));
}

.tbl-csi__view--visual .tbl-csi__section--numbered {
	margin-top: calc(147px * var(--csi-gap-scale));
}

/* Default-plus-override, not three [data-cs-section] rules: the attribute
   carries the section NUMBER, so a case study with a 4th numbered section
   would match nothing and fall back to 0 — the exact bug being fixed. */
.tbl-csi__view--visual .tbl-csi__section--numbered[data-cs-section="03"] {
	margin-top: calc(137px * var(--csi-gap-scale));
}

.tbl-csi__view--visual .tbl-csi__section--testimonial {
	margin-top: calc(190px * var(--csi-gap-scale));
}

/* --- Reading view: grid + rail --------------------------------------------
   Two more grid tokens, shipped at Chris's Figma values (RESEARCH §1.3):
   rail right edge 560, content column x 672 -> a 112px gutter; column
   787 wide. D-02 separately enumerates Motto's proportions for the SAME
   two quantities (32vw rail, 43.8vw column, 9.167vw gap) — Task 3's
   checkpoint resolved this: Ben chose the Figma frame values over D-02's
   Motto proportions. D-02 remains authority only for widths Chris did
   NOT draw; these two he drew, so the frame wins. Values below are
   final, not provisional. */
.tbl-csi {
	--csi-rail-gap: 7%;        /* 112 / 1600 */
	--csi-col-w-abs: 49.1875%; /* 787 / 1600 — same as --csi-col-w */
	--csi-gap: 96px;
	--csi-gap-lg: 107.5px;
	--csi-gap-xl: 184.885px;   /* overview -> first numbered section only (frame 10597:4883 -> :5226) */
}

.tbl-csi__reading-grid {
	display: grid;
	grid-template-columns: var(--csi-rail-x) var(--csi-rail-w) var(--csi-rail-gap) var(--csi-col-w-abs);
	column-gap: 0;
	align-items: start;
}

/* Task 3 answer (a): Ben wants the TEXT to pin, not the rail — "once the
   text ends and the images are longer, then the text pins and the images
   keep scrolling."

   This shipped as `bottom: 0` on the premise that bottom-sticky engages
   once the box's bottom edge passes above the viewport bottom. That
   premise is inverted, and ticket 06 measured it in a bare document:
   bottom-sticky only ever shifts a box UPWARD — it pulls a box whose
   natural position is still below the fold up to the viewport bottom and
   releases it when the scroll catches up. This column sits at the START
   of its grid area (align-items: start), so there is no room above it and
   the shift is structurally 0 at every scroll position. `position`
   computed `sticky` the whole time, which is why every check passed.

   Bottom-pinning a box TALLER than the viewport is `top: 100vh - height`.
   The height is content-driven, so tbl-cs-interior.js measures it into
   --csi-col-h with a ResizeObserver — no scroll listener, and sticky
   still adds no document height, so the plan-12 wheel-gate sentinel's
   max-scroll assumption stays valid. min() clamps a column SHORTER than
   the viewport back to top: 0 rather than pinning it mid-screen; the
   unmeasured fallback resolves to the same 0, and is unreachable anyway
   because @media (scripting: none) never shows this panel. Narrow widths
   unset position entirely, further down. */
.tbl-csi__reading-col {
	grid-column: 4;
	width: 100%;
	position: sticky;
	top: min(0px, calc(100vh - var(--csi-col-h, 100vh)));
	bottom: auto;
}

/* Uniform 15px gap across all 14 tiles (RESEARCH §1.5) — one gap value,
   no per-tile override. Deferred rendering plus an intrinsic-size hint
   is the Claude's-Discretion call D-12 invites for a ~6,808px rail; the
   hint below is what keeps the scrollbar stable while tiles render,
   which the wheel-gate sentinel's max-scroll math depends on.
   No sticky, no max-height, no scroll container on the rail or its
   tiles — Task 3 resolved rail-length behaviour as text-pins-not-rail,
   implemented on .tbl-csi__reading-col above, not here. */
.tbl-csi__rail {
	grid-column: 2;
	display: flex;
	flex-direction: column;
	gap: 15px;
	margin: 0;
}

/* The per-item `--csi-aspect` is emitted by the shared item renderer on the
   INNER `.tbl-csi__tile` (interior-media.php:120), not on this wrapper, so a
   `var(--csi-aspect)` here never resolved and every one of the 14 tiles fell
   back to the landscape default — cropping the 515x604 portrait sources to
   roughly half their height under `object-fit: cover`. Custom properties
   inherit down, never up. The wrapper now takes its height from the tile;
   the tile consumes the property where it is actually defined. Frame
   10597:4819 has TWO ratios (515x297 landscape, 515x604.45 portrait). */
.tbl-csi__rail-tile {
	width: 100%;
	aspect-ratio: auto;
	overflow: hidden;
	content-visibility: auto;
	contain-intrinsic-size: auto 515px auto 300px;
	border-radius: var(--Corner-Radius-None);
}

.tbl-csi__rail-tile > .tbl-csi__tile {
	aspect-ratio: var(--csi-aspect, 1.7243);
}

/* --- Reading view: text sections ------------------------------------------
   Same markup as the visual view, different container (RESEARCH §3.2). */
.tbl-csi__view--reading .tbl-csi__section--overview,
.tbl-csi__view--reading .tbl-csi__section--numbered,
.tbl-csi__view--reading .tbl-csi__section--testimonial {
	display: flex;
	flex-direction: column;
	width: 100%;
}

/* Frame 10597:4883 — "Project Overview" label ends at y 30, Heading 3 starts
   at 66, so the label sits 36 above the body. It was measured flush (0). The
   36 matches the overview body's own internal gap, which is why the label
   reads as part of that stack rather than a floating rail. */
.tbl-csi__view--reading .tbl-csi__section--overview .tbl-csi__section-rail {
	padding-left: 0;
	margin-bottom: 36px;
}

.tbl-csi__view--reading .tbl-csi__section--overview .tbl-csi__section-body {
	display: flex;
	flex-direction: column;
	gap: 36px;
}

/* Frames 10597:5226 / :5228 / :5247 all agree: the 39.985-tall chip row ends
   at 39.985 and the body container starts at 69.985 — a 30px gap, not the
   69.98 that was here. The old value looks like the body's OFFSET (69.985)
   used as if it were the gap; measured live at 69.98 against the frame's 30. */
.tbl-csi__view--reading .tbl-csi__section--numbered .tbl-csi__section-rail {
	margin-bottom: 30px;
}

.tbl-csi__view--reading .tbl-csi__section--numbered .tbl-csi__section-body {
	display: flex;
	flex-direction: column;
	gap: 40px;
}

/* Section rhythm — two tokens, not twelve literals. The large gap lands
   after the CTA and after the Solution section (data-cs-section="02"),
   the two spots RESEARCH §1.4 records at 107.5 rather than the base 96. */
.tbl-csi__reading-col > * + * {
	margin-top: var(--csi-gap);
}

/* The overview boundary is its OWN value, not the large gap. Measured from
   the frame: overview 10597:4883 ends at 2283.115, Challenge 10597:5226
   starts at 2468 — 184.885, against the 96/107.5 everywhere else. Live was
   107.5, so this boundary was 77.39 short. A third token rather than a
   literal, for the same reason --csi-gap-lg is one: the 1024 block scales
   all three off --csi-gap so they cannot drift apart. */
.tbl-csi__reading-col .tbl-csi__section--overview + * {
	margin-top: var(--csi-gap-xl);
}

.tbl-csi__reading-col .tbl-csi__section--numbered[data-cs-section="02"] + * {
	margin-top: var(--csi-gap-lg);
}

/* Testimonial, reading view: blockquote indents, quote text caps, and
   the "Let's work together" link — emitted once in the head row by
   plan 06 — moves below the attribution via `order`, never a second
   copy and never `display:none`. `display: contents` on the head row
   promotes its two children into the section's own flex flow so a
   single flex container can reorder all four parts. Typography comes from
   .text-link on the anchor itself. */
.tbl-csi__quote-text {
	white-space: pre-line;
}

.tbl-csi__view--reading .tbl-csi__section--testimonial .tbl-csi__quote-head {
	display: contents;
}

.tbl-csi__view--reading .tbl-csi__section--testimonial .tbl-csi__quote-label {
	order: 1;
}

/* Frame 10597:5234, frame-box to frame-box: label bottom 18 -> blockquote
   top 58 (40), blockquote bottom 490 -> attribution top 530 (40),
   attribution bottom 604.66 -> link top 644.66 (40, already set below).
   This is a flex column, so these margins add rather than collapse — the
   figure's own margin is zeroed in the CSI-21 rule above. Was 50/50. */
.tbl-csi__view--reading .tbl-csi__section--testimonial .tbl-csi__quote {
	order: 2;
	margin: 40px 0;
	border-left: 2px solid #000;
	padding-left: 42.66px;
	padding-right: 0;
	line-height: 52px; /* theme sets blockquote>p{line-height:inherit!important} — set it here so the <p> inherits it, rather than fighting the !important on the <p> itself */
}

/* Kill the theme's own blockquote::before rule bar — a real border-left
   is used above instead, so there are never two rules stacked. */
.tbl-csi__view--reading .tbl-csi__section--testimonial .tbl-csi__quote::before {
	content: none;
}

.tbl-csi__view--reading .tbl-csi__section--testimonial .tbl-csi__quote-text {
	max-width: 690px;
	font-size: 42px;
}

.tbl-csi__view--reading .tbl-csi__section--testimonial .tbl-csi__attrib {
	order: 3;
}

/* Frame 10597:5244 — the narrower column wraps the same caption to two
   lines at 417 wide, against the visual view's single 855-wide line. */
.tbl-csi__view--reading .tbl-csi__section--testimonial .tbl-csi__attrib-text {
	max-width: 417px;
}

.tbl-csi__view--reading .tbl-csi__section--testimonial .tbl-csi__quote-link {
	order: 4;
	margin-top: 40px;
	align-self: flex-start;
}

/* --- Responsive: 1024 band (plan 13, Task 1) -----------------------------
   Chris drew only 1600px frames; RESEARCH §6.2 confirms Motto's own
   percentage-based tokens are linear from 1600 down to 768, so most of
   this page needs nothing here. Everything below is a MEASURED correction
   to an absolute-pixel value that becomes proportionally wrong at 1024 —
   not a redesign. Everything else (the whole grid, both view bodies, the
   hero band, every percentage token) was already correct at 1024 —
   recorded here so plan 15 does not re-litigate it. */
@media (max-width: 1024.98px) {
	.tbl-csi {
		/* Section rhythm: 96/107.5 is a fixed 96:107.5 ratio (1:1.119792) —
		   derive the large gap from the base gap so the two can never
		   drift apart, rather than two independent clamps. */
		--csi-gap: clamp(56px, 6vw, 96px);
		--csi-gap-lg: calc(var(--csi-gap) * 1.119792);
		/* 184.885 / 96 — same derive-don't-restate rule as --csi-gap-lg. */
		--csi-gap-xl: calc(var(--csi-gap) * 1.925885);
		/* Per-slot media gaps (117/154/165/152/251.34/25) share this one
		   scale factor — the six existing calc() uses above pick it up
		   with no further edits needed here. */
		--csi-gap-scale: 0.66;
		/* Reading rail: measured 330px rail / 504px prose column at 1024 —
		   below a comfortable ~60-75-character measure (estimated ~53
		   characters at Paragraph-Large's 20px, arithmetic only, no
		   browser available to this executor — flagged for 06-15 to
		   confirm live). Widen the column and narrow the gap, never the
		   rail — it carries the media and is the reason to be in reading
		   view at all. --csi-rail-w and --csi-rail-x are untouched. */
		--csi-rail-gap: 4%;
		--csi-col-w-abs: 52.1875%;
	}

	/* Hero inner block: the fixed 1005px width exceeds the padded
	   viewport below ~1120px. Identical to 62.8125% at 1600 and correct
	   below it. */
	.tbl-csi__hero-inner {
		width: min(1005px, 100%);
	}

}

/* --- Responsive: 768 breakpoint, D-01's mobile deletion (plan 13, Task 2)
   Locked, and a DELETION, not a reflow — Chris drew no mobile frame,
   Motto solves it by deletion (RESEARCH §6.4), and D-01 requires no
   affordance that a second view exists. Every value below is JUDGMENT,
   recorded in the plan 13 summary; there is no mobile frame for any of
   it. */
@media (max-width: 767.98px) {
	.tbl-csi__toggle { display: none; }

	/* Both of these carry the attribute prefix ON PURPOSE. The dedup round
	   moved the panel gate onto `display`, which is the same property this
	   block uses to delete the reading view — so a bare
	   `.tbl-csi__view--reading { display: none }` at (0,1,0) now LOSES to
	   the (0,3,0) `.tbl-csi[data-cs-view="reading"] .tbl-csi__view--reading`
	   active rule, and a visitor who switched to reading at 1600 and then
	   narrowed would get the reading panel on a phone. Matching that
	   specificity and relying on source order (the same trick the visual
	   rule below already used) restores D-01's deletion. A media query
	   adds no specificity of its own. */
	.tbl-csi[data-cs-view] .tbl-csi__view--reading {
		display: none;
	}

	/* Forces the visual panel rendered and static regardless of
	   data-cs-view (T-06-13-01) — without this, a visitor who enters
	   reading view at 1600 and then narrows the window sees a blank page,
	   since the root attribute is still "reading" and nothing would
	   otherwise override the base .tbl-csi__view display:none/opacity:0
	   rule for the visual panel in that state. */
	.tbl-csi[data-cs-view] .tbl-csi__view--visual {
		display: block;
		position: static;
		opacity: 1;
		pointer-events: auto;
	}

	/* Required fix carried from plan 10's summary: bottom-sticky on a
	   full-width single-column reading layout is wrong. The reading view
	   is fully display:none above (D-01 deletes rather than reflows it
	   below 768), so this unset is defensive rather than load-bearing —
	   made explicit per the plan 10 handoff note rather than left
	   ambiguous. */
	.tbl-csi__reading-col {
		position: static;
	}

	/* Two 366px portrait tiles side by side on a 390px phone is not a
	   design either frame supports. */
	.tbl-csi__twoup {
		grid-template-columns: 1fr;
	}

	.tbl-csi {
		--csi-photo-gap: 12px;
	}

	.tbl-csi__hero {
		aspect-ratio: auto;
		/* CSI-20 — the base 9.875% resolves to 38.51px at 390, which puts the
		   whole eyebrow (38.51 -> 65.51) behind the 80px sticky header, a
		   measured 41.49px overlap. The percentage is correct at desktop
		   widths and wrong at every mobile one, so floor it rather than
		   replace it: `max()` keeps the desktop behaviour intact above the
		   crossover (~1053px, outside this block anyway) and pins the floor
		   below it — originally 104px, the 80px header plus the same 24px
		   the collage uses for its top margin.
		   JUDGMENT: there is no mobile frame for this. */
		/* Pastel #150 (Chris) — +30px over the 104px floor (80px nav bar + 24px
		   breathing room) on mobile. The percentage arm is unchanged; below
		   ~1360px the max() has always resolved to the floor anyway. */
		padding-top: max(9.875%, 134px);
		padding-right: 12px;
		padding-bottom: 12px;
		padding-left: 12px;
	}

	.tbl-csi__hero-collage {
		position: relative;
		margin-top: 24px;
		aspect-ratio: 4 / 3;
		/* CSI-17 — the base `calc(100% + 1px)` reproduces the frame's collage
		   being 1px wider than its padded box at 1600. At 390 that extra
		   pixel eats the right inset (11px against the left's 12px), which
		   reads as a visible misalignment at phone scale. */
		width: 100%;
	}

	/* TT-01/04 — the round-5 collage scrim (140px, alpha 0.72) is GONE from
	   here. It was anchored to the collage, so it only ever covered the
	   bar's resting position; the bar now paints its own scrim, which
	   travels with it and is measurably darker than 0.572-backdrop AA
	   needs at this width too. Two scrims would only double-darken the
	   same 140px. */

	/* Over the image, 24px up from the collage's bottom edge (Ben, round 4).
	   The negative top margin exactly cancels the bar's own height so the
	   hero's 12px bottom padding still lands below the collage; `bottom`
	   then lifts it visually without touching flow. */
	.tbl-csi__viewbar {
		position: relative;
		bottom: 0;
		width: 100%;
		margin: calc(-1 * var(--csi-viewbar-h)) 0 0;
		padding-bottom: 0;
	}

	/* --- TT-01/05 — the next-case teaser at 390 --------------------------
	   JUDGMENT, all of it. Chris drew this tail at 1600 only, so every
	   value below is a call, on exactly the same footing as the hero block
	   above. The target is Motto's own 390 treatment, measured live in
	   research-06: a full-viewport panel, art full-bleed behind it, copy
	   left-aligned in normal flow.

	   The defect: the base rule's `aspect-ratio: 1600 / 1224` computes to a
	   298px box at 390, and every child is absolutely positioned at a
	   PERCENTAGE `top` resolved against it — so the badge, title and focus
	   line all landed BELOW their own section, piled on each other. It is
	   two failures at once: display type in a 192px column, inside a box
	   too short to hold it. Dropping the ratio and returning the type to
	   flow fixes both halves together.

	   DEPARTURE FROM THE REFERENCE, deliberate: Motto covers its own title
	   with the photo and shows none, but Motto puts "keep scrolling for the
	   next case study" INSIDE its panel. On TBL that copy lives in the
	   separate nextbar above, so a titleless teaser would leave a visitor
	   with a badge, a focus line and no idea which case study is next. The
	   title is kept and shrunk. */

	/* `min-height`, not `height`, so long copy can still grow the panel.
	   The 100vh/100svh pair is the same fallback `.tbl-csi__sentinel` uses,
	   for the same reason: mobile browser chrome must not make it
	   overshoot. Padding is the hero's mobile rhythm verbatim (134px after
	   Pastel #150 raised the hero's floor from 104; 12px on the other three)
	   rather than a third spacing system — research-04 established the
	   teaser and the hero are the same construction, so #150 moves both or
	   the two drift apart on the one page that shows them back to back. */
	.tbl-csi__teaser {
		aspect-ratio: auto;
		min-height: 0;
		padding: max(9.875%, 134px) 12px 12px;
		/* Flex purely so the in-flow anchor below can stretch to the panel.
		   Taking the anchor out of `inset: 0` shrank the tap target to the
		   158px of copy and left ~580px of dead panel that IS a link on
		   every other width — a regression this rule introduced. The collage
		   is absolute, so it is not a flex item and is unaffected. */
		display: flex;
	}

	/* The anchor comes out of absolute positioning and back into flow so it
	   can size to its own type. It stays POSITIONED (`relative`), which is
	   what keeps it painting above the collage: both are z-index:auto
	   positioned boxes, so source order decides, and interior-chrome.php
	   emits the collage first precisely for this. Getting it wrong swallows
	   every click on the panel — verify the tap target, do not assume it. */
	.tbl-csi__teaser-link {
		position: relative;
		inset: auto;
		display: block;
		flex: 1 1 auto;
	}

	/* Full-bleed art. `inset: 0` clears the desktop `top`/`left`, but the
	   base `width` and `aspect-ratio` would still win over it, so both are
	   unset here. The img already carries object-fit: cover at 100%/100%.
	   No inset, no radius, no letterbox — research-06 rules all three out. */
	.tbl-csi__teaser-collage {
		position: absolute;
		inset: 0;
		width: auto;
		aspect-ratio: auto;
		border-radius: 0;
	}

	.tbl-csi__teaser-collage img {
		border-radius: 0;
	}

	/* Below 768 the art goes behind the copy for the first time — on
	   desktop the collage sits at 32.19% and never meets the type at all.
	   White ink straight onto a photo is a contrast defect this rule
	   introduced, not one it inherited: Spero's feature image is pale
	   orange behind the title. Painted by the FIGURE so it is bounded to
	   the art and cannot appear on a teaser that has no image (Kubota,
	   Simpliphy and Dunlop have none), and as ::after on an earlier
	   positioned sibling so the anchor still paints above it.

	   Alpha 0.58 is ticket 04's derived value, reused rather than
	   re-guessed: it clears the universal floor of 0.535 at which white
	   text passes AA's 4.5:1 over ANY backdrop, including pure white.
	   Full strength to 45% (380px) covers the deepest copy block measured
	   across the four published studies (334px), then ramps out by 65%. */
	.tbl-csi__teaser-collage::after {
		content: "";
		position: absolute;
		inset: 0;
		background: linear-gradient(to bottom,
			rgba(0, 0, 0, 0.58) 0%,
			rgba(0, 0, 0, 0.58) 45%,
			rgba(0, 0, 0, 0) 65%);
	}

	/* The pill keeps its flex box and its 140x22 — only the centring comes
	   off, so it sits at the 12px gutter like everything else. */
	.tbl-csi__teaser-badge {
		position: static;
		transform: none;
	}

	/* Off `--csi-col-w` (49.1875% = 192px at this width) and off
	   `--h1-font-size`. The size is a literal on purpose: the fluid
	   `--h0`-`--h6` tokens divide a length by a length inside clamp() and
	   collapse to 16px in Firefox (variables.css:898-928), and this element
	   has no desktop frame to fall back on if that happens. Left-aligned,
	   because centring display type in a 366px column produces exactly the
	   ragged block this rule is fixing. */
	.tbl-csi__teaser-title {
		position: static;
		display: block;
		transform: none;
		width: auto;
		margin-top: 16px;
		text-align: start;
		/* 24/30 is derived from a live measurement, not picked: at 32px the
		   longest published title ("Helping the leading in paddlesports
		   launch the next generation of pickleball", Simpliphy) fit 23
		   characters to a line in this 366px column and ran to FOUR lines.
		   Characters-per-line scales inversely with size, so three lines
		   needs ~26/line and 23 x 32 / 26 = 28.3px is the ceiling; 24px
		   lands ~30/line, which leaves slack for a word break rather than
		   sitting on the limit. */
		font-size: 24px;
		line-height: 30px;
		letter-spacing: -0.3px;
	}

	.tbl-csi__teaser-focus {
		position: static;
		display: block;
		transform: none;
		margin-top: 24px;
		text-align: start;
	}
}

/* --- Reduced motion (plan 13, Task 3) -------------------------------------
   Transitions off, state changes preserved. Plan 11's mask still MOVES
   (it snaps instead of tweening) and plan 12 never attaches the wheel/touch
   gate listeners under reduced motion (JS-side, nothing to duplicate here).
   .tbl-csi__notes-link is included per spec even though no such class is
   emitted anywhere in this file today (no Field Notes website link rule
   exists) — harmless no-op, kept for forward compatibility rather than
   dropped silently. */
@media (prefers-reduced-motion: reduce) {
	html {
		scroll-behavior: auto;
	}

	.tbl-csi__view,
	.tbl-csi[data-cs-view="visual"] .tbl-csi__view--visual,
	.tbl-csi[data-cs-view="reading"] .tbl-csi__view--reading,
	.tbl-csi[data-cs-view="reading"] {
		transition: none;
	}

	/* .tbl-csi__toggle-seg was missing from this list through ten build
	   rounds: it carries `transition: var(--button-easing)` at :308, so the
	   segment still tweened its colours under the preference while every
	   sibling control was already covered (ticket 03). */
	.tbl-csi__toggle-seg,
	.tbl-csi__toggle-mask,
	.tbl-csi__viewbar-all,
	.tbl-csi__notes-link,
	.tbl-csi__quote-link,
	.tbl-csi__nextbar-fill {
		transition: none;
	}

	.tbl-csi__hero-eyebrow,
	.tbl-csi__hero-title,
	.tbl-csi__focus,
	.tbl-csi__hero-collage {
		animation: none;
	}
}

/* --- Case-study nav state (260805-lvw, items 5/7/8) -----------------------
   Desktop only: scripts.js only calls openBG()/closeBG() above 1024px, so
   below 1025 `.open-bg` never appears and a transparent treatment would
   strand white ink on a white bar. Tablet/mobile keep today's nav.
   The default state is expressed on :not(.open-bg) so the scrolled state
   needs no revert rule — the variables.css defaults simply return. */
@media (min-width: 1025px) {
	body.single-case_study #tb-site-header:not(.open-bg) {
		--nav-header-bg: transparent;
		--nav-header-blur: 0px;
		--nav-link-color: var(--White);
		/* Pastel #50 — was --Neutral-100. The banner colour is author-set per
		   case study, so a dark hover ink disappears entirely on the dark ones
		   (VintageView). The theme's hover rule already draws an underline
		   (theme.css :23961), so keeping the colour at White leaves the
		   underline as the whole affordance, which is what was asked for. */
		--nav-link-hover-color: var(--White);
		--nav-logo-color: var(--White);
		--nav-dots-color: var(--White);
		--nav-bolt-circle-bg: var(--Neutral-100);
		--nav-bolt-color: var(--White);
	}

	/* (1,3,1) — outranks BOTH theme.css's base .inner-header rule and its
	   @supports not(backdrop-filter) fallback, which sets a literal
	   rgba(255,255,255,0.95) and bypasses --nav-header-bg entirely. */
	body.single-case_study #tb-site-header:not(.open-bg) .inner-header {
		background: transparent;
	}

	body.single-case_study #tb-site-header:not(.open-bg) .menu-drawer-trigger:hover,
	body.single-case_study #tb-site-header:not(.open-bg) .menu-drawer-trigger:focus-visible {
		--nav-dots-color: var(--Neutral-100);
	}
}

/* --- Semantic pass (round 8) ---------------------------------------------
   The hero eyebrow + headline are now ONE <h1> (two styled spans), and the
   section labels / chips / "Client Testimonial" carry heading TAGS so the
   document outline reads Overview -> Challenge -> Solution -> Results ->
   Client Testimonial, with the descriptive line demoted to <h3>. Visual
   hierarchy is CSS, not tag — every rule below exists so the tag change is
   invisible on screen. */
.tbl-csi__hero-h1 {
	display: flex;
	flex-direction: column;
	align-items: center;
	margin: 0;
	font: inherit;
	letter-spacing: normal;
}

.tbl-csi__chips,
.tbl-csi__quote-label {
	font: inherit;
	letter-spacing: normal;
	margin: 0;
}

.tbl-csi__section-label {
	font-family: inherit;
	font-weight: 400;
	letter-spacing: normal;
	font-size: 20px;
	line-height: 30px;
	margin: 0;
}

.tbl-csi__section-heading {
	font-size: 50px;
	line-height: 54px;
	letter-spacing: -0.5px;
	font-weight: 400;
	margin: 0;
}

/* --- Responsive: visual view collapses to one column below 1024 (TT-01/01)
   The :617 grid tokens are percentages, so the desktop proportions survived
   at every width and nothing ever reflowed. Measured at 390: a 163.8px label
   track holding a 131px chip, a 50px heading rendering into a 191.8px box,
   and body copy at 24 characters a line — 42% of the phone viewport spent on
   an empty column.

   Chris drew no mobile frame, so EVERY VALUE BELOW IS JUDGMENT, recorded the
   same way the 768 block already records the hero's.

   Scoped to --visual throughout. The reading view is live between 768 and
   1024 and its own grid is deliberate; this block must not reach it. That
   scoping is also what makes the heading rule safe here — the round-8
   semantic block above re-declares .tbl-csi__section-heading at (0,1,0), and
   a media query adds no specificity of its own, so an unscoped rule would
   depend entirely on sitting later in the file. */
@media (max-width: 1024.98px) {
	/* Both selectors from :617, not just --numbered — Project Overview
	   reflows on the same rule as the numbered sections. */
	.tbl-csi__view--visual .tbl-csi__section--overview,
	.tbl-csi__view--visual .tbl-csi__section--numbered {
		display: block;
		padding-left: var(--csi-eyebrow-x);
		padding-right: var(--csi-eyebrow-x);
		/* Caps the measure without adding a second content box: the section
		   IS the text column, so label, heading and body all share one
		   width. 540px is ~78 characters at the 16px body's measured 6.92px
		   advance; below ~640px the viewport is the narrower constraint and
		   this does nothing. Percentages in max-width resolve against the
		   same containing block --csi-eyebrow-x does, so the two agree. */
		max-width: calc(540px + 2 * var(--csi-eyebrow-x));
		/* Round 2. Without this the cap left the section at x:0 and dropped
		   all the surplus on the right — QA round 3 measured a 4.6:1 gutter
		   ratio at 1023 against media bands that stay symmetric at every
		   width. Inert below ~640px, where the cap itself is inert. */
		margin-inline: auto;
	}

	/* The rail's inset is 20.0893% of the 42% TRACK (:33). With the grid gone
	   it would resolve against the section's content box instead and indent
	   the label against a body that is not indented. */
	.tbl-csi__view--visual .tbl-csi__section-rail {
		padding-left: 0;
		margin-bottom: 30px;
	}

	/* 36 rather than 30, matching the overview body's own internal stack gap
	   — the same split the reading view already makes at :1043 / :1058. */
	.tbl-csi__view--visual .tbl-csi__section--overview .tbl-csi__section-rail {
		margin-bottom: 36px;
	}

	/* 50px in a 324px column is ~15 characters a line. Fluid to 28 at the
	   floor, reaching the frame's 50 exactly at 1024 where the desktop
	   column returns. Line height is the frame's 54/50 as a ratio so the two
	   cannot drift. No division inside calc() — QA round 1 found Firefox
	   correctly rejecting calc(length / length) in variables.css.

	   The floor is 28 rather than 32 because greedy wrapping makes the fill
	   of a heading's widest line jump around with the type size: measured
	   across the four Kubota headings at 390, a 32px floor left "Show the
	   results. Celebrate the client wins." filling 70.8% of the column,
	   30px lifted the worst case to 88.1% and 28px to 94.7%. 28 is the
	   value with margin, not the value that merely passes. */
	.tbl-csi__view--visual .tbl-csi__section-heading {
		font-size: clamp(28px, 4.883vw, 50px);
		line-height: 1.08;
	}
}

/* --- Figma diff (TT-01/01 round 3, answering QA round 4) ------------------
   Six desktop fidelity gaps against frames 10362:4526 (What We Did),
   10597:5251 (testimonial link) and 10362:4563 (next-case bar). None of
   these are about the mobile reflow this ticket was written for; they are
   pre-existing and no round had measured them.

   Read via get_metadata + get_variable_defs + get_screenshot.
   get_design_context timed out twice at 300s+ and dropped its transport
   once, so no fill or corner-radius value was readable this session — the
   two places that matters are flagged inline.

   END OF FILE ON PURPOSE. The round-8 semantic block re-declares
   .tbl-csi__quote-label at (0,1,0) with `font: inherit` (:1399-1404), and
   `font` is a shorthand — it resets size, line-height, weight and family
   together. Anything placed earlier loses to it silently. */

/* CSI-22 — the three category labels. Chris binds all of them to
   `Category - Small` (Sequel Sans / 10 / 17.78 / +2 tracking); all three
   inherited the 16px body instead, in both views, so a small tracked
   eyebrow rendered as ordinary body copy. The frame's Book Disp 405 is
   unreachable, not ignored — Book Disp is unlicensed and the 300 slot
   falls back to Roman (fonts.css:152-160), so 400 is what 405 would draw. */
.tbl-csi__notes-label,
.tbl-csi__did-label,
.tbl-csi__quote-label {
	font-size: 10px;
	line-height: 17.78px;
	letter-spacing: 2px;
	text-transform: uppercase;
}

/* CSI-23 — What We Did list rhythm and type.
   36.333 is the PITCH, not the line box: Chris nests a 31-tall `Item`
   inside a 36.333-tall `Item:margin`. :745 read it as the line box, which
   made every row 6.333px too tall before anything else went wrong.

   The size is Paragraph-Large (20 / 30 / +1). Two independent reads land
   on 20: the frame's item text box is 31 tall, and its 462px width against
   the live string's 329.19px at 16px leaves a 50.5px residual over 49
   characters — 1.03/char, which is the token's tracking.

   The gap is 6.333 rather than the frame's 5.333 because line-height here
   is the token's 30, not the frame box's 31; the extra pixel moves into
   the gap so the pitch stays exactly 36.333 either way.

   padding-bottom is zeroed because a theme rule puts 6px on every
   li:not(:last-child). `.tbl-csi__did-item { margin: 0 }` (:743) looks
   like it handles that and does not — the 6px is padding, so it was
   adding to every pitch unseen. */
.tbl-csi__did-item {
	font-size: var(--h5-font-size);
	line-height: 30px;
	letter-spacing: 0;   /* Pastel #132 (Chris) — was the token's +1 */
	padding-bottom: 0;
	margin-bottom: 6.333px;
	color: var(--header-dark-color);
}

.tbl-csi__did-item:last-child {
	margin-bottom: 0;
}

.tbl-csi__did-item::before {
	border-radius: 50%;
}

/* CSI-24 — the testimonial link. It computed `oklch(0.68 0.17 228)` from a
   global anchor colour, with a matching out-of-gamut half-transparent
   underline (`color(srgb -0.307 0.664 0.916 / 0.5)`) — the only coloured
   element in an otherwise monochrome section. `inherit` rather than a hex
   so it tracks the section's ink.

   Scoped one level deeper than the :1104 rule on purpose: the cyan's
   source is in a CORS-blocked stylesheet and could not be read, so this
   buys specificity headroom without reaching for !important.

   FLAG: the frame's exact fill is unread. It renders black-ish against the
   label's grey, which is also what a 20px glyph does next to a 10px one.
   If Chris drew pure black this wants one more pass. */
.tbl-csi .tbl-csi__quote-link {
	color: inherit;
	text-decoration-color: currentColor;
}

/* CSI-25 — air around the next-case bar. Chris leaves 121 between the last
   media band and the rule, and 57 between the counter row and the next
   case's colour block. Both were 0. That is not only tight: with no gap
   the 1px rule sat exactly on the media band's bottom edge, so the divider
   did not render at all, and the counter row touched the teaser.

   `* var(--csi-gap-scale)` is the file's own mechanism for per-slot
   vertical gaps (:908-955) and carries these to 79.9 / 37.6 below 1024
   without a new media query. 121px of dead air on a phone is not a value
   Chris drew. */
.tbl-csi__tail {
	margin-top: calc(121px * var(--csi-gap-scale));
}

.tbl-csi__nextbar {
	padding-bottom: calc(57px * var(--csi-gap-scale));
}

/* --- Pastel QA round (canvas 4gl50ggo), mobile -----------------------------
   Appended at end of file on purpose: every rule here overrides an earlier
   one at equal specificity and relies on source order, the same mechanism
   the 767.98 block above already uses. Comment numbers are Pastel's. */
@media (max-width: 767.98px) {

	/* #86 / #87 — "make the padding here 60px on mobile ... for each section
	   in the case study interior". The per-slot desktop gaps (92/147/137/190)
	   ride --csi-gap-scale 0.66, which lands 60.7/97/90.4/125.4 at 390 — one
	   of the four happened to be right and the other three were not. Flatten
	   all four to the requested 60. */
	.tbl-csi__view--visual .tbl-csi__section--overview,
	.tbl-csi__view--visual .tbl-csi__section--numbered,
	.tbl-csi__view--visual .tbl-csi__section--numbered[data-cs-section="03"],
	.tbl-csi__view--visual .tbl-csi__section--testimonial {
		margin-top: 60px;
	}

	/* #88 — left/right padding 20px. The testimonial keeps its desktop
	   --csi-testi-x / --csi-testi-w (13.75% / 74.07%), which is a 51.5px
	   left inset and a 277px column at 375 — visibly narrower than every
	   other section and not the requested gutter. */
	.tbl-csi__view--visual .tbl-csi__section--testimonial {
		margin-left: 20px;
		margin-right: 20px;
		width: auto;
	}

	/* #90 — "the text in this needs to scale down like the headers do based
	   on screen width". The 42px/52px frame value is unconditional today, so
	   the quote runs ~7 words a line at 375. Linear 28px at 375 -> 42px at
	   767, written as an explicit clamp rather than an --h* token: the fluid
	   tokens divide a length by a length inside clamp() and collapse to 16px
	   in Firefox (variables.css:898-928), which this element cannot absorb.
	   Line-height goes on the BLOCKQUOTE, not the <p> — theme.css carries a
	   LAYERED `blockquote:not(.tiktok-embed) > p { line-height: inherit
	   !important }`, so the child's own value is unreachable. */
	.tbl-csi__view--visual .tbl-csi__quote .tbl-csi__quote-text {
		font-size: clamp(28px, calc(14.61px + 3.57vw), 42px);
	}

	.tbl-csi__view--visual .tbl-csi__quote {
		min-height: 0;
		line-height: 1.24;
		margin: 40px 0 32px;
	}

	/* #92 — "decrease the line height". 45px against the 20px
	   --body-font-size-larger is a 2.25 ratio; at 375 the two-line label
	   measured 90px tall for 40px of type. */
	.tbl-csi__nextbar-label {
		line-height: 28px;
	}
}

/* #14 / #48 — "88px between the top banner and the content on desktop and
   60px for mobile". The gap was the first section's own 92px rhythm margin
   doing double duty; it now belongs to whichever panel is first in flow, so
   reading and visual views agree and the inter-section rhythm above is
   free to differ from it. Placed on the child rather than a padding on
   .tbl-csi__views because the inactive panel is absolutely positioned
   against that container during the 510ms fade. */
.tbl-csi__view > :first-child {
	margin-top: 88px;
}

@media (max-width: 767.98px) {
	.tbl-csi__view > :first-child {
		margin-top: 60px;
	}
}

/* #13 (Chris) — testimonial quote was 600 (semibold); the Figma face is
   medium. 500 is a real loaded Sequel Sans cut, not a synthesised one, so
   this is a face swap rather than a faux-bold change. Set on the
   blockquote AND the <p>: theme.css's layered blockquote rules have bitten
   this element twice already (line-height, padding-right). */
/* Pastel #99 (Chris) — the reading view was never included, so only the
   visual view got the medium cut. Both views now carry it. */
.tbl-csi__view--visual .tbl-csi__quote,
.tbl-csi__view--visual .tbl-csi__quote .tbl-csi__quote-text,
.tbl-csi__view--reading .tbl-csi__section--testimonial .tbl-csi__quote,
.tbl-csi__view--reading .tbl-csi__section--testimonial .tbl-csi__quote .tbl-csi__quote-text {
	font-weight: 500;
}

/* Pastel #97 (Chris) — case-study body copy read too tight; the body token is
   0 tracking, this opens it to the file's own 0.2px. Both views. */
.tbl-csi__section-copy p {
	letter-spacing: 0.2px;
}

/* #45 — hover on the NON-active segment. The active one is covered by the
   sliding mask (z-index 3), so this only ever paints under the inactive
   label; keying it off aria-checked rather than :not(:hover) gymnastics
   means the swap is automatic when the mask moves. --csi-chip-bg is the
   file's existing light grey, reused rather than a seventh unbound hex. */
.tbl-csi__toggle-seg[aria-checked="false"]:hover {
	background: var(--csi-chip-bg);
}

/* #12 (Chris) / #85 / #95 — the hero collage opens on scroll.

   Three asks have landed on this one effect:

   1. #12 "no space at the bottom of the image" — the hero's 20px
      padding-bottom was zeroed here at >= 768 so the collage sat flush with
      its own section's edge. REVERTED 2026-08-26 (Ben): with the grow now a
      clip window rather than a bottom-anchored scale, he wants the 20px gap
      back. The base rule at the top of this file carries it; there is no
      override left, which is why nothing appears here for ask 1.

   2. #85 "start smaller and fill its container width".

   3. #95 (Ben) — the scale(0.7) that answered #85 resized the PHOTO along
      with the box. Replaced with the Expanding Full Width Image module's
      construction: a clip window that opens over a media element which never
      changes size. Travel is the amount Ben asked for — the window opens 30%
      to reach its width (start 1/1.3 = 76.92% wide, so 11.54% clipped per
      side) and 10% to reach its height (start 1/1.1 = 90.91% tall).

   The height opens UPWARD only — the bottom inset stays 0 — so the collage's
   visual bottom edge never leaves the flush position ask (1) established, and
   the sticky viewbar that overlays that edge is unaffected. The layout box
   never changes size, so there is no reflow on any frame.

   `round` carries the corner radius through the scrub; without it the box
   renders square-cornered until the window is fully open.

   Timeline stays scroll(root block), NOT the Expanding Image module's
   view(block) — measured, not assumed. #95 asks for full size "by the time it
   reaches the center of the viewport", which on a view timeline is exactly
   `cover 0% cover 50%`. But this collage is the page's hero: at 1489x1364 it
   loads with its top at 419px, already 88% of the way through that range, so
   the window would arrive on screen essentially open and the grow would never
   be seen. A page-scroll range keeps the effect visible from load, which is
   the behaviour the comment is refining rather than removing.

   400px is the center-crossing distance for a ~1000px-tall viewport: the
   collage's own centre sits at 805px from the document top here, so it passes
   the viewport's centre after (805 - vh/2) px of scroll — 123px at 1364,
   405px at 800. That distance depends on the hero title's line count and the
   collage's width-driven height, so it is not expressible portably in CSS;
   400 is the middle of the desktop range.

   linear is mandatory, not stylistic: `animation` shorthand defaults to
   `ease`, which on a scroll-scrubbed timeline reads as the image lagging and
   then snapping. Scoped to >= 768 (the mobile hero is a different
   construction entirely, see the 767.98 block) and gated behind @supports, so
   browsers without scroll-driven animations get the finished state — the CSS
   default IS the end state, never a permanently clipped hero. */

/* Pastel #124 (Chris): "can image start up higher... more outward expansion on
   left and right vs all sides."

   Both halves are the same edit. The collage BOX already sits only 20px under the
   Project Focus block — measured live — so the ~84px gap he is pointing at is the
   9.09% TOP inset hiding the first 56px of the picture. Taking the vertical
   component to zero starts the image at the box's own top edge, which is as high
   as it can go without moving the layout, and leaves the travel purely lateral,
   which is the second half of what he asked for.

   The horizontal start goes 11.54% -> 14% so the sideways movement reads as more
   than it did rather than merely as all that is left. Reference (vcc2, the
   .tb-reveal-banner he named) opens a uniform 60px on a full-bleed frame and is
   finished by the section's centre; the manner is the same, the emphasis is not,
   because he explicitly asked for left/right over all sides. */
@keyframes tbl-csi-collage-expand {
	from { clip-path: inset(0% 14% round var(--csi-media-radius)); }
	to   { clip-path: inset(0% 0% round var(--csi-media-radius)); }
}

@supports (animation-timeline: scroll()) {
	@media (min-width: 768px) and (prefers-reduced-motion: no-preference) {
		.tbl-csi__hero-collage {
			animation: tbl-csi-collage-expand linear both;
			animation-timeline: scroll(root block);
			animation-range: 0 400px;
		}
	}
}

/* --- Pastel #149 (Chris), mobile only --------------------------------------
   "On mobile, I'd love to see this area styled just like the top of the case
   study pages (where there's a solid background color, headline, then a peek
   of the image)." So the next-case teaser stops being copy floated over
   full-bleed art and becomes the hero's construction at phone size. Every
   value below is the hero's own mobile value read off the live page, not a
   third spacing system — research-04 already calls the two one construction,
   and this makes that claim literal where a visitor can see both.

   The "peek" is structural, not a crop: the panel is deliberately taller than
   its slot and clipped by .tbl-csi__tail (--csi-tail-lift, #49's climb), so
   the art meets the fold and finishes revealing as the gate fills.

   This REVERSES a documented judgment. The 767.98 block left-aligns the teaser
   title because centring 24px display type in this column read ragged to us.
   The hero centres 34px in the same 351px column and Chris has now asked for
   parity in as many words, so his call supersedes ours. Checked against the
   longest published title before shipping.

   Appended at end of file with the rest of the Pastel round: every rule here
   beats the 767.98 block at equal specificity on source order alone. */
@media (max-width: 767.98px) {

	.tbl-csi__teaser {
		flex-direction: column;
		align-items: center;
	}

	/* Out of `flex: 1 1 auto` and, critically, out of `position: relative`.
	   Static is load-bearing: the tap-target ::after below resolves its inset
	   against the nearest positioned ancestor, and a relative anchor would
	   trap it inside the ~130px copy block instead of the panel. */
	.tbl-csi__teaser-link {
		position: static;
		order: 1;
		flex: 0 0 auto;
		width: 100%;
		display: flex;
		flex-direction: column;
		align-items: center;
	}

	/* The panel stays ONE tap target. The art is the obvious thing to press
	   and it now sits outside the anchor, so the anchor reaches back over the
	   whole panel. The teaser is the containing block (it is position:
	   relative AND carries `translate`, either of which is enough), and this
	   is the panel's only positioned box now that the collage is static — so
	   it paints above the art without needing a z-index.
	   VERIFY WITH elementFromPoint, do not assume — the rule this replaces
	   carried the same warning for the same reason. */
	.tbl-csi__teaser-link::after {
		content: "";
		position: absolute;
		inset: 0;
	}

	/* Back into flow beneath the copy, at the hero's own 4/3 and 24px gap.
	   `flex: 0 1 auto` + `min-height: 0` on purpose, NOT `0 0 auto`: the panel
	   is a fixed slot with `overflow: hidden`, so a card that does not fit is
	   permanently chopped rather than merely below the fold. Measured at 767 a
	   rigid 4/3 is 743x557 against ~426px of remaining panel, so 131px of it
	   could never be revealed by the climb. Letting it shrink hands the excess
	   to object-fit: cover, which is a crop — the failure mode the whole panel
	   already uses. At phone widths there is slack and nothing shrinks, so the
	   4/3 stands where Chris will actually see it. */
	.tbl-csi__teaser-collage {
		position: static;
		order: 2;
		flex: 0 1 auto;
		min-height: 0;
		width: 100%;
		margin-top: 24px;
		aspect-ratio: 4 / 3;
		border-radius: var(--csi-media-radius);
	}

	/* The gradient's only job was keeping white ink legible over the photo.
	   No copy touches the art any more, so it would now be dimming the image
	   for nothing. */
	.tbl-csi__teaser-collage::after {
		content: none;
	}

	.tbl-csi__teaser-title {
		text-align: center;
	}

	/* The 767.98 block flattened this to `display: block`, which put the label
	   and the value on one inline run — "PROJECT FOCUSFINANCE". Invisible while
	   the copy was left-aligned over art; obvious the moment it sits centred
	   next to the hero's own stacked pair. Restores the base rule, which is
	   already the hero's construction (flex column, centred). */
	.tbl-csi__teaser-focus {
		display: flex;
		flex-direction: column;
		align-items: center;
		text-align: center;
	}
}
