/* self-hosted — the reference's actual typeface (PP Neue Montreal), not
   a lookalike. Confirmed via computed font-family/@font-face on the
   live site and downloaded from its own font file. Variable weight
   100-900, matching the reference's own @font-face declaration. */
@font-face{
	font-family:'PPNeue';
	font-style:normal;
	font-weight:100 900;
	font-display:swap;
	src:url('../assets/fonts/ppneuemontreal-variable.woff2') format('woff2-variations');
}

/* ===== reset ===== */
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0;border:0}
img,video{display:block;max-width:100%;height:auto}
a{color:inherit;text-decoration:none}
button{font:inherit;color:inherit;background:none;cursor:pointer;-webkit-appearance:none}
ul{list-style:none}

/* ===== fluid type system (same mechanism as reference: clamp root size to viewport) ===== */
:root{
	--viewport-desktop:1600;
	--factor-desktop:1vw;
	--viewport-mobile:800;
	--factor-mobile:2vw;
	--viewport:var(--viewport-mobile);
	--factor:var(--factor-mobile);
	--viewport-ratio:calc(1000 / var(--viewport) * var(--factor));
}
@media (min-width:800px){
	:root{--viewport:var(--viewport-desktop);--factor:var(--factor-desktop)}
}
html{font-size:56.25%} /* 9px */
@media (min-width:360px){html{font-size:var(--viewport-ratio)}}
@media (min-width:1600px){html{font-size:62.5%}} /* 10px */

@media (prefers-reduced-motion:reduce){
	*,*::before,*::after{animation-duration:.001ms!important;animation-iteration-count:1!important;transition-duration:.001ms!important;scroll-behavior:auto!important}
}

/* ===== tokens ===== */
:root{
	--ease: cubic-bezier(.19,1,.22,1);   /* signature easing used everywhere on the reference */
	--ease-soft: cubic-bezier(.215,.61,.355,1);
	--font-main: 'PPNeue', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;

	--bg-dark: linear-gradient(#17181a,#0c0d0e);
	--bg-light: #f2f2f2;

	--nav-width: 12rem;
	--nav-visual-width: 11rem;
}

/* the reference site's homepage is light throughout — no persistent dark hero */
body{
	--fg-default: rgba(0,0,0,.9);
	--fg-mute: rgba(0,0,0,.6);
	--fg-subtle: rgba(0,0,0,.25);
	--fg-faint: rgba(0,0,0,.05);
	--bg-current: var(--bg-light);
}

html,body{height:100%}
body{
	font-family:var(--font-main);
	/* 1.6rem, not 1.5 — confirmed against the reference's actual computed
	   body font-size (16px at this root scale, not 15px). Since every
	   other size on the page is expressed in rem relative to this base,
	   that one-notch error was cascading into every button/heading/label
	   being ~6% off throughout the whole site. */
	font-size:max(9px,1.6rem);
	line-height:1.5;
	color:var(--fg-default);
	background:var(--bg-current);
	-webkit-font-smoothing:antialiased;
	transition:background .6s var(--ease), color .3s linear;
	overflow-x:hidden;
}

/* ===== NAV ===== */
.nav{
	position:fixed;
	left:0;bottom:0;
	width:100%;
	z-index:50;
	display:flex;
	flex-direction:row;
	align-items:center;
	justify-content:space-between;
	padding:1.2rem 1.6rem;
	background:var(--bg-current);
	transition:background .6s var(--ease);
}
@media (min-width:800px){
	.nav{
		top:0;left:0;bottom:0;width:var(--nav-visual-width);
		flex-direction:column;justify-content:space-between;
		padding:2.4rem 0;
		opacity:0;
		pointer-events:none;
		/* hard toggle, not a fade/slide-in — confirmed against the
		   reference's actual behavior */
	}
	/* hidden until the user actually scrolls — matches the reference,
	   which never shows the sidebar at scrollY=0 */
	body.has-scrolled .nav{
		opacity:1;
		pointer-events:auto;
	}
}

body[data-home="true"] .nav-items,
body[data-home="true"] .nav-foot{
	transition:opacity .2s var(--ease);
}

.nav-top{display:none}
@media (min-width:800px){
	/* centered within the sidebar's own width, not left-aligned with a
	   fixed padding — requested for both this page and the homepage */
	.nav-top{display:flex;flex-direction:column;align-items:center;gap:.2rem;padding:0 .8rem;text-align:center}
}
.nav-top-name{font-size:1rem;font-weight:700;letter-spacing:.02em;color:var(--fg-default);white-space:nowrap}
.nav-top-tagline{font-size:1rem;font-style:italic;font-weight:400;letter-spacing:.02em;color:var(--fg-mute);line-height:1.2}

.nav-items{display:flex;flex-direction:row;gap:.8rem}
@media (min-width:800px){
	.nav-items{flex-direction:column;align-items:center;gap:1.6rem}
}
/* icon box is a 44x44 square (matches reference), but the item itself
   is NOT forced into that same square — it auto-sizes taller to fit
   the label stacked below it (confirmed on the reference: 44 wide,
   57 tall for the whole icon+label item, not a fixed 44x44) */
.nav-item{
	display:flex;flex-direction:column;align-items:center;gap:.4rem;
	width:4.4rem;justify-content:center;
	color:var(--fg-default);
}
/* no hover background at all — confirmed on the reference: nothing
   changes (bg, icon fill, label opacity, all identical) when hovering
   these nav items, so the highlight this used to have was invented,
   not something to match */
.nav-icon{
	position:relative;
	width:4.4rem;height:4.4rem;display:flex;align-items:center;justify-content:center;
}
/* same scale+fade backdrop mechanism as .hire-btn-bg, white instead
   of the faint black used there — a deliberate addition, not
   something the reference itself has on these icons */
.nav-icon-bg{
	position:absolute;inset:0;
	border-radius:1.6rem;background:#fff;
	opacity:0;transform:scale(.8);
	transition:opacity .3s var(--ease),transform .3s var(--ease);
}
.nav-item:hover .nav-icon-bg{opacity:1;transform:scale(1)}
.nav-icon svg{position:relative;width:2.4rem;height:2.4rem} /* 24px, confirmed exactly against the reference's own svg */
/* 11px/600/line-height 1, letter-spacing .01em, NOT uppercase, full
   opacity — every value confirmed via computed styles on the
   reference's own nav labels (text-transform:uppercase and
   opacity:.85 were wrong guesses, reference shows "Home" etc in
   normal case at full opacity) */
.nav-label{
	font-size:1.1rem;font-weight:600;line-height:1;letter-spacing:.01em;
}
@media (max-width:799px){.nav-label{display:none}}

.nav-foot{display:flex;gap:1rem;font-size:1rem;font-weight:600;text-transform:uppercase;color:var(--fg-mute)}
@media (min-width:800px){
	.nav-foot{flex-direction:column;align-items:center;gap:.4rem}
}

/* share/copy widget (services.html only) — floats at the bottom-left
   of the CONTENT area, not the sidebar; confirmed via the reference's
   own button rects (x:136, just past its ~128px sidebar, stacked
   vertically near the bottom of the viewport) */
.svc-share-stack{display:none}
@media (min-width:800px){
	.svc-share-stack{
		position:fixed;z-index:55;
		left:calc(var(--nav-width) + 1.6rem);bottom:1.6rem;
		display:flex;flex-direction:column;
		transition:left .4s var(--ease);
	}
	body.projects-panel-open .svc-share-stack{left:calc(var(--nav-width) + 40rem + 1.6rem)}
}
.svc-share,.svc-copy{
	position:relative;
	display:flex;align-items:center;justify-content:center;
	width:4rem;height:4rem;
	color:rgba(255,255,255,.95);
}
.svc-share svg,.svc-copy svg{width:2.4rem;height:2.4rem}
/* exact values confirmed against the reference's own "_tooltip-menu":
   bg rgb(26,26,26), text rgb(229,229,229), 16px text (not 11px),
   8px/12px padding (not 4px/8px), 8px radius (not 6px) */
.svc-copy-tip{
	position:absolute;left:calc(100% + .8rem);top:50%;transform:translateY(-50%);
	display:flex;align-items:center;gap:.6rem;
	padding:.8rem 1.2rem;border-radius:.8rem;
	background:#1a1a1a;color:#e5e5e5;
	font-size:1.6rem;font-weight:500;white-space:nowrap;
	opacity:0;pointer-events:none;
	transition:opacity .2s var(--ease);
}
.svc-copy-tip-check{width:1.6rem;height:1.6rem;flex-shrink:0}
.svc-copy:hover .svc-copy-tip{opacity:1}
.svc-copy.is-copied .svc-copy-tip{opacity:1}

.svc-share-menu{
	position:absolute;left:calc(100% + 1.2rem);bottom:0;
	width:16rem;padding:1.2rem;border-radius:1.2rem;
	background:rgba(20,20,20,.95);backdrop-filter:blur(1.2rem);
	box-shadow:0 1rem 3rem rgba(0,0,0,.3);
}
.svc-share-menu-label{font-size:1.1rem;font-weight:600;text-transform:uppercase;color:rgba(255,255,255,.5);margin-bottom:.8rem}
.svc-share-item{
	display:flex;align-items:center;gap:1rem;
	padding:.8rem 0;
	font-size:1.4rem;font-weight:500;color:#fff;
}
.svc-share-item svg{width:1.8rem;height:1.8rem;flex-shrink:0;color:rgba(255,255,255,.95)}

/* ===== MAIN ===== */
/* full-bleed, no reserved gutter — the nav is an overlay on the
   reference, it never pushes content over, even once visible. The
   Projects panel is the one exception: when it's open it pushes
   .main over by its own width, matching the reference. */
.main{
	min-height:100%;
	width:100%;
	transition:margin-left .5s var(--ease);
}
@media (min-width:800px){
	/* explicit width:100% above matters here: .main would otherwise be
	   width:auto, and margin-left on an auto-width block SHRINKS the box
	   by 40rem instead of sliding it — anything inside centered with
	   margin:0 auto (the pitch-grid/formula text columns) then only
	   re-centers by HALF of that, drifting out of alignment with
	   #reelCard below, which gets the full shift via its own transform.
	   With width pinned to 100%, margin-left instead pushes the whole
	   box right by the full 40rem (overflowing off the right edge,
	   harmlessly clipped by body's overflow-x:hidden), so every child —
	   centered or not — shifts by the exact same amount. (transform
	   would do this more cleanly, but #reelCard is a *descendant* of
	   .main and position:fixed, so a transform on .main would turn it
	   into #reelCard's containing block and break its JS-driven
	   viewport-relative left/top entirely — margin-left avoids that.) */
	body.projects-panel-open .main{margin-left:24rem}
	/* .reel-fixed is position:fixed with its left/top/width/height
	   driven straight from JS every frame (see updateReelProgress), so
	   .main's margin-left has zero effect on it — it needs its own
	   transform to shift over in step with everything else, which
	   layers on top of those JS-set offsets without fighting them.
	   transform is added to its existing transition list (not
	   replacing it) so the reel's own growth animation keeps its fast
	   linear easing. */
	body.projects-panel-open .reel-fixed{transform:translateX(24rem)}
}

/* ===== WORK / MASONRY ===== */
/* .work-pin reserves exactly the scroll distance the reel's grow
   needs (progress 0->1, i.e. vh*1.05 of real scroll) plus one more vh
   so .work only starts actually scrolling away once growth is fully
   done — matches completionY in updateReelProgress: (1.05 + 1)vh.
   No fade-out buffer any more — the reel docks into the page instead
   of fading, see the docking comment in main.js. */
.work-pin{height:205vh;height:205svh}

.work{
	position:sticky;
	top:0;
	/* transparent on purpose, not var(--bg-light) — body sits directly
	   behind it at the exact same color, so this is visually identical
	   during the intro, but it removes any chance of .work's own box
	   ever painting an opaque block over .pitch-grid/.team/.hire-cta
	   if it still physically overlaps them after being released from
	   sticky (the actual root cause of the "solid color over the grid"
	   bug — z-index ordering alone doesn't help once two elements are
	   genuinely tied and opaque). */
	background:transparent;
	color:rgba(0,0,0,.9);
	border-radius:1.6rem 1.6rem 0 0;
	/* same padding top and bottom — the last item in each column (col1's
	   4th image, col4's scroll-hint) should sit flush against it,
	   mirroring the top exactly */
	padding:1.6rem 0;
	/* clips both axes: the side columns already push far past the left/
	   right edges by design, and now the scroll-hint also needs a clean
	   bottom edge to disappear behind once it's pushed down */
	overflow:hidden;
	/* #reelCard used to live inside .work and briefly got its z-index:5
	   bumped up to .work's own level to escape .work's sticky-induced
	   stacking context — but that dragged .work's opaque background
	   along too, hiding .pitch-grid's text instead of the video. Fixed
	   properly now by moving #reelCard out of .work in the markup, so
	   this can go back to a plain z-index:1 tie with the sections after
	   it (which is fine — by the time they're visible, .work's own
	   content here has already faded to nothing anyway). */
	z-index:1;
	/* sticky needs an explicit height (not min-height) to know how
	   much of .work-pin's extra scroll room it's allowed to occupy
	   while pinned — the per-column fit in main.js (fitColumnsToViewport)
	   is what actually guarantees every column's cards land flush
	   against this box's own top/bottom padding, so there's no
	   mismatch between this and the real content height. */
	height:100vh;
	height:100svh;
}
/* 7 explicit columns (confirmed from the live reference's DOM: a CSS
   Grid of fixed column tracks, each holding a manually-ordered vertical
   stack — not CSS multicol auto-balancing). Column width is uniform;
   only each card's own image aspect ratio drives its height.
   Measured directly on the reference at 1700px viewport: 255px columns,
   16px gaps -> 7 cols + 6 gaps = 1881px, ~90px wider than the viewport
   on each side -> genuinely edge-cropped by design, not padded. */
:root{--reel-progress:0}
.masonry{
	display:grid;
	grid-template-columns:repeat(3,1fr);
	gap:1rem;
	width:calc(100% + 14rem);
	margin-left:-7rem;
	justify-content:center;
	/* gap itself stays fixed at the base value always — the spreading
	   motion instead comes from a translateX on the side .gcol columns
	   (driven from JS, in sync with the reel's own growth) so the
	   padding next to the reel never changes, only columns further out
	   visibly drift apart. Opacity is NOT set here either: a parent's
	   opacity caps every descendant's rendered opacity regardless of
	   what the descendant's own opacity is set to, so that's applied
	   per-card instead, below. */
}
@media (min-width:800px){
	.masonry{gap:1.6rem;grid-template-columns:repeat(7,1fr)}
}

.gcol{display:flex;flex-direction:column;gap:inherit}

.card{
	border-radius:1.2rem;
	overflow:hidden;
	cursor:pointer;
	transition:filter .5s var(--ease), box-shadow .3s var(--ease);
}

/* every real card fades out as the reel grows (CDP-confirmed against
   the reference: grid opacity 1->0) — applied per-card rather than on
   .masonry itself, see the note on .masonry above. .reel-spacer is
   excluded: it's already invisible and just reserves layout space for
   the one real video (#reelCard, a sibling of .masonry, unaffected by
   any of this since it's not a descendant). */
.masonry .card:not(.reel-spacer){
	opacity:calc(1 - var(--reel-progress, 0));
	transition:opacity .1s linear;
}

.reel-spacer{visibility:hidden}
.reel-spacer .card-media{aspect-ratio:var(--ar, .75)}

.card-media{position:relative;width:100%;height:100%}
.card:hover .card-media .ph,
.card:hover .card-media video{transform:scale(1.035)}
.ph,.card-media video{transition:transform .6s var(--ease)}
.card-media video{
	display:block;width:100%;height:100%;
	object-fit:cover;
	aspect-ratio:var(--ar, 3/4);
}

.ph{
	display:block;width:100%;height:100%;
	object-fit:cover;
	aspect-ratio:var(--ar, 3/4);
}

/* hover-cycle card (mirrors the reference's card that cross-fades through extra frames) */
[data-cycle] .card-media{aspect-ratio:var(--ar, 3/4);position:relative}
[data-cycle] .frame{
	position:absolute;inset:0;opacity:0;width:100%;height:100%;object-fit:cover;
	transition:opacity .6s var(--ease);
}
[data-cycle] .frame.is-active{opacity:1}

/* reel: ONE real video. left/top/width/height are driven live from JS
   (updateReelProgress, FLIP-style) starting from the exact rect of
   .reel-spacer (so at rest it sits precisely inside the grid, looking
   like a normal column-4 card) and growing/moving to a large centered
   overlay as the user scrolls — in sync with the grid's own
   column-gap growth. Visible throughout (no crossfade-in): it only
   fades on the way OUT, once fully grown, right before the pitch
   section would otherwise show through underneath it. */
.reel-fixed{
	position:fixed;
	z-index:5;
	/* interactive (hover shadow, cursor) at every stage, not just once
	   fully grown — opening the fullscreen video on click is still
	   separately guarded by .is-ready in main.js, so this is safe */
	pointer-events:auto;
	cursor:pointer;
	opacity:1;
	transition:width .1s linear, height .1s linear, left .1s linear, top .1s linear, transform .5s var(--ease);
}
.reel-fixed .card-media{
	width:100%;height:100%;
	border-radius:1.2rem;overflow:hidden;background:#000;
	box-shadow:0 0 0 rgba(0,0,0,0); /* none by default */
	transition:box-shadow .3s var(--ease);
}
/* plain :hover, not gated on .is-ready — applies whenever hovered,
   at rest, mid-growth, or docked */
.reel-fixed:hover .card-media{
	box-shadow:0 2rem 6rem rgba(0,0,0,.25);
}
.reel-fixed .card-media video{width:100%;height:100%;object-fit:cover}

/* hidden (hard toggle, not a fade) until the crossfade finishes.
   pointer-events:none here (the heading is plain text, not clickable —
   .reel-fixed's own click-to-open handler would otherwise fire when
   clicking the text too) with the button opted back in below, since
   it needs its own click. */
.reel-expanded-content{
	position:absolute;bottom:100%;left:0;right:0;
	text-align:center;margin-bottom:1.6rem;
	opacity:0;
	pointer-events:none;
}
.reel-fixed.is-ready .reel-expanded-content{opacity:1}
/* 32px / weight 600 / line-height 1.15 — confirmed via computed styles
   on the reference's own matching heading ("Deux Huit Huit. A
   super-digital design studio. Ready when you are."), was 26px/500/1.3 */
/* no independent max-width — .reel-expanded-content (its parent)
   already spans left:0/right:0 of .reel-fixed, which is sized by JS
   to the exact same width as the grown video card below it, so this
   just uses 100% of that instead of a separate, narrower cap */
.reel-expanded-content h2{
	font-size:3.2rem;font-weight:600;line-height:1.15;
	width:100%;margin:0 0 2rem;
	color:rgba(0,0,0,.9);
}

/* skip-intro hint — colors confirmed against the live reference
   (rgba(0,122,255,.2) background, rgba(0,0,0,.9) icon). It stays at
   full opacity the whole time on the reference — it doesn't fade, it
   just gets pushed down (translateY, driven from JS) until it's
   clipped out of view by .work's own box (overflow:hidden). */
.scroll-hint{
	display:flex;align-items:center;justify-content:center;
	width:100%;height:5.2rem;border-radius:999px; /* 52px, confirmed — was 48px */
	background:rgba(0,122,255,.2);color:rgba(0,0,0,.9);
	flex-shrink:0;
	transition:transform .1s linear;
}
.scroll-hint svg{animation:hint-bounce .7s infinite alternate var(--ease)}
@keyframes hint-bounce{from{transform:translateY(-.2rem)}to{transform:translateY(.2rem)}}

/* empty — width/height/margin-top set from JS (updateReelProgress) to
   match the reel's grown target box exactly, pulled up by a negative
   margin-top so its page position lines up with wherever the docked
   #reelCard freezes to (see the docking comment in main.js). The
   reel itself (position:fixed, z-index:5) renders on top of this the
   whole time — this div never shows anything, it only reserves the
   matching document space so everything after it lands in the right
   place, with nothing overlapping. */
.reel-landing{width:min(72vw,900px);margin:0 auto}

/* two-column pitch copy, directly below the docked video card.
   max-width 48rem (480px) and gap 2.4rem (24px) confirmed via computed
   styles directly on the reference's own "Allies to local..." grid —
   same values the formula section already uses below, not the much
   wider 90rem/4rem this used to have. */
/* width:min(48rem, 100% - 3.2rem), not max-width+horizontal padding —
   border-box padding was shrinking the actual content to 44.8rem
   (448px), short of the reference's true 480px (its own grid has zero
   padding of its own). This hits exactly 48rem once the viewport is
   wide enough, same as reference, while still keeping a safe margin
   on narrow screens instead of running edge to edge. */
.pitch-grid{
	background:var(--bg-light);
	display:grid;gap:2.4rem;
	width:min(48rem, 100% - 3.2rem);margin:0 auto;
	padding:4rem 0;
	position:relative;z-index:1;
}
@media (min-width:800px){.pitch-grid{grid-template-columns:1fr 1fr}}
/* works as a plain <p> (single paragraph) or a <div> wrapping several —
   font-size/line-height/color are inherited by nested <p> either way,
   and the flex+gap only matters when there's more than one */
/* weight 500, line-height 1.15, color .6 — confirmed via computed
   styles on the reference (same values the formula section below
   uses, not the heavier/looser defaults this used to have) */
.pitch-col{
	font-size:1.6rem;font-weight:500;line-height:1.15;color:rgba(0,0,0,.6);
	display:flex;flex-direction:column;gap:1.6rem;
}

/* ===== TEAM RING + HIRE TOGGLE ===== */
.team{padding:6rem 1.6rem 2rem;text-align:center;background:var(--bg-light);position:relative;z-index:1}
.team-heading{
	font-size:3.2rem;font-weight:600;line-height:1.15;
	color:rgba(0,0,0,.9);
	white-space:pre-line;
}
.team-ring{
	position:relative;
	width:min(56rem,92vw);
	aspect-ratio:1/1;
	margin:4rem auto 0;
	display:grid;
}
.team-ring>*{grid-area:1/1}
.team-glow{
	width:70%;height:44%;
	margin:auto;
	filter:blur(4rem);
	transform:rotate(-45deg);
	opacity:.85;
}
.team-ring-img{width:100%;height:100%;object-fit:contain}
.team-center{
	z-index:2;
	display:flex;flex-direction:column;align-items:center;gap:1.6rem;
	width:62%;max-width:30rem;margin:auto;
}
.team-label{
	font-size:1.6rem;font-weight:600;line-height:1.15;
	color:rgba(0,0,0,.6);
	max-width:20rem;
}
.team-toggle{
	position:relative;
	display:block;width:100%;height:5.2rem;
	border-radius:999px;overflow:hidden;
	background:url(../assets/images/team-glow.png) center/cover;
	transition:width .6s var(--ease), transform .3s var(--ease);
}
.team-toggle:hover{width:75%;transform:scale(1.1)}
.team-toggle-knob{
	position:absolute;top:.4rem;left:.4rem;
	height:calc(100% - .8rem);aspect-ratio:1;
	border-radius:50%;background:#fff;
	box-shadow:0 .2rem .6rem rgba(0,0,0,.15);
	transition:box-shadow .3s var(--ease);
}
.team-toggle:hover .team-toggle-knob{box-shadow:0 .4rem 1.2rem rgba(0,0,0,.25)}

.hire-cta{display:flex;justify-content:center;padding:2rem 1.6rem;background:var(--bg-light);position:relative;z-index:1}
/* real "Hire us" button — every value here confirmed via computed
   styles directly on the reference, not guessed: .hire-btn is 28px
   tall, 8px radius, #007AFF bg, white 16px/500 text. .hire-btn-bg is
   a SEPARATE layer sitting behind it (within the .8rem padded hit
   area), invisible at rest (opacity 0, scaled to 80%) that scales up
   to 100% and fades in only on hover — a subtle halo effect, not a
   color change on the button itself. */
.hire-btn-wrap{
	position:relative;display:inline-flex;
	padding:.8rem;
	pointer-events:auto; /* needed when nested in .reel-expanded-content, which is pointer-events:none */
	cursor:pointer;
}
.hire-btn-bg{
	position:absolute;inset:0;
	border-radius:1.6rem;background:rgba(0,0,0,.05);
	opacity:0;transform:scale(.8);
	transition:opacity .3s var(--ease),transform .3s var(--ease);
}
.hire-btn-wrap:hover .hire-btn-bg{opacity:1;transform:scale(1)}
.hire-btn{
	position:relative;
	display:flex;align-items:center;justify-content:center;gap:.2rem;
	height:2.8rem;padding:0 1rem;border-radius:.8rem;
	background:#007AFF;color:#fff;
	font-size:1.6rem;font-weight:500;line-height:1;
	white-space:nowrap;
}
.hire-btn svg{width:2rem;height:2rem;flex-shrink:0}

/* ===== "simple and effective formula" ===== */
/* left-aligned on the reference, not centered — confirmed via computed
   styles (text-align:start throughout this whole block) */
/* same width:min() fix as .pitch-grid above — was shrinking to 448px
   via border-box padding instead of the reference's true 480px */
.formula{
	width:min(48rem, 100% - 3.2rem);margin:0 auto;
	padding:6rem 0;text-align:left;
	background:var(--bg-light);position:relative;z-index:1;
}
.formula-label{font-size:1.6rem;font-weight:600;line-height:1.15;color:rgba(0,0,0,.9)}
.formula-heading{
	font-size:3.2rem;font-weight:600;line-height:1.15;
	color:rgba(0,0,0,.9);
	white-space:pre-line;
	padding-top:.8rem;
}
.formula-grid{
	display:grid;gap:2.4rem;
	padding-top:1.2rem;
}
@media (min-width:600px){.formula-grid{grid-template-columns:1fr 1fr}}
/* tighter and smaller than .pitch-col elsewhere (1.15 line-height vs
   1.4, weight 500 vs 400, .6 opacity vs .75) — confirmed via computed
   styles, this section reads noticeably denser than the others */
.formula-col{
	display:flex;flex-direction:column;gap:1.2rem;
	font-size:1.6rem;font-weight:500;line-height:1.15;
	color:rgba(0,0,0,.6);
}

/* ===== "Check out our latest projects" ===== */
/* every value here (484px header, 48px height, #007AFF, 724px grid,
   4x181px columns, 16px/8px radii, 36px logo, 12px text) confirmed via
   computed-style inspection directly on the reference */
.projects{padding:4rem 1.6rem;background:var(--bg-light);position:relative;z-index:1}
.projects-header{
	max-width:48.4rem;height:4.8rem;margin:0 auto;
	display:flex;align-items:center;justify-content:space-between;
	border-top:1px solid var(--fg-faint);
}
.projects-header h2{font-size:1.6rem;font-weight:600;color:rgba(0,0,0,.9)}
.projects-all{
	display:flex;align-items:center;gap:.4rem;
	font-size:1.6rem;font-weight:600;color:#007AFF;
}
.projects-all svg{width:1.6rem;height:1.6rem}
.projects-grid{
	max-width:72.4rem;margin:2.4rem auto 0;
	display:grid;grid-template-columns:repeat(3,1fr);gap:.8rem;
}
@media (min-width:800px){.projects-grid{grid-template-columns:repeat(4,1fr);gap:0}}
.project-card{display:block;position:relative}
.project-img{position:relative;aspect-ratio:1/1}
@media (min-width:800px){.project-img{margin:0 .8rem}}
.project-img img{width:100%;height:100%;object-fit:cover;border-radius:1.6rem;position:relative}
.project-meta{display:flex;align-items:center;gap:.8rem;padding-top:1.2rem}
@media (min-width:800px){.project-meta{padding:1.2rem .8rem 0}}
.project-logo{width:3.6rem;height:3.6rem;border-radius:.8rem;object-fit:cover;flex-shrink:0}
.project-text{overflow:hidden}
.project-title,.project-cat{font-size:1.2rem;font-weight:500;line-height:1.15;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.project-cat{color:rgba(0,0,0,.6)}

/* ===== service cards ===== */
/* 225x281 card, bg rgb(255,248,81), h4 26px/600, category label
   16px/600 below the card (not inside it) — all confirmed via
   computed-style inspection on the reference's own cards. Reference
   scrolls these horizontally rather than wrapping to a grid. */
.services{padding:4rem 0;background:var(--bg-light);position:relative;z-index:1}
.services-divider{max-width:48.4rem;height:4.8rem;margin:0 auto;border-top:1px solid rgba(0,0,0,.05)}
/* true 3x3 grid on the reference, confirmed by measuring all 9 card
   positions directly (3 distinct x values x 3 distinct y values) —
   not a horizontal scroll row, which is what this used to be */
.services-row{
	display:grid;grid-template-columns:repeat(3,22.5rem);gap:1.6rem;
	width:fit-content;margin:0 auto;
	padding:0 1.6rem;
}
@media (max-width:800px){
	.services-row{grid-template-columns:1fr;width:100%}
}
.service-card{width:100%;position:relative}
/* 4:5 aspect-ratio (not a guess — 225.3x281.7 measured directly,
   which is exactly 4:5). Title pinned to the top, category+arrow
   pinned to the bottom via justify-space-between on the inner
   wrapper — both confirmed from the reference's actual card markup,
   not the single bottom-aligned caption this used to be. */
.service-img{
	position:relative;
	width:100%;aspect-ratio:4/5;
	border-radius:.8rem;overflow:hidden;
	background-color:rgb(255,248,81);
	background-size:cover;background-position:center;
}
.service-card-inner{
	position:absolute;inset:0;
	display:flex;flex-direction:column;justify-content:space-between;gap:.8rem;
	padding:1.6rem 1.2rem;
}
.service-card-inner h4{
	font-size:1.8rem;font-weight:600;line-height:1.15;
	color:rgb(13,13,13);
}
@media (min-width:800px){.service-card-inner h4{font-size:2.6rem}}
.service-card-foot{display:flex;align-items:flex-end;justify-content:space-between;gap:.8rem}
.service-card-foot h3{font-size:1.4rem;font-weight:600;line-height:1.15;color:rgb(13,13,13)}
@media (min-width:800px){.service-card-foot h3{font-size:1.6rem}}
.service-card-foot svg{width:1.6rem;height:1.6rem;flex-shrink:0;color:rgb(13,13,13)}

/* ===== "What's it like working with us?" video ===== */
/* 484px wide, 9:16, 12px radius, object-fit:cover — confirmed via
   computed styles on the reference's own video (484x860.4, exactly 9:16) */
.working-video-section{
	width:min(48.4rem, 100% - 3.2rem);margin:3.2rem auto 0;
	background:var(--bg-light);position:relative;z-index:1;
}
/* scale(1.02) + soft layered shadow on hover — confirmed directly on
   the reference's own video (was missing entirely) */
.working-video{
	width:100%;aspect-ratio:9/16;
	border-radius:1.2rem;object-fit:cover;
	transition:transform .3s cubic-bezier(.4,0,.2,1), box-shadow .3s cubic-bezier(.4,0,.2,1);
}
.working-video:hover{
	transform:scale(1.02);
	box-shadow:0 1px 2px rgba(0,0,0,.06), 0 3.6px 3.6px rgba(0,0,0,.02), 0 6.4px 6.4px rgba(0,0,0,.04), 0 11px 11px rgba(0,0,0,.07), 0 20px 20px rgba(0,0,0,.13), 0 40px 40px rgba(0,0,0,.25);
}

/* ===== "Deux Huit Huit in a nutshell" footer ===== */
/* same 480px prose width as every other section; row divider is
   rgba(0,0,0,.25) (not the faint .05 used elsewhere) with the first
   row skipping its border — both confirmed via computed styles */
.nutshell{
	width:min(48rem, 100% - 3.2rem);margin:6.4rem auto 4rem;
	background:var(--bg-light);position:relative;z-index:1;
}
.nutshell-rows{padding-top:2rem}
.nutshell-row{
	display:grid;grid-template-columns:1fr 1fr;gap:2.4rem;
	align-items:start;padding:1.2rem 0;
	border-top:1px solid rgba(0,0,0,.25);
	line-height:1.15;
}
.nutshell-row:first-child{border-top:0}
.nutshell-row h3{
	display:flex;align-items:center;gap:.4rem;
	font-size:1.6rem;font-weight:500;color:rgba(0,0,0,.6);
}
.nutshell-row h3 svg{width:1.2rem;height:1.3rem;flex-shrink:0;opacity:.3}
.nutshell-row>div,.nutshell-row>ul{font-size:1.6rem;font-weight:600;color:rgba(0,0,0,.9)}

/* "Online presence" (real links + arrow icon) and "Leadership"
   (avatar + name/title) sub-lists — confirmed on the reference: each
   item gets its own 12px vertical padding and divider, nested inside
   the outer nutshell-row, not plain <br>-separated text */
.nutshell-list li{border-top:1px solid rgba(0,0,0,.25)}
.nutshell-list li:first-child{border-top:0}
.nutshell-list a{
	display:flex;align-items:center;justify-content:space-between;
	padding:1.2rem 0;line-height:1.15;
	transition:color .2s var(--ease);
}
.nutshell-list a:hover{color:rgba(0,0,0,.6)}
.nutshell-list a svg{width:1.6rem;height:1.7rem;flex-shrink:0;opacity:.3}
.nutshell-person{display:flex;align-items:center;gap:.8rem;padding:.8rem 0;font-size:1.2rem;font-weight:500;color:rgba(0,0,0,.6)}
.nutshell-person img{width:2.8rem;height:2.8rem;border-radius:999px;object-fit:cover;flex-shrink:0}

/* ===== reel video modal ===== */
/* every value here confirmed by clicking the real button on the
   reference and inspecting the dialog: blurred-video backdrop, 80vh
   video with native controls + 8px radius, 52px close circle at
   15% white opacity that scales 110% on hover, top-right 24px inset */
.reel-dialog{position:fixed;inset:0;z-index:100;display:grid}
.reel-dialog[hidden]{display:none}
.reel-dialog-overlay{position:relative;grid-area:1/1;width:100%;height:100%;background:#000;overflow:hidden}
.reel-dialog-bg{width:100%;height:100%;object-fit:cover;filter:blur(10rem);transform:scale(1.2)}
.reel-dialog-inner{position:relative;grid-area:1/1;display:grid;place-items:center;padding:2.4rem}
.reel-dialog-close{
	position:absolute;top:2.4rem;right:2.4rem;
	width:5.2rem;height:5.2rem;border-radius:999px;
	display:flex;align-items:center;justify-content:center;
	color:#fff;
}
.reel-dialog-close-bg{position:absolute;inset:0;border-radius:999px;background:rgba(255,255,255,.15);transition:transform .2s var(--ease)}
.reel-dialog-close:hover .reel-dialog-close-bg{transform:scale(1.1)}
.reel-dialog-close svg{position:relative;width:2rem;height:2rem}
.reel-dialog-video{height:80vh;width:auto;max-width:100%;border-radius:.8rem}

/* same scale+fade grey backdrop as .hire-btn-bg / .nav-icon-bg,
   applied to the project cards and service cards via ::before so no
   extra markup is needed — a deliberate addition, not something the
   reference itself has on these specific cards. For project cards
   the glow hugs only the image (not the logo/title row below it),
   with equal inset on all four sides. */
.project-img::before,.service-card::before,.projects-panel-card-img::before,.proj-coll-card-img::before{
	content:'';
	position:absolute;inset:-.8rem;
	border-radius:1.6rem;background:#E6E6E6;
	opacity:0;transform:scale(.8);
	transition:opacity .3s var(--ease),transform .3s var(--ease);
	z-index:-1;pointer-events:none;
}
.project-img:hover::before,.service-card:hover::before,.projects-panel-card-img:hover::before,.proj-coll-card-img:hover::before{opacity:1;transform:scale(1)}


/* ===== PROJECTS PANEL ===== */
/* slide-out panel docked next to the icon sidebar, confirmed against
   the reference by clicking its real "Projects" nav button: a 400px
   translucent white card (fixed, 8px inset top/bottom, 16px radius)
   sitting at left:nav-width, with a sticky tabs pill row and a 2-col
   project grid, and .main pushed over by the panel's own width
   rather than being overlaid/dimmed */
.projects-panel{
	position:fixed;top:0;bottom:0;left:var(--nav-width);
	width:40rem;max-width:calc(100vw - var(--nav-width));
	z-index:40;
	opacity:0;pointer-events:none;
	/* no transform here (dropped the translateX slide-in) — a
	   transform transition on this element, which contains a
	   backdrop-filter child (.projects-panel-bg) and cards with their
	   own z-index:-1 hover pseudo-elements several levels down, is a
	   known troublesome combination for Chrome's/WebKit's compositor:
	   the descendant hover transitions can get stuck un-painted until
	   some other layout-triggering mutation forces a fresh paint pass
	   — which only a tab switch happened to do, hence hover glow never
	   showing on the default tab until the user switched tabs once.
	   Opacity-only avoids the transform side of that combination. */
	transition:opacity .4s var(--ease);
}
.projects-panel.is-open{opacity:1;pointer-events:auto}
/* below the desktop breakpoint the icon nav is a bottom bar, not a
   left column, so the panel takes the full screen and slides up
   instead of sliding in from the left — same mechanism, different axis */
@media (max-width:799px){
	.projects-panel{
		left:0;top:0;bottom:0;width:100%;max-width:100%;
		transform:translateY(2.4rem);
	}
	.projects-panel.is-open{transform:translateY(0)}
	.projects-panel-bg{inset:0;border-radius:0}
	.projects-panel-close{display:flex}
}

.projects-panel-bg{
	position:absolute;inset:.8rem;
	border-radius:1.6rem;
	/* .97, not .85 — at .85 the blurred, shifting content behind the
	   panel (whatever .main scrolled to) bleeds through as a noisy,
	   unpredictable tint, which is what was swallowing the project
	   cards' #E6E6E6 hover glow (identical CSS to the homepage, but
	   unreadable against that noise). Near-opaque gives the glow a
	   flat, predictable surface to sit on instead. */
	background:rgba(255,255,255,.97);
	backdrop-filter:blur(2rem);
	-webkit-backdrop-filter:blur(2rem);
}
/* re-measured directly against the reference's real DOM (not just its
   rendered pixels this time): it's a genuine TWO-LEVEL sticky, not one
   shared sticky lid over both the head and the tabs like this used to
   be. The outer panel is a grid with an "auto" row for the head
   (truly fixed — not scrolling, not even sticky, just not part of the
   scrollable area at all) and a "1fr" row that's the actual scroll
   container; the tabs live INSIDE that scroll container with their
   OWN independent position:sticky, so they re-pin to the top of the
   card area specifically as you scroll, separate from the head above
   them. The single shared .projects-panel-sticky lid this replaces
   could only ever move the head and tabs away together, never
   replicate that independent re-pinning. */
.projects-panel-inner{
	position:absolute;inset:.8rem;
	border-radius:1.6rem;
	display:grid;grid-template-rows:auto 1fr;
	overflow:hidden;
}
.projects-panel-head{
	background:rgba(255,255,255,.97);
	border-radius:1.6rem 1.6rem 0 0;
	display:grid;grid-template-columns:1fr auto 1fr;align-items:center;
	gap:.8rem;
	/* 2rem 2rem .8rem — matches the reference's own header row padding
	   exactly (pt-20/px-20/pb-8) now that it's genuinely standalone;
	   the inflated padding-bottom this briefly had was only
	   compensating for the tabs sharing this same box, which they no
	   longer do. */
	padding:2rem 2rem .8rem;
}
.projects-panel-scroll{
	overflow-y:auto;
	overflow-x:hidden;
	scrollbar-width:none;
	padding:0 2rem 2rem;
}
.projects-panel-scroll::-webkit-scrollbar{display:none}
/* explicit grid-column on every child, not just source order — the
   close button is display:none on desktop, and a display:none item
   drops out of grid auto-placement entirely, which was shifting h2
   and .projects-panel-all into columns 1/2 instead of 2/3 (collapsing
   the "All" link right up against "Projects" instead of the far
   right) whenever the close button wasn't there to occupy column 1.
   Outer tracks are both 1fr (not auto) so they stay equal width
   regardless of the close button's/"All" link's own content width —
   that's what actually centers "Projects" between the panel's two
   true edges, rather than just centering within whatever's left over
   after an "All"-width-sized gap on one side only. */
.projects-panel-close{grid-column:1;justify-self:start}
.projects-panel-head h2{grid-column:2;text-align:center}
.projects-panel-all{grid-column:3;justify-self:end}
.projects-panel-close{
	display:none;align-items:center;justify-content:center;
	width:3.2rem;height:3.2rem;border-radius:999px;
	background:var(--fg-faint);color:var(--fg-default);
}
.projects-panel-close svg{width:1.6rem;height:1.6rem}
.projects-panel-head h2{font-size:1.6rem;font-weight:500;color:var(--fg-default)}
.projects-panel-all{
	display:flex;align-items:center;gap:.8rem;
	font-size:1.4rem;font-weight:500;color:var(--fg-default);
}
.projects-panel-all svg{width:1.6rem;height:1.6rem}

/* margin-bottom:0, not 1.6rem — .projects-panel-sticky (the tabs'
   now lives inside .projects-panel-scroll (see .projects-panel-inner's
   own comment above) with its own independent position:sticky, so it
   re-pins to the top of the card-scroll area on its own — separate
   from .projects-panel-head, which is a standalone non-scrolling grid
   row above this whole scroll area now, not sharing a lid with this
   anymore. margin-top:1.6rem (not the head's own padding) is what
   creates the gap below the head at rest, matching the reference's
   own pt-16 on its equivalent tabs wrapper. */
.projects-panel-tabs{
	position:sticky;top:0;z-index:2;
	display:flex;gap:0;
	background:var(--fg-faint);
	border-radius:.9rem;padding:.2rem;
	margin-top:1.6rem;
	margin-bottom:0;
}
.projects-panel-tab{
	flex:1;height:2.4rem;border-radius:.7rem;
	font-size:1.2rem;font-weight:500;letter-spacing:.02em;
	color:var(--fg-default);
	transition:background .2s var(--ease);
}
.projects-panel-tab.is-active{background:rgba(255,255,255,.95)}

.projects-panel-grid{
	display:grid;grid-template-columns:1fr 1fr;
	gap:1.6rem 0;
	margin-top:2rem;
	padding-bottom:2rem;
	/* no permanent transition here on purpose — main.js applies one only
	   for the duration of a tab switch (switchProjectsTab), so there's
	   zero chance of it competing with anything during the panel's own
	   opening animation */
}
/* min-width:0 overrides the grid item's default min-width:auto — without
   it, a 1fr track grows to fit its content's intrinsic min-content size
   (here, the <img>'s own natural width) instead of the fair 180px share,
   which is exactly what started happening once real downloaded photos
   (wider than the old placeholder images) replaced the original 4 cards,
   blowing both columns out to ~233px and overflowing the panel */
.projects-panel-card{display:block;min-width:0}
.projects-panel-card[hidden]{display:none}
.projects-panel-card-img{position:relative;aspect-ratio:1/1;padding:.8rem}
.projects-panel-card-img img{width:100%;height:100%;object-fit:cover;border-radius:1.6rem;position:relative}
/* hover glow color/radius/timing are shared with .project-img's rule
   above, but inset is overridden to 0 here specifically: unlike
   .project-img (no padding, image fills the wrapper edge-to-edge),
   .projects-panel-card-img has its own padding:.8rem (needed for the
   2-column grid's gutter, since the grid's own column-gap is 0 and
   relies on this per-card padding instead). That padding already
   insets the image 8px from the wrapper. Keeping the glow's -.8rem on
   top of that was DOUBLING the visible gap (8px padding + 8px glow
   inset = 16px) versus the homepage's single 8px — inset:0 puts the
   glow flush with the wrapper's own edge, so the total gap matches
   the homepage's 8px exactly. */
.projects-panel-card-img::before{inset:0}
.projects-panel-card-meta{display:flex;align-items:center;gap:.8rem;padding:.8rem .8rem 0}
.projects-panel-card-meta img{width:3.2rem;height:3.2rem;border-radius:.8rem;object-fit:cover;flex-shrink:0}
.projects-panel-card-text{overflow:hidden;font-size:1.2rem;line-height:1.15}
.projects-panel-card-title{color:var(--fg-default);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.projects-panel-card-cat{color:var(--fg-mute);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}


/* ===== SERVICES PAGE ===== */
/* cloned from deuxhuithuit.com/en/services, re-scanned pixel-by-pixel
   after the first pass got the color scheme backwards: the whole page
   (hero AND the TOC/accordion sections below it) sits on ONE
   persistent dark background — confirmed by decoding the page's own
   fixed background-image data URI and finding its ._default-background
   layer uses the exact linear-gradient(#303436,#242728) already in
   this file as --bg-dark. It isn't scoped to just the hero; there's no
   light-themed section anywhere on this page. */
/* overscroll-behavior:none stops the browser from rubber-banding the
   outer page once .svc-main's own internal scroll hits its end — body
   has nothing left to scroll (it's pinned to exactly one viewport),
   so without this the bounce attempt visibly shifts .svc-shell's own
   .8rem margin instead (top gap vanishes, bottom gap doubles) */
body.svc-page{overscroll-behavior:none}
/* transparent, not var(--bg-dark) — an opaque flat background here was
   completely blocking the fixed .svc-bg (blurred ambient photo) from
   ever showing through the content column, which is exactly why it
   was rendering as flat neutral grey instead of the reference's warm
   dark-brown tones. Confirmed by sampling actual rendered pixel
   colors against the reference: ours was landing almost exactly on
   --bg-dark's own RGB values (the fallback, not a blend with any
   photo color at all). */
.svc-main{min-height:100%;color:#fff}

.svc-hero{
	position:relative;
	min-height:100svh;
	display:flex;flex-direction:column;
	overflow:hidden;
}
/* desktop: the reference's hero is NOT a full-viewport block — its
   content row has an intrinsic height (660.8px / 66.4rem heading column
   measured directly from the live page), so the next section (00.
   Foreword) naturally peeks into the bottom of the first screen on
   arrival instead of being hidden until you scroll. min-height:100svh
   stays for mobile, where there's no peeking section by design. */
@media (min-width:800px){
	.svc-hero{min-height:0}
}
/* user feedback: this image was scrolling away with the hero section —
   on the reference it stays fixed to the viewport the whole time, and
   only the dark overlay (.svc-scroll-gradient, which DOES scroll with
   content) passes over it, revealing progressively more darkness.
   Switched from position:absolute (inset:0 relative to .svc-hero, moves
   with scroll) to position:fixed, scoped to the same content-column
   screen bounds .svc-content-tint used to occupy — no longer relies on
   its own ::after tint since .svc-scroll-gradient (z-index -1, above
   this at -2) already starts at the same calibrated .87 darkness at
   scroll=0 and covers this image too now that it isn't buried under an
   opaque in-flow layer anymore. */
.svc-hero-bg{position:absolute;inset:0;z-index:-2}
@media (min-width:800px){
	.svc-hero-bg{
		position:fixed;
		top:.8rem;bottom:.8rem;left:calc(var(--nav-width) + .8rem);right:.8rem;
		border-radius:1.2rem;overflow:hidden;
		transition:left .4s var(--ease);
	}
	/* fixed positioning means this never moves on its own when the
	   content column shifts right for the Projects panel (that's a
	   real grid-column reflow, not something position:fixed tracks) —
	   shifted explicitly to match, or the content ends up visually
	   detached from its own background. */
	body.projects-panel-open .svc-hero-bg{left:calc(var(--nav-width) + 40rem + .8rem)}
}
.svc-hero-bg img,.svc-hero-bg video{width:100%;height:100%;object-fit:cover}
.svc-hero-header{
	position:relative;z-index:1;
	display:flex;align-items:center;justify-content:space-between;
	height:5.6rem;
	padding:0 2.4rem;
	border-bottom:1px solid rgba(255,255,255,.1);
	color:#fff;
}
/* margin, not padding — confirmed against the reference's own header:
   the divider LINE itself (border-bottom) sits inset from the content
   column's edge, not just the icon/text inside it. Padding only moves
   the content, leaving the border spanning the full box; margin moves
   the whole box (border included) to match. The hero image behind it
   stays full-bleed with no inset either way. */
@media (min-width:800px){.svc-hero-header{margin-left:5.6rem;margin-right:2.4rem;padding:0}}
/* positioned independently of the header, 16px from the content
   column's left edge and vertically centered in the 56px header row —
   confirmed via the reference's own icon rect (x:144 vs content
   column's x:128, y:24 inside an 8px-margin shell) */
.svc-crumb-icon{
	position:absolute;top:1.6rem;left:1.6rem;z-index:2;
	display:none;
	width:2.4rem;height:2.4rem;
	color:rgba(255,255,255,.95);
}
@media (min-width:800px){.svc-crumb-icon{display:flex}}
.svc-crumb-icon svg{width:100%;height:100%}
.svc-hero-header h1{font-size:2rem;font-weight:500;color:rgba(255,255,255,.95)}
.svc-hire-btn{
	display:flex;align-items:center;gap:.8rem;
	font-size:1.4rem;font-weight:600;color:rgba(255,255,255,.95);
}
@media (min-width:800px){.svc-hire-btn{font-size:1.6rem}}
.svc-hire-plus{
	display:grid;place-items:center;
	width:1.8rem;height:2.8rem;border-radius:.4rem;
	background:rgba(255,255,255,.1);
}

.svc-hero-content{
	position:relative;z-index:1;flex:1;
	display:grid;align-content:center;gap:2.4rem;
	padding:4rem 2.4rem;
}
/* exact values measured off the live reference (grid-cols-[66.4rem_auto],
   gap-24, row height 660.8px = 66rem) rather than guessed proportions */
@media (min-width:800px){
	.svc-hero-content{
		flex:none;
		height:83.6rem;
		padding:0 2.4rem 0 5.6rem;
		grid-template-columns:66.4rem auto;align-items:center;gap:2.4rem;
	}
	/* heading : marquee width ratio goes from 1:1 to 1:2 once the
	   Projects panel narrows the content column, giving the marquee
	   proportionally more of the remaining space instead of both
	   columns shrinking by the same amount */
	body.projects-panel-open .svc-hero-content{grid-template-columns:1fr 2fr}
}
/* every rule below only changes anything while the Projects panel is
   open — the base/closed-state rules stay exactly as originally
   authored, untouched, so nothing about the page's normal layout
   shifts just because these exist. */
@media (min-width:800px){
	/* stacked into a single column instead of squeezing two text blocks
	   side by side into an already-narrow third of the row — asked for
	   explicitly: vertical rows here rather than forcing 3 columns.
	   (shrink-safety for these tracks — min-width:0 via minmax(0,..) —
	   now lives on the base rules themselves, since the same overflow
	   happens at any narrow-enough viewport, panel open or not; only
	   this stacking behavior is actually panel-specific.) */
	body.projects-panel-open .svc-block-wrap{grid-template-columns:1fr}
	body.projects-panel-open .svc-block-wrap > .svc-block:only-child{grid-column:1/-1}
}
.svc-hero-content h2{font-size:2.4rem;font-weight:500;line-height:1.1;max-width:32rem;color:rgba(255,255,255,.95)}
@media (min-width:800px){.svc-hero-content h2{font-size:3.2rem}}

/* two-column testimonial ticker — confirmed on the reference these
   are plain text (no card background/radius/padding at all, that was
   an invented embellishment in the first pass), 48px between items,
   24px round avatars only on the first item of each repeating group */
.svc-marquee{
	display:none;
	height:100%;
	gap:2.4rem;
	overflow:hidden;
	-webkit-mask-image:linear-gradient(180deg,transparent,#000 25%,#000 75%,transparent);
	mask-image:linear-gradient(180deg,transparent,#000 25%,#000 75%,transparent);
}
@media (min-width:800px){.svc-marquee{display:flex}}
.svc-marquee-col{flex:1;overflow:hidden}
.svc-marquee-track{
	display:flex;flex-direction:column;gap:4.8rem;
	animation:svc-marquee-up 16s linear infinite;
}
.svc-marquee-track-rev{animation-name:svc-marquee-down}
@keyframes svc-marquee-up{from{transform:translateY(0)}to{transform:translateY(-50%)}}
@keyframes svc-marquee-down{from{transform:translateY(-50%)}to{transform:translateY(0)}}
.svc-testi{display:flex;flex-direction:column;gap:.8rem}
.svc-testi-with-avatar{flex-direction:row;align-items:flex-start;gap:.4rem}
.svc-testi-with-avatar img{width:2.4rem;height:2.4rem;border-radius:999px;object-fit:cover;flex-shrink:0}
.svc-testi-text p{font-size:1.4rem;line-height:1.4;color:rgba(255,255,255,.6)}
@media (min-width:800px){.svc-testi-text p{font-size:2.6rem;line-height:1.15}}
.svc-testi-text span{display:block;margin-top:.8rem;font-size:1.4rem;color:rgba(255,255,255,.95)}
.svc-testi-text span b{font-weight:600}

/* ===== TOC + accordion sections ===== */
.svc-toc-section{
	padding:4rem 2.4rem 8rem;
}
@media (min-width:800px){
	.svc-toc-section{
		padding-left:5.6rem;padding-right:2.4rem;padding-top:2.4rem;
		margin-top:0;
		border-top:1px solid rgba(255,255,255,.1);
		display:grid;grid-template-columns:28rem minmax(0,1fr);gap:3.2rem;
	}
}
.svc-toc{display:none}
@media (min-width:800px){.svc-toc{display:block}}
.svc-toc ul{position:sticky;top:4rem;display:flex;flex-direction:column;gap:1.2rem}
.svc-toc a{
	display:flex;gap:.8rem;
	font-size:1.6rem;font-weight:500;color:rgba(255,255,255,.25);
	transition:color .2s ease-in-out;
}
.svc-toc a:hover{color:rgba(255,255,255,.6)}
.svc-toc a.is-active{color:rgba(255,255,255,.95)}
.svc-toc a span{width:2em;flex-shrink:0}

.svc-sections{display:flex;flex-direction:column}
/* scroll-margin-top gives every TOC jump the same breathing room above
   its heading — Foreword specifically has no padding-top of its own
   (first-child override below), so without this its anchor landed
   flush at the very top of the viewport with zero gap, unlike every
   other section which gets that gap for free from its own padding */
.svc-section{border-top:1px solid rgba(255,255,255,.1);padding:2.4rem 0;scroll-margin-top:2.4rem}
.svc-section:first-child{border-top:0;padding-top:0}
.svc-section-head{
	display:flex;align-items:center;gap:1.2rem;width:100%;text-align:left;
}
.svc-section-num{font-size:2rem;font-weight:600;color:rgba(255,255,255,.95);width:4rem;flex-shrink:0}
@media (min-width:800px){.svc-section-num{font-size:3.2rem}}
.svc-section-head h2{font-size:2rem;font-weight:600;flex:1;color:rgba(255,255,255,.95)}
@media (min-width:800px){.svc-section-head h2{font-size:3.2rem}}
.svc-section-toggle{
	display:grid;place-items:center;width:2rem;height:2rem;
	font-size:1.8rem;color:rgba(255,255,255,.6);flex-shrink:0;
}
@media (min-width:800px){.svc-section-toggle{display:none}}

.svc-section-body{padding-top:1.2rem}
.svc-section-grid{display:flex;flex-direction:column;gap:2.4rem}
@media (min-width:800px){
	.svc-section-grid{display:grid;grid-template-columns:32rem minmax(0,1fr);gap:3.2rem;align-items:start}
}
.svc-block h3{font-size:1.8rem;font-weight:600;margin-bottom:1.2rem;color:rgba(255,255,255,.95)}
.svc-block p{font-size:1.5rem;line-height:1.5;color:rgba(255,255,255,.6);margin-bottom:1.2rem}
.svc-block p:last-child{margin-bottom:0}
.svc-block p strong{color:rgba(255,255,255,.95);font-weight:600}
.svc-block ul{margin:0 0 1.2rem;padding-left:1.8rem;list-style:disc}
.svc-block ul li{font-size:1.5rem;line-height:1.5;color:rgba(255,255,255,.6);margin-bottom:.4rem}
.svc-block-wrap{display:flex;flex-direction:column;gap:2.4rem}
@media (min-width:800px){
	.svc-block-wrap{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:2.4rem}
}
.svc-block-pad{padding-top:0}
@media (min-width:800px){.svc-block-pad{padding-top:6.4rem}}

/* floating rounded-card shell — confirmed on the reference's own
   services page (<main> gets padding:8px, the inner page wrapper gets
   border-radius:12px;overflow:hidden), which is what reveals the dark
   ambient gradient in a visible margin around the whole card. Scoped
   to this page only (.svc-page) rather than applied site-wide, since
   the homepage's own full-bleed nav behavior was already deliberately
   matched to the reference earlier and shouldn't change.

   Two corrections from the first pass:
   1) the gradient was set as body{background}, which stretches across
      the FULL scrollable document height — over a ~5000px page the
      48,52,54 -> 36,39,40 transition is so gradual it reads as flat
      grey. The reference pins this to a position:fixed, one-viewport
      layer instead, so the gradient is always visible within a single
      screen's height. Replicated here with .svc-bg.
   2) the sidebar on the reference is a REAL grid column with its own
      opaque background (reserving layout space), not an overlay with
      the hero image running full-bleed underneath it — confirmed the
      hero image sits only in the second grid column. .svc-shell is
      now display:grid with the nav as a sticky first column instead
      of position:fixed/absolute. */
/* the reference's actual page-level background isn't the flat
   #303436->#242728 gradient — that's a fallback layer sitting BEHIND
   a second, visible one: the same hero photo, heavily blurred, used
   as a warm ambient wash (confirmed by decoding the page's own
   base64 LQIP data URI — olive/brown tones matching the hero image,
   not grey). Layered here as the visible background with the flat
   gradient as a fallback underneath. */
.svc-bg{
	position:fixed;inset:0;z-index:-3;
	background:var(--bg-dark);
}
/* the margin/nav area's visible color comes from the ambient photo
   itself (opaque, covers --bg-dark completely), not the CSS variable —
   darkening that variable alone did nothing visible here. This overlay
   sits directly on the photo instead. */
.svc-bg::after{
	content:'';position:absolute;inset:0;
	background:rgba(0,0,0,.12);
}
/* no CSS filter — confirmed the reference applies none either
   (filter:none on its own equivalent image); it's already soft purely
   from stretching a tiny 48x64 source to full screen, and an extra
   blur() on top of that was washing out the darker tones */
.svc-bg img,.svc-bg video{width:100%;height:100%;object-fit:cover}

/* the content-column darkening overlay, confirmed against the reference's
   own DOM as a layer sitting over the ambient photo (._bg::after +
   a separate fixed ._tile). User feedback: it should span the FULL
   scrollable content height (not just one viewport) so it visibly gets
   darker as you scroll deeper into the page, rather than staying static.
   Implemented as a normal descendant of .svc-main (inside .svc-scroll-inner,
   which has no explicit height so it naturally grows to the full content
   height) instead of position:fixed — so it scrolls WITH the content while
   the ambient photo (.svc-bg) stays pinned behind it. Top value (.87
   effective) matches what was already pixel-verified against the live
   reference at scroll=0 (ours vs reference: (21,15,13) vs (23,16,14),
   (25,21,17) vs (27,22,18), (21,17,13) vs (23,18,15)); it deepens toward
   the bottom of the page for the scroll-darkening effect.
   Clipping: .svc-scroll-inner has no border-radius of its own — it relies
   on .svc-main's own overflow-x:hidden/overflow-y:auto + border-radius
   (1.2rem 0 0 1.2rem) to clip its descendants, so this gradient's corners
   round in sync with the nav seam instead of showing sharp underneath it
   (the old position:fixed div sat outside .svc-main and was never clipped
   by its radius — that's what was missing). */
/* flow-root so the first child's margin-top (.svc-crumb-icon's 1.6rem,
   on proj-page) can't collapse through and push this element's own box
   down — it was shifting .svc-scroll-gradient's top:0 start 16px below
   .svc-main's actual top edge, leaving an un-overlaid strip at the very
   top of the content column. */
.svc-scroll-inner{position:relative;display:flow-root}
.svc-scroll-gradient{display:none}
@media (min-width:800px){
	/* reference confirmed: by the time you've scrolled to where "00.
	   Foreword" begins, the overlay has gone fully solid (image no longer
	   visible at all) — not a gradual fade spread across the whole page.
	   #foreword sits at ~12.5% of the total scroll height (1040px of
	   8301px), so the gradient reaches full opacity there and stays
	   solid for the rest of the page. */
	/* --grad-end is the % down the page where the overlay reaches full
	   solid darkness — set on .svc-main by JS as the user scrolls, so
	   the transition point sits lower at rest (more of the fixed photo
	   stays visible) and migrates upward the deeper you scroll. Default
	   (no JS / before first scroll tick) sits lower than the old fixed
	   18%. */
	.svc-scroll-gradient{
		display:block;
		position:absolute;top:0;left:0;right:0;height:100%;z-index:-1;
		pointer-events:none;
		background:linear-gradient(to bottom,rgba(0,0,0,.7) 0%,rgba(26,18,13,1) var(--grad-end,30%),rgba(26,18,13,1) 100%);
	}
}

/* the nav is transparent on this page (confirmed: background-color
   rgba(0,0,0,0) on the reference's own sidebar), letting the warm
   blurred ambient background show through behind it — on every other
   page .nav is opaque white, so this is scoped to .svc-page only */
body.svc-page .nav{background:transparent}
body.svc-page .nav-top-name,
body.svc-page .nav-item,
body.svc-page .lang,
body.svc-page .terms{color:rgba(255,255,255,.95)}
body.svc-page .nav-top-tagline,
body.svc-page .nav-foot{color:rgba(255,255,255,.6)}
body.svc-page .nav-icon-bg{background:rgba(255,255,255,.15)}
body.svc-page .svc-share,
body.svc-page .svc-copy{color:rgba(255,255,255,.95)}

/* Projects panel opened from the services page: dark/warm theme instead
   of the homepage's white card — confirmed against the reference, which
   keeps the panel in the same dark palette as the rest of the services
   page rather than switching to light. Local --fg-* overrides cascade
   to every descendant (tab labels, card titles/categories, close/all
   icons) that already reads those variables, instead of re-touching
   each rule individually. */
body.svc-page .projects-panel-bg{background:rgba(26,18,13,.4)}
body.svc-page .projects-panel{
	--fg-default:rgba(255,255,255,.95);
	--fg-mute:rgba(255,255,255,.6);
	--fg-faint:rgba(255,255,255,.1);
}
body.svc-page .projects-panel-tab.is-active{color:#141400}
body.svc-page .projects-panel-card-img:hover::before,
body.proj-page .proj-coll-card-img:hover::before{opacity:.2}
/* the inner scroll container's own .8rem top inset left a gap above
   the header even after the header's own box pulled back to the inner
   box's own edge — removing just the top inset closes that gap, flush
   with the panel's rounded top edge, while left/right/bottom keep
   their .8rem card margin */
body.svc-page .projects-panel-inner{top:0;left:0;right:1rem}
/* was scoped to .projects-panel-sticky, the old shared head+tabs lid
   — retargeted to .projects-panel-head now that it's the standalone
   non-scrolling row carrying this same "lid" role (see .projects-
   panel-inner's own comment for why that split happened). padding
   kept at its previously-tuned 1.6rem/2rem for dark pages rather than
   inheriting the light-mode base rule's 2rem/2rem/.8rem. */
body.svc-page .projects-panel-head{
	padding:1.6rem 2rem;
	background:rgba(26,18,13,.2);
	backdrop-filter:blur(1.6rem);-webkit-backdrop-filter:blur(1.6rem);
}
/* the visible card (.projects-panel-bg) was vertically inset .8rem on
   both top and bottom within the panel's own box — shorter than
   .svc-main's card, which spans its box edge-to-edge since its
   rounding comes straight from its own border-radius+overflow, not an
   inset child. Matched here so both cards reach the same full height. */
/* left:0, not .8rem — matches nav and .svc-main, both flush with their
   own grid track's left edge (no inset card margin on that side) on
   this page, so the panel's starting point lines up exactly with
   where content itself starts once the panel closes. right:1rem left
   as the visible gap before content, matching the ~10px nav-to-content
   gap in the closed state. */
body.svc-page .projects-panel-bg{top:0;bottom:0;left:0;right:1rem}
.svc-shell{
	position:relative;
	margin:.8rem;
	border-radius:1.2rem;
	overflow:hidden;
}
@media (min-width:800px){
	/* fixed to one viewport, not grown-to-content — the reference
	   keeps the whole card (both columns) pinned at 100vh and scrolls
	   the right column's own content INSIDE it, rather than the page
	   itself scrolling past the card. */
	.svc-shell{display:grid;grid-template-columns:var(--nav-width) 1fr;height:calc(100vh - 1.6rem)}
	/* a real grid column, not fixed/absolute — reserves actual layout
	   width so the hero image (in the second column) can never run
	   underneath it. */
	/* position:fixed, flush at the true viewport edge — matching the
	   homepage's own nav exactly, rather than sitting inset by the
	   shell's .8rem margin like a grid-column child would. The grid's
	   first track still reserves --nav-width of space so content lines
	   up correctly; nav just renders independently on top of it. */
	body.svc-page .nav{position:fixed;top:0;left:0;bottom:0;height:auto;grid-column:1}
	/* no margin-left compensation — nav's own width (--nav-visual-width)
	   is already identical to the homepage, so letting nav-items center
	   naturally within it keeps the gap from nav's OWN left edge to the
	   icons consistent between pages. Absolute screen position still
	   differs by the shell's .8rem outer margin, which is the correct,
	   intended difference — not something to paper over. */
	/* visibility:hidden, not display:none — .nav is a space-between flex
	   column, so removing these from flow entirely would let nav-items
	   drift to fill their spot instead of staying put. Only while the
	   Projects panel is open; otherwise they stay visible as normal. */
	body.projects-panel-open.svc-page .nav-top,
	body.projects-panel-open.svc-page .nav-foot{visibility:hidden}
	/* a REAL third grid track between nav and content, not an absolutely
	   positioned overlay riding on top of a content column pushed over
	   by a matching margin — confirmed against the reference, which
	   genuinely reflows to nav | panel | content. The middle track is
	   0 width when closed (panel clipped via overflow:hidden) and
	   animates open to the panel's own 40rem width. */
	body.svc-page .svc-shell{grid-template-columns:var(--nav-width) 0 1fr;transition:grid-template-columns .4s var(--ease)}
	body.svc-page.projects-panel-open .svc-shell{grid-template-columns:var(--nav-width) 40rem 1fr}
	body.svc-page .projects-panel{
		position:relative;top:auto;bottom:auto;left:auto;
		grid-column:2;width:auto;max-width:none;height:100%;overflow:hidden;
	}
	/* .svc-main is the content column's own internal scroll container —
	   everything below the hero (TOC + all 6 sections) scrolls inside
	   this box instead of the document itself scrolling */
	/* the content column gets its own rounded top-left/bottom-left
	   corners at the nav seam, confirmed directly against the
	   reference's own screenshot — not just the outer shell's 4 outer
	   corners */
	.svc-main{height:100%;overflow-y:auto;overflow-x:hidden;overscroll-behavior-y:contain;scrollbar-width:none;border-radius:1.2rem}
	.svc-main::-webkit-scrollbar{display:none}
}

/* per-section demo video (sections 01-05 only) — confirmed on the
   reference as a sibling of BOTH text blocks in the wrap column, not
   nested inside the first one (which squeezed it to half-width the
   first time around). grid-column:1/-1 spans the full 2-col wrap, and
   square (1/1) not 4/3 — confirmed via the reference's own video
   element rendering at 652x652, exactly matching its 652px-wide
   column's full width, not half of it. */
.svc-video{
	grid-column:1/-1;
	width:100%;aspect-ratio:1/1;object-fit:cover;
	border-radius:1.2rem;margin-top:.8rem;
}

/* "Get started" — 3 yellow CTA cards per section (01-05), linking to
   the hire page. Confirmed yellow (not themed to the dark page) is
   the reference's own deliberate accent color here. */
.svc-getstarted{
	display:flex;flex-direction:column;gap:1.2rem;
	padding-top:3.2rem;margin-top:3.2rem;
	border-top:1px solid rgba(255,255,255,.1);
}
@media (min-width:800px){
	.svc-getstarted{
		display:grid;grid-template-columns:32rem minmax(0,1fr);gap:3.2rem;align-items:start;
		padding-top:2.4rem;margin-top:0;border-top:0;
	}
}
.svc-getstarted-label{font-size:1.5rem;font-weight:500;color:rgba(255,255,255,.6)}
.svc-getstarted-cards{display:flex;flex-direction:column;gap:1.2rem}
/* auto-fit with a real minimum, not a hardcoded repeat(3,..) — at the
   1728px viewport this was designed against, 3 columns fit comfortably
   on their own; at a real laptop width (1440/1366/1280px) three fixed
   1fr tracks squeeze each card below a readable size and its text
   overflows past .svc-main's own edge. auto-fit still renders 3 equal
   columns whenever there's room for them, same look, but drops to 2
   (or 1) once there genuinely isn't, at ANY viewport, not just when
   the Projects panel is open. */
@media (min-width:800px){.svc-getstarted-cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(18rem,1fr));gap:1.6rem}}
.svc-getstarted-card{
	display:flex;flex-direction:column;justify-content:space-between;gap:2.4rem;
	min-height:14rem;padding:1.6rem;border-radius:1.2rem;
	background:rgb(255,248,81);color:#141400;
	transition:transform .3s var(--ease);
}
.svc-getstarted-card:hover{transform:translateY(-.4rem)}
.svc-getstarted-card p{font-size:1.6rem;font-weight:600;line-height:1.2}
.svc-getstarted-card-foot{display:flex;align-items:center;justify-content:space-between;gap:.8rem}
.svc-getstarted-card-foot span{font-size:1.3rem;font-weight:500}
.svc-getstarted-card-foot svg{width:1.6rem;height:1.6rem}

/* "Capabilities" block — appears after Get Started on 4 of the 5
   sections (not on Design System, confirmed absent there on the
   reference). Exact values pulled from the live reference's computed
   styles rather than estimated. */
.svc-capabilities{border-top:1px solid rgba(255,255,255,.1);padding-top:2rem;margin-top:2.4rem}
@media (min-width:800px){
	.svc-capabilities{display:grid;grid-template-columns:32rem minmax(0,82.4rem);gap:2.4rem;padding-top:0}
}
.svc-capabilities-label{padding-top:2rem;font-size:1.4rem;color:rgba(255,255,255,.95)}
.svc-capabilities-content{max-width:65.2rem;font-size:1.4rem;color:rgba(255,255,255,.95)}
.svc-cap-groups{display:grid;gap:3.2rem 2.4rem;padding:2rem 0}
@media (min-width:800px){.svc-cap-groups{grid-template-columns:minmax(0,1fr) minmax(0,1fr)}}
.svc-cap-group{display:flex;flex-direction:column;gap:.8rem}
.svc-cap-num{font-size:1.1rem;font-weight:600;color:rgba(255,255,255,.95)}
.svc-cap-title{font-size:1.4rem;font-weight:600;color:rgba(255,255,255,.95)}
.svc-cap-list{display:flex;flex-direction:column;gap:.8rem;font-size:1.4rem;color:rgba(255,255,255,.6)}

/* ===== PROJECTS PAGE ===== */
/* the category rows reuse .projects-panel-card, whose title/category
   text reads color:var(--fg-default)/var(--fg-mute) — those are only
   overridden to light values inside the actual slide-out panel
   (body.svc-page .projects-panel{...}), so outside it here they fell
   back to the light-theme (dark) defaults, nearly invisible against
   this page's own dark background. */
body.proj-page{
	--fg-default:rgba(255,255,255,.95);
	--fg-mute:rgba(255,255,255,.6);
	--fg-faint:rgba(255,255,255,.1);
}
.proj-main{min-height:100%;color:#fff}
/* this page's hero isn't a bright warm photo like Services — it's
   already a muted flat grey with a softly-visible illustration, so the
   heavy dark overlay calibrated for that photo over-darkens it here.
   Much lighter touch, just enough to keep header text legible. */
@media (min-width:800px){
	/* same stops/opacities as the base (Services) rule above — only the
	   solid color differs: darkest tone of this page's own ambient pink
	   background photo (rgb(20,8,14)) instead of Services' dark brown. */
	body.proj-page .svc-scroll-gradient{
		background:linear-gradient(to bottom,rgba(0,0,0,.7) 0%,rgba(20,8,14,1) var(--grad-end,30%),rgba(20,8,14,1) 100%);
	}
}

.proj-hero{
	position:relative;z-index:1;
	padding:4rem 2.4rem;
}
@media (min-width:800px){
	.proj-hero{padding:0 2.4rem 4rem 5.6rem}
}
/* the crumb icon used to be position:absolute inside .proj-hero
   (position:relative), but now sits as a sibling of the sticky header
   at the top of .svc-scroll-inner — sticky instead of absolute so it
   stays docked next to the header as it scrolls, instead of just
   scrolling away with the rest of the page content */
body.proj-page .svc-crumb-icon{
	position:sticky;top:1.6rem;left:auto;
	margin:1.6rem 0 -4rem 1.6rem;z-index:4;
}
.proj-hero-header{
	display:flex;flex-direction:column;gap:1.6rem;
	height:5.6rem;
	padding:1.6rem 2.4rem;
	position:sticky;top:0;z-index:3;
	border-radius:1.2rem 1.2rem 0 0;
	/* --header-progress (0-1) is set continuously by JS as a function of
	   scroll position, not toggled as a class past a fixed threshold —
	   so the header solidifies gradually in step with the scroll instead
	   of snapping. Same solid color as .svc-scroll-gradient's end stop. */
	background:rgba(20,8,14,var(--header-progress,0));
	border-bottom:1px solid rgba(255,255,255,calc(var(--header-progress,0) * .1));
	backdrop-filter:blur(calc(var(--header-progress,0) * 1.6rem));
	-webkit-backdrop-filter:blur(calc(var(--header-progress,0) * 1.6rem));
	transition:background .1s linear,border-color .1s linear,backdrop-filter .1s linear;
}
@media (min-width:800px){
	.proj-hero-header{
		flex-direction:row;align-items:center;justify-content:space-between;gap:2.4rem;
		height:5.6rem;padding:0 2.4rem 0 5.6rem;
	}
}
.proj-hero-header h1{font-size:2rem;font-weight:500;color:rgba(255,255,255,.95);flex-shrink:0}
.proj-hero-desc{display:none}
@media (min-width:800px){
	.proj-hero-desc{display:flex;gap:2.4rem;flex:1;max-width:60rem}
}
.proj-hero-desc p{font-size:1.4rem;line-height:1.4;color:rgba(255,255,255,.6)}
.proj-hero-update{display:none;flex-direction:column;text-align:right;flex-shrink:0}
@media (min-width:800px){.proj-hero-update{display:flex}}
.proj-hero-update-label{font-size:1.2rem;color:rgba(255,255,255,.5)}
.proj-hero-update-date{font-size:1.4rem;color:rgba(255,255,255,.8)}

.proj-hero-content{padding:6rem 0 2rem;max-width:56rem}
@media (min-width:800px){.proj-hero-content{padding:9rem 0 4rem}}
.proj-hero-content h2{font-size:2rem;font-weight:500;line-height:1.3;color:rgba(255,255,255,.95)}
@media (min-width:800px){.proj-hero-content h2{font-size:2.8rem}}

/* label in its own left column, grid to the right — same pattern as
   .svc-toc-section on the services page (28rem label column) and the
   .proj-category rows below, not stacked as a row header above the
   grid like the first pass had it */
.proj-collections{
	position:relative;z-index:1;
	padding:2.4rem 2.4rem 0;
	border-top:1px solid rgba(255,255,255,.1);
}
@media (min-width:800px){
	.proj-collections{
		padding:2.4rem 2.4rem 0 5.6rem;
		display:grid;grid-template-columns:23rem minmax(0,1fr);gap:3.2rem;
	}
}
.proj-collections-head{margin-bottom:2rem}
@media (min-width:800px){.proj-collections-head{margin-bottom:0}}
.proj-collections-label{font-size:1.4rem;font-weight:500;color:rgba(255,255,255,.95)}
.proj-collections-sub{font-size:1.3rem;color:rgba(255,255,255,.5);margin-top:.2rem}

/* fixed-width tracks, not 1fr — confirmed against the reference, whose
   grid doesn't stretch to fill the content column's full width (5
   columns at a fixed 23.68rem each, leaving space unused on wide
   screens, same on ours now). Exact values measured directly: 4px
   column-gap, 16px row-gap. */
.proj-collections-grid{
	display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:1.6rem .4rem;
	padding-bottom:3.2rem;
}
@media (min-width:640px){.proj-collections-grid{grid-template-columns:repeat(3,minmax(0,1fr))}}
/* auto-fill, not a literal repeat(5,..) — the reference shows 5
   columns at a wide 1728px window, but that's a consequence of how
   many 23.68rem tracks fit at that specific width, not a hardcoded
   count. A real browser window narrower than that (or the Projects
   panel open) fits fewer, and auto-fill reflows to match instead of
   overflowing the content column. */
/* same track sizing as .proj-category-row's project cards (18rem min),
   not an independent fixed width — collection cards now match project
   card width exactly at any viewport, with height following along
   proportionally via the existing aspect-ratio:3/5 on the image box
   rather than a separately computed height value */
/* max-width caps auto-fill at 5 columns worth of space (5*18rem +
   4*1.6rem gaps) — without it, a wider real monitor than this was
   tested against just fits a 6th+ column instead of stopping at 5 */
/* reverted to the collection cards' own real measurements — 236.8px
   fixed width (not matched to project cards), 4px column-gap / 16px
   row-gap (not the uniform 16px fetched from the panel). Re-confirmed
   via a fresh direct re-measurement of the live reference. */
@media (min-width:1000px){.proj-collections-grid{grid-template-columns:repeat(auto-fill,22rem);gap:2rem;max-width:118rem}}
.proj-coll-card{display:flex;flex-direction:column;gap:1.2rem}
/* no overflow:hidden on this element itself — matches the pattern
   .projects-panel-card-img already uses (rounding/clipping lives on
   the <img> instead). An overflow:hidden parent clips its own
   ::before glow pseudo-element too, since that's what inset:-.8rem
   needs to extend past, so the hover glow was rendering at the right
   opacity but completely invisible, clipped before it ever showed. */
/* explicit fixed height instead of aspect-ratio — width now changes
   independently (grid track width) without scaling height along with
   it, per explicit request to grow width only */
.proj-coll-card-img{
	position:relative;height:33.333rem;
	background:#222;border-radius:1.6rem;
}
.proj-coll-card-img img{width:100%;height:100%;object-fit:cover;border-radius:1.6rem}
.proj-coll-watermark{
	position:absolute;top:1.2rem;left:0;right:0;
	font-size:.9rem;font-weight:700;line-height:1.3;text-align:center;text-transform:uppercase;
	color:rgba(255,255,255,.85);letter-spacing:.02em;
	text-shadow:0 1px 4px rgba(0,0,0,.4);
}
.proj-coll-title{
	position:absolute;left:1.6rem;right:1.6rem;top:58%;
	font-size:2rem;font-weight:500;color:#fff;line-height:1.15;
	text-shadow:0 1px 6px rgba(0,0,0,.3);
}
.proj-coll-caption{font-size:1.3rem;line-height:1.4;color:rgba(255,255,255,.6)}

/* same left-label-column pattern as .proj-collections and the services
   page's .svc-toc-section — the head sits in its own 28rem column
   instead of stacked above the row */
.proj-category{
	position:relative;z-index:1;
	padding:2.4rem 2.4rem;
	border-top:1px solid rgba(255,255,255,.1);
}
@media (min-width:800px){
	.proj-category{
		padding:2.4rem 2.4rem 2.4rem 5.6rem;
		display:grid;grid-template-columns:23rem minmax(0,1fr);gap:3.2rem;
		align-items:start;
	}
}
.proj-category-head{
	display:inline-flex;align-items:center;gap:.4rem;
	margin-bottom:2rem;color:rgba(255,255,255,.95);
}
@media (min-width:800px){.proj-category-head{margin-bottom:0}}
.proj-category-head h3{font-size:1.6rem;font-weight:500}
.proj-category-head svg{width:1.6rem;height:1.6rem}
/* .projects-panel-card-img's own padding:.8rem (needed for the actual
   slide-out panel's layout) was shrinking the VISIBLE photo to 204x204
   inside a 220x220 box, while collection cards have zero padding so
   their full box IS the photo — that's why project cards read smaller
   despite an identical outer box width. Scoped to just this row so
   neither Collections nor the real panel are touched. */
.proj-category-row .projects-panel-card-img{padding:0}
/* the base inset:0 on this glow exists specifically to compensate for
   .projects-panel-card-img's own padding:.8rem (avoids doubling the
   gap to 16px) — but padding is now 0 right above, in this context
   only, so that compensation is stale here and was leaving the glow
   flush with the edge instead of extending past it like collection
   cards'. Restored to the same -.8rem collection cards use, scoped so
   the actual panel (padding:.8rem still, unchanged) keeps its own
   correct inset:0. */
.proj-category-row .projects-panel-card-img::before{inset:-.8rem}
/* 1.6rem gap — matches the homepage's own project-card spacing
   (.project-img{margin:0 .8rem}, measured at 16px between cards) */
.proj-category-row{
	display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:1.6rem;
}
@media (min-width:640px){.proj-category-row{grid-template-columns:repeat(3,minmax(0,1fr))}}
/* 236.8px fixed, matching .proj-collections-grid exactly — freshly
   re-measured directly off the live reference's own category rows
   (not the earlier assumed 18rem, which came from nowhere real).
   Scoped to this grid container only — .projects-panel-card itself is
   untouched, so the actual slide-out panel's own 2-column grid keeps
   its separate, correct sizing. */
@media (min-width:1000px){.proj-category-row{grid-template-columns:repeat(auto-fill,22rem);gap:2rem;max-width:118rem}}

/* ===== PROJECT DETAIL PAGE (projects/chialove.html et al) ===== */
/* the outer column (crumb icon, share/copy — everything outside the
   inner scrollable content) reads as an overlay, not the sharp ambient
   photo on its own — matches the reference's own outer "._clip" frame,
   which carries a plain background-color class (bg-bg-moderate), not
   an image. Translucent (not opaque), so the shared fixed photo still
   shows through, just tinted. Scoped to this page only; other
   proj-pages' .svc-main still lets .svc-hero-bg show through plain. */
/* fully opaque — .svc-hero-bg/.svc-bg are real fixed photo images
   behind the whole page; any transparency or blur here still reads as
   "a photo," just softened, not a true flat overlay with zero image
   influence. Opaque is the only way to actually remove it from view. */
/* exact same values as body.svc-page .projects-panel-bg, not an
   approximation — rgba(26,18,13,.4) + backdrop-filter:blur(2rem).
   overflow:hidden (overriding the shared .svc-main overflow-y:auto,
   scoped to this page only) because .proj-detail-frame below becomes
   the actual scroll container instead. */
.proj-detail-main{padding:0;position:relative;overflow:hidden;background:rgba(26,18,13,.4);backdrop-filter:blur(2rem);-webkit-backdrop-filter:blur(2rem)}
/* darkest orange, not the shared body.proj-page rules' dark-pink
   rgb(20,8,14) — .proj-detail-header carries the shared .proj-hero-
   header class (same scroll-progress background mechanic), and
   .svc-scroll-gradient is shared with projects.html too, so both need
   a scoped override here rather than changing the shared rule (which
   stays dark-pink for projects.html, already approved as-is).
   rgb(20,8,2) — same near-black darkness as that dark-pink reference
   value, scaled proportionally from the nav gradient's own orange hue
   (109:45:10) instead of a pink one, not the medium-bright rgb(124,
   51,11) first tried (too light — "darkest" meant blackish, not just
   the darkest stop already sampled off the nav). */
/* disabled on this page (was a flat-gradient overlay layered ON TOP of
   .proj-detail-hero-bg's own --scrim, which independently ramps the
   hero image to solid color on its own schedule) — stacking both
   darkened the upper portion of the viewport (where this gradient's
   own 0%→30% fade hadn't reached its solid stop yet) more than the
   header or the area further down, producing a visible seam: header
   flat (20,8,2), area just below it measurably darker (10,4,1) at the
   same scroll position. The scrim plus the header's own --header-
   progress background already tell this page's whole darkening story;
   a second, independently-timed gradient on top just fights them. */
body.proj-detail-page .svc-scroll-gradient{background:none}
body.proj-detail-page .proj-hero-header{background:rgba(20,8,2,var(--header-progress,0))}
/* a genuinely fixed-size inset frame — like A4 paper laid on A3 — not
   a margin on the scrolling content itself (which only ever reveals
   the gap at the very top/bottom of a scroll, not consistently).
   This frame stays put at a fixed inset from .proj-detail-main's own
   edges regardless of scroll position; the actual scrolling happens
   inside it. Gap derived from .svc-shell's own .8rem margin to the
   true viewport edge: top/right/bottom = that .8rem ÷ 3 (0.2667rem),
   left = 0 (explicitly no left gap, confirmed). */
.proj-detail-frame{position:absolute;top:.5rem;right:.5rem;bottom:.5rem;left:6rem;overflow-y:auto;overflow-x:hidden;border-radius:1.2rem;transform:translateZ(0)}
.proj-detail-scroll-inner{margin:0}
/* .svc-bg/.svc-hero-bg no longer carry an <img> on this page — a flat
   CSS gradient instead of a photo, sampled directly from the
   reference's own nav sidebar screenshot: rendered top #7e3000, bottom
   #7a4028. Input values here are brightened ~12% from those (to
   #8f3700/#8b492d) to compensate for .svc-bg::after's existing
   rgba(0,0,0,.12) tint sitting on top of it, so the FINAL rendered
   color (after that overlay) lands on the sampled target exactly
   instead of reading darker than the reference. */
body.proj-detail-page .svc-bg,
body.proj-detail-page .svc-hero-bg{background:linear-gradient(180deg,#8f3700 0%,#7c330b 74.5%,#8b4a2d 100%)}
.proj-detail-header{padding:1.6rem 2.4rem}
/* the shared .proj-hero-header ties its border-bottom to the same
   --header-progress as the fill, so both are invisible at rest. Here
   the divider stays visible at a fixed, low opacity the whole time —
   only the fill itself solidifies on scroll. */
.proj-detail-header{border-bottom-color:rgba(255,255,255,.1)}
/* 2.4rem left, not the shared .proj-hero-header's 5.6rem — that larger
   value exists to clear the nav sidebar for headers that sit full-bleed
   outside an inset frame (projects.html's own header). This header
   lives inside .proj-detail-frame, which already supplies its own
   6rem left inset, so the extra 5.6rem was stacking on top of that and
   pushing the breadcrumb far past where it should start. */
@media (min-width:800px){.proj-detail-header{padding:0 2.4rem}}
.proj-detail-crumb{display:flex;align-items:center;gap:.6rem;font-size:1.4rem;color:rgba(255,255,255,.6)}
.proj-detail-crumb a{display:flex;align-items:center;gap:.4rem;color:rgba(255,255,255,.6);font-weight:600}
.proj-detail-crumb-arrow{transform:rotate(180deg);width:14px;height:14px}
.proj-detail-crumb-sep{color:rgba(255,255,255,.3)}
.proj-detail-tags{display:flex;align-items:center;gap:1.2rem;font-size:1.4rem;color:rgba(255,255,255,.6)}
.proj-detail-dot{width:.4rem;height:.4rem;border-radius:50%;background:currentColor}
.proj-detail-hire{display:flex;align-items:center;gap:.8rem;color:rgba(255,255,255,.95);font-weight:600}
.proj-detail-hire-plus{display:grid;place-items:center;width:1.8rem;height:1.8rem;border-radius:50%;background:rgba(255,255,255,.1);font-size:1.2rem}

/* hero: dedicated local photo again, not the shared fixed .svc-hero-bg
   — now that .proj-detail-main itself carries a translucent overlay
   background, that overlay would otherwise sit in front of the shared
   fixed photo for the hero too, double-tinting it inconsistently with
   the hero's own stronger scrim below. A local image stays independent
   of that outer-column treatment. */
/* height, not min-height — the reference's hero is exactly h-svh, one
   full viewport tall, not "at least" that tall. --frame-vh (set by JS
   on .proj-detail-frame, inherited down) is the frame's own real pixel
   height, standing in for svh since the frame is a smaller, inset
   "viewport" than the actual browser window. */
/* Ground truth re-captured via real wheel-scroll screenshots of the
   live reference at 50px steps (DOM computed-style reads were lying —
   duplicate/SSR text nodes and a canvas/mask-driven fade that never
   shows up as element.style.opacity). That capture proved title and
   intro paragraph are NORMAL in-flow content that scrolls 1:1 with the
   page — NOT pinned/centered/repositioned by JS. Each just has its own
   independent opacity fade window keyed to scrollTop (see main.js:
   HERO_FADE_* / INTRO_FADE_* constants), applied on top of ordinary
   document flow. .proj-detail-hero is now a plain flow section; the
   old grid-centering lives on .proj-detail-hero-stage so the title
   still opens vertically centered in the first screenful, same as
   before, while intro-text flows normally beneath it instead of being
   absolutely positioned and JS-animated into place. */
.proj-detail-hero{position:relative;z-index:1}
.proj-detail-hero-stage{height:var(--frame-vh,100vh);margin-top:-5.6rem;display:grid;place-items:center;padding:4rem 2.4rem;overflow:hidden}
@media (min-width:800px){.proj-detail-hero-stage{padding:4rem}}
/* position:fixed, pinned to .proj-detail-frame's own box (via the
   transform on it below) rather than scrolling away with .proj-detail-
   hero's own section — spans the frame's FULL height, starting from
   its true top edge behind the header too. z-index:-2, below
   .svc-scroll-gradient's -1 — since the frame's transform makes it
   this image's stacking context too, it directly compares z-index
   against the gradient within that SAME context and needs to be
   explicitly lower so the gradient still paints over it. */
/* position:sticky, not fixed — fixed breaks when .proj-detail-frame has
   a transform (needed elsewhere for other reasons): it stops being
   immune to scrolling and just acts like absolute relative to that
   frame, so it scrolled away 1:1 with the frame's own internal scroll
   instead of staying pinned (confirmed: its rect.top moved in exact
   lockstep with scrollTop). The reference's own equivalent layer never
   moves either (confirmed via the same wheel-scroll screenshots), so
   staying fully static here is correct, not just a technical fallback. */
/* a sibling of .proj-detail-hero now, not nested inside it — nested,
   its only possible containing block (.proj-detail-hero's own box) was
   exactly the same height as this element itself, leaving zero slack
   for sticky to ever actually engage (it just scrolled normally, 1:1,
   same bug fixed-position had). As a direct child of .svc-scroll-inner
   instead — which is thousands of px tall, covering the whole page —
   there's effectively unlimited slack, so it stays genuinely pinned
   for as long as it needs to (through the gallery handoff and beyond;
   later opaque sections simply paint over it by z-index, no need to
   explicitly release or hide it). margin-top cancels the header's own
   height the same way .proj-detail-hero-stage's own margin does, since
   this is now ALSO a direct post-header sibling needing the same fix. */
/* giving the sticky child slack (a parent taller than itself) by
   making THIS wrapper genuinely tall pushed every later sibling down
   by that same height, since the wrapper still reserves real layout
   space for it — that's what sent the headline ~974px below the fold.
   The negative margin-bottom cancels that reserved space back out for
   anything AFTER this wrapper, while the wrapper's own interior still
   has the full height for the sticky child's slack calculation. */
/* 3000px, not 20000 — only needs enough slack to keep .proj-detail-
   hero-bg sticky-pinned until the gallery's opaque media fully covers
   it (that handoff finishes well under 1000px of scroll). 20000 was a
   lazy round number that caused a real bug: the negative margin-bottom
   cancels its reserved LAYOUT space for later siblings, but the
   scroll container's own scrollHeight is still the farthest extent of
   any box, so when total real page content (~15000px here) is LESS
   than the wrapper's own height, the wrapper's height wins — leaving
   ~5000px of dead scrollable space after the real content ends. */
.proj-detail-hero-bg-wrap{height:3000px;margin-bottom:-3000px;position:relative}
.proj-detail-hero-bg{position:sticky;top:0;left:0;width:100%;height:var(--frame-vh,100vh);margin-top:-5.6rem;z-index:-2}
.proj-detail-hero-bg img{width:100%;height:100%;object-fit:cover}
/* dedicated scrim, not just the shared .svc-scroll-gradient — measured
   directly against the reference: at the same point it reads roughly
   (38,16,0) where ours came out (70,62,55) through the shared gradient
   alone (its .7 opacity 0%-stop, calibrated for the lighter Services
   hero photo, isn't strong enough here). The warm, saturated dark tone
   (not flat black) also can't come from a plain black overlay — a
   black overlay only scales down existing hue ratios, it can't shift
   them this far toward red. Starts at opacity:0 — the image is fully
   visible at rest — and is driven to 1 (solid near-black) by main.js
   as the page scrolls; it used to be a constant .78 the whole time,
   which is why the reference's "goes solid, then the gallery shows
   through" beat never actually happened for us. */
.proj-detail-hero-bg::after{content:'';position:absolute;inset:0;background:rgba(20,8,2,1);opacity:var(--scrim,0)}
.proj-detail-hero-content{position:relative;z-index:1}
.proj-detail-hero-content{display:flex;flex-direction:column;align-items:center;gap:2rem;text-align:center;max-width:98rem}
.proj-detail-hero-badge{display:flex;align-items:center;gap:.8rem;font-size:1.6rem;color:rgba(255,255,255,.7);font-weight:600}
.proj-detail-hero-badge img{width:3.2rem;height:3.2rem;border-radius:.8rem;object-fit:cover}
.proj-detail-hero-headline{font-size:2.4rem;font-weight:600;line-height:1.15;color:rgba(255,255,255,.97)}
/* 5.6rem + line-height:1, not 4.4rem/1.15 — matches the reference
   exactly (56px/56px, measured directly). Combined with the same
   98rem max-width we already had, this is what makes it wrap to two
   lines the way theirs does, not just a size bump on its own. */
@media (min-width:800px){.proj-detail-hero-headline{font-size:5.6rem;line-height:1}}

/* intro rich-text: normal flow block directly after the hero's centered
   stage, NOT absolutely positioned or JS-repositioned — the wheel-
   scroll ground truth showed it moves in exact 1:1 lockstep with
   scrollTop the entire time it's on screen, which is just what plain
   document flow already does for free. margin-top pulls it up out of
   the stage's trailing space so its resting gap below the headline
   matches the reference's own measured ~485px title-to-paragraph gap
   (tuned against screenshots, not guessed). Only opacity is animated
   (main.js), for the fade-in/hold/fade-out triple confirmed in the
   reference captures — no position or transform animation needed. */
/* 1.6rem/1.15 + left-aligned — matches the reference exactly (measured
   directly: 16px font, 18.4px line-height, text-align:start), not the
   larger centered treatment first guessed at. max-width bumped to
   88.8rem to match its own 888px reference value too. */
.proj-detail-intro-text{position:relative;z-index:1;margin:-28rem auto 0;padding:0 2.4rem;max-width:88.8rem;display:flex;flex-direction:column;gap:1.6rem;font-size:1.6rem;line-height:1.15;color:rgba(255,255,255,.85);text-align:left;font-weight:500}

/* gallery: scroll-pinned slide stack, matching the reference's own
   mechanic (confirmed via DOM inspection: each slide is a tall row
   whose media stays fixed on screen for part of that row's scroll
   range, then the next row's media covers it) — but built with
   position:sticky + negative margin-bottom instead of the reference's
   position:fixed, since .svc-main is its own internal scroll container
   and a fixed descendant would escape its rounded clipping and overlap
   the nav. Sticky achieves the same pin-then-cover visual within a
   scrolling ancestor, which fixed cannot do here.
   Row height 200vh + sticky media 100vh + margin-bottom:-100vh on every
   row but the last: a row's own dwell window (the range where its
   media is fully pinned, filling the screen) runs from 0 to row-height
   minus media-height = 100vh into the row. The next row's natural
   (pre-sticky) top sits at row-height minus the pull-up = 200vh-100vh
   = 100vh too — exactly where the current row's dwell ends, so the
   handoff is a clean hard cut with no premature overlap and no gap.
   (An earlier 150vh/-100vh pairing left only a 50vh dwell and let the
   next row's un-stuck box encroach on the current one's pinned area
   from the start — three slides visibly fighting for the same screen
   instead of one clean cover at a time.) */
.proj-detail-gallery{position:relative;z-index:1;display:grid;grid-template-columns:1fr auto;gap:0 1.6rem;padding:.8rem 1.6rem .8rem 0}
.proj-gallery-stack{position:relative;grid-column:1}
.proj-gallery-row{height:200vh}
.proj-gallery-row:not(.proj-gallery-row-last){margin-bottom:-100vh}
/* contain, not cover — confirmed directly against the reference: its
   media box ("._insight-deck-row-media") spans the full sticky area at
   ~fixed-viewport size; the <img> inside is object-fit:cover (re-
   measured directly against the live reference — an earlier pass had
   recorded object-fit:contain here, which was wrong), so it fills its
   inset padded box completely edge-to-edge, cropping as needed, rather
   than letterboxing at its own aspect ratio. border-radius:4px matches
   the reference's own measured value (its rounded-4 utility is a
   literal 4px here, not the usual 1rem a "rounded-4" name might
   suggest elsewhere in a Tailwind scale). */
/* padding + box-sizing:border-box (not display:grid+place-items) — a
   grid item's auto-sized implicit row has no definite height for a
   percentage-height child to resolve against, so neither
   max-height:100% nor height:100% reliably capped the media's own
   height through a grid; it kept overflowing past the 100vh box no
   matter which property was used. A plain block (or flex, for split
   pairs) parent with a definite height — which padding+border-box
   still gives here — resolves child percentage-heights correctly,
   no grid-track ambiguity involved. */
/* opacity 0→1 crossfade on becoming the active row (main.js toggles
   .is-active via the same IntersectionObserver that drives the rail),
   not the sticky-stack's bare z-order cut — matches the reference's
   own slide-to-slide handoff, which is a genuine dissolve, not a cut.
   #gslide-0 is excluded: its own fade is driven by inline style.opacity
   from the hero→gallery scroll handoff (main.js), which already wins
   over this class rule by specificity — this rule would otherwise
   reset it to 0 between that handoff completing and IntersectionObserver
   first marking row 0 .is-active. */
.proj-gallery-row:not(#gslide-0) .proj-gallery-media{opacity:0;transition:opacity .6s var(--ease)}
.proj-gallery-row.is-active:not(#gslide-0) .proj-gallery-media{opacity:1}
.proj-gallery-media{position:sticky;top:0;height:100vh;padding:4rem 0;box-sizing:border-box}
.proj-gallery-media img,.proj-gallery-media video{display:block;width:100%;height:100%;object-fit:cover;border-radius:.4rem}
.proj-gallery-split{display:flex;flex-direction:column;gap:.8rem}
@media (min-width:800px){.proj-gallery-split{flex-direction:row;gap:.4rem}}
.proj-gallery-split img,.proj-gallery-split video{flex:1;min-width:0;min-height:0}

/* rail: sticky within the gallery's own (tall) grid row, so it stays
   vertically centered on screen for the whole gallery scroll range */
.proj-gallery-rail{display:none}
@media (min-width:800px){
	.proj-gallery-rail{
		/* gap:.4rem (4px) — measured directly off the reference's own
		   minimap column gap, not guessed */
		display:flex;flex-direction:column;gap:.4rem;
		position:sticky;top:50%;transform:translateY(-50%);
		grid-column:2;align-self:start;z-index:2;
	}
}
/* thin indicator line, not a per-thumbnail highlight — the reference's
   own minimap has exactly this: a bar sliding down the rail (top
   driven by scroll progress through the gallery) IN ADDITION to each
   thumbnail's own opacity state. 2px tall, square corners (no radius)
   — measured directly, not the thicker .8rem/rounded bar first guessed
   at. Position set by main.js, synced to the same IntersectionObserver
   already tracking the active slide. */
.proj-gallery-progress{position:absolute;left:0;right:0;top:0;height:.2rem;background:rgba(255,255,255,.95);z-index:1;transition:top .3s var(--ease)}
.proj-gallery-thumb{
	/* 5.6rem × 4rem (56×40px), landscape not portrait — measured
	   directly off the reference's own minimap thumbnail images, which
	   are noticeably WIDER than tall, the opposite of the 4×5.4rem
	   portrait shape first guessed at */
	width:5.6rem;height:4rem;border-radius:.4rem;overflow:hidden;padding:0;border:none;
	/* 50%, not 40% — measured directly off the reference's own inactive
	   thumbnail opacity (Tailwind opacity-50) */
	opacity:.5;cursor:pointer;transition:opacity .3s var(--ease),outline-color .3s var(--ease);
	outline:2px solid transparent;outline-offset:2px;
	-webkit-tap-highlight-color:transparent;
}
.proj-gallery-thumb img,.proj-gallery-thumb video{width:100%;height:100%;object-fit:cover;pointer-events:none}
.proj-gallery-thumb.is-active{opacity:1;outline-color:rgba(255,255,255,.8)}

/* related collections: reuses .proj-coll-card verbatim (same classes as
   the frozen Collections section on projects.html) — only this grid
   container is new, sized for 3 cards instead of the full responsive
   auto-fill grid */
/* solid background on all three post-gallery sections, not just
   .proj-detail-main's blanket rgba(26,18,13,.4) tint — that tint alone
   lets .svc-bg's warm sidebar gradient bleed through once scrolled
   past .proj-detail-hero-bg's own sticky coverage, so collections/
   credits/facts read as a different (warmer, lighter) brown than the
   solid near-black the gallery settles into. Same color as the hero
   scrim's own resting color and the header background, for one
   continuous dark tone for the rest of the page once it goes solid. */
.proj-detail-collections,.proj-detail-credits,.proj-detail-facts{background:rgba(20,8,2,1)}
.proj-detail-collections{position:relative;z-index:1;padding:4rem 2.4rem;border-top:1px solid rgba(255,255,255,.1)}
@media (min-width:800px){
	.proj-detail-collections{padding:4rem 2.4rem 4rem 5.6rem;display:grid;grid-template-columns:23rem minmax(0,1fr);gap:3.2rem}
}
.proj-detail-collections-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:2rem}
@media (min-width:640px){.proj-detail-collections-grid{grid-template-columns:repeat(3,minmax(0,1fr));max-width:74rem}}

/* credits grid */
.proj-detail-credits{position:relative;z-index:1;padding:4rem 2.4rem;border-top:1px solid rgba(255,255,255,.1)}
@media (min-width:800px){.proj-detail-credits{padding:4rem 5.6rem}}
.proj-credit-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:2.4rem 1.6rem}
@media (min-width:640px){.proj-credit-grid{grid-template-columns:repeat(3,minmax(0,1fr))}}
.proj-credit-card img{width:100%;aspect-ratio:1/1;object-fit:cover;border-radius:.8rem;background:#222}
.proj-credit-card p{margin-top:1rem;font-size:1.3rem;color:rgba(255,255,255,.5)}
.proj-credit-card p span{color:rgba(255,255,255,.9);font-weight:500}
.proj-detail-thanks{margin-top:3.2rem;padding-top:3.2rem;border-top:1px solid rgba(255,255,255,.1);display:grid;gap:1.2rem}
@media (min-width:800px){.proj-detail-thanks{grid-template-columns:23rem minmax(0,1fr);gap:3.2rem}}
.proj-detail-thanks-label{font-size:1.4rem;font-weight:600;color:rgba(255,255,255,.95)}
.proj-detail-thanks-list{font-size:1.3rem;line-height:1.8;color:rgba(255,255,255,.5)}
.proj-detail-thanks-list b{font-weight:500;color:rgba(255,255,255,.8)}

/* facts panel */
.proj-detail-facts{position:relative;z-index:1;padding:4rem 2.4rem 6rem;border-top:1px solid rgba(255,255,255,.1)}
@media (min-width:800px){.proj-detail-facts{padding:4rem 5.6rem 6rem}}
.proj-facts-row{display:grid;grid-template-columns:1fr;gap:.8rem;padding:1.6rem 0;border-bottom:1px solid rgba(255,255,255,.1)}
@media (min-width:800px){.proj-facts-row{grid-template-columns:23rem minmax(0,1fr)}}
.proj-facts-row-last{border-bottom:none}
.proj-facts-label{font-size:1.4rem;font-weight:600;color:rgba(255,255,255,.95)}
.proj-facts-value{font-size:1.4rem;color:rgba(255,255,255,.7)}
.proj-facts-services{display:flex;flex-direction:column;gap:1.2rem}
.proj-facts-service{display:flex;align-items:center;justify-content:space-between;font-size:1.4rem;color:rgba(255,255,255,.8)}
.proj-facts-learn{display:flex;align-items:center;gap:.4rem;color:rgba(255,255,255,.5);font-size:1.3rem}
.proj-facts-client{display:flex;align-items:center;gap:.8rem;font-size:1.4rem;color:rgba(255,255,255,.8)}
.proj-facts-client img{width:2rem;height:2rem;border-radius:.4rem;object-fit:contain;background:#fff}

/* ===================================================================
   CONTACT PAGE — bento-grid widget dashboard over a full-bleed looping
   background video. Grid columns/rows/gap below are measured directly
   off the live reference at the same 1728px desktop viewport this
   whole site is built against (confirmed --nav-width already matches
   the reference's own 120px nav exactly, so these values carry over
   without rescaling): columns 83/83/83/252/474/90/266px, rows
   113/90/90/90/162/90px, gap 2px (.2rem at this project's 10px rem
   root) — not guessed proportions.
   =================================================================== */
.contact-main{position:relative;overflow:hidden;height:100%;display:flex;align-items:center;justify-content:center}
.contact-bg{position:absolute;inset:0;z-index:0}
.contact-bg-video{width:100%;height:100%;object-fit:cover;display:block}
/* rgba(0,0,0,.1) — measured directly off the reference's own overlay
   (bg-black-alpha-100 resolved to 10% black, not a heavier dim) */
.contact-bg-overlay{position:absolute;inset:0;background:rgba(0,0,0,.1)}

.contact-grid{position:relative;z-index:1;display:none}
@media (min-width:800px){
	.contact-grid{
		display:grid;
		grid-template-columns:8.3rem 8.3rem 8.3rem 25.2rem 47.4rem 9rem 26.6rem;
		grid-template-rows:11.3rem 9rem 9rem 9rem 16.2rem 9rem;
		gap:.2rem;
		grid-template-areas:
			"clock clock clock map location team sound"
			"social social social map location team sound"
			"social social social map location team sound"
			"social social social map location team sound"
			"hours hours hours map location team sound"
			"hours hours hours map reach team sound";
	}
}
.contact-widget{background:rgba(0,0,0,.75);border-radius:.8rem;padding:1.6rem;color:rgba(255,255,255,.95);font-size:1.4rem;line-height:1.15}

/* clock */
.contact-clock{grid-area:clock;display:flex;flex-direction:column;justify-content:space-between}
.contact-clock-time{font-size:3.2rem;font-weight:500;letter-spacing:-.02em}
.contact-blink{display:inline-block;margin:0 .1rem;animation:contactBlink 1s step-start infinite}
@keyframes contactBlink{50%{opacity:0}}
.contact-clock-weather{font-size:1.4rem;font-weight:600;color:rgba(255,255,255,.95)}

/* social icon 3x3 */
.contact-social{grid-area:social;display:grid;grid-template-columns:repeat(3,1fr);grid-template-rows:repeat(3,1fr);gap:.2rem}
.contact-social-icon{background:rgba(0,0,0,.75);border-radius:.8rem;display:flex;align-items:center;justify-content:center;color:#fff}
.contact-social-icon svg{width:3.6rem;height:3.6rem}

/* hours */
/* space-between, not flex-end — reference's own widget pins the "Open
   from..." label to the TOP of the card and the day list to the
   bottom, with the empty space distributed between them, not both
   collapsed together at the bottom with empty space only above */
.contact-hours{grid-area:hours;display:flex;flex-direction:column;justify-content:space-between}
.contact-hours-head{font-size:1.4rem;font-weight:600}
.contact-hours-days{font-size:2.4rem;font-weight:500;line-height:1.2}

/* map / address */
.contact-map{grid-area:map;position:relative;border-radius:.8rem;overflow:hidden}
/* real OSM tiles (stitched + darkened to a muted night-map look), not
   a drawn grid placeholder — fetched directly for Sector 47, Gurugram */
.contact-map-bg{position:absolute;inset:0;background:url('../assets/images/contact/map-bg.jpg') center/cover}
.contact-map-bg::after{content:'';position:absolute;inset:0;background:linear-gradient(190deg,rgba(0,0,0,.15) 0%,rgba(0,0,0,.65) 100%)}
.contact-map-info{position:relative;height:100%;display:flex;flex-direction:column;justify-content:space-between;padding:1.6rem;color:#fff}
.contact-map-address{font-style:normal;font-size:2rem;font-weight:500;line-height:1.25;text-shadow:0 2px 12px rgba(0,0,0,.6)}
.contact-map-directions{display:flex;align-items:center;justify-content:space-between;gap:.8rem;background:rgba(0,0,0,.6);border-radius:.6rem;padding:1rem 1.2rem;font-size:1.3rem;font-weight:600;color:#fff}
.contact-map-sub{display:block;font-weight:400;color:rgba(255,255,255,.6);font-size:1.2rem;margin-top:.2rem}

/* location card */
.contact-location{grid-area:location;position:relative;border-radius:.8rem;overflow:hidden}
/* real Gurugram (Cyber City) skyline photo, CC-licensed via Wikimedia
   Commons, not a flat gradient placeholder */
.contact-location-bg{position:absolute;inset:0;background:url('../assets/images/contact/gurugram.jpg') center 30%/cover}
.contact-location-bg::after{content:'';position:absolute;inset:0;background:linear-gradient(0deg,rgba(0,0,0,.7) 0%,rgba(0,0,0,.1) 45%,rgba(0,0,0,.35) 100%)}
.contact-location-info{position:relative;padding:1.6rem}
.contact-location-label{display:flex;align-items:center;gap:1rem;font-size:2.6rem;font-weight:500;color:#fff}
.contact-location-label svg{width:3.2rem;height:3.2rem;flex-shrink:0}

/* phone/email row */
.contact-reach{grid-area:reach;display:grid;grid-template-columns:1fr 1fr;gap:.2rem}
.contact-reach-card{background:rgba(0,0,0,.75);border-radius:.8rem;padding:1.6rem;display:flex;align-items:center;justify-content:space-between;gap:.8rem;color:#fff}
.contact-reach-label{font-size:1.3rem;font-weight:600;color:rgba(255,255,255,.6)}
.contact-reach-value{font-size:1.4rem;font-weight:500;margin-top:.2rem}
.contact-reach-icon{width:3.6rem;height:3.6rem;border-radius:50%;background:rgba(255,255,255,.12);display:flex;align-items:center;justify-content:center;flex-shrink:0}

/* team marquee */
.contact-team{grid-area:team;border-radius:.8rem;overflow:hidden;position:relative}
.contact-team-track{display:flex;flex-direction:column;animation:contactMarquee 28s linear infinite}
.contact-team-track img{width:9rem;height:9rem;object-fit:cover;display:block}
@keyframes contactMarquee{from{transform:translateY(0)}to{transform:translateY(-50%)}}

/* sound / pronunciation widget — two grid rows (button, caption), not a
   centered flex column with a flat gap: the button's own row is 1fr so
   it centers in whatever space is left, while the caption stays
   pinned to the widget's bottom edge, matching the reference's own
   asymmetric spacing (more room above the caption than below it) */
.contact-sound{grid-area:sound;display:grid;grid-template-rows:1fr auto;justify-items:center;padding:1.6rem}
.contact-sound-btn{align-self:center;width:22.5rem;max-width:100%;background:none;border:none;padding:0;cursor:pointer;color:inherit}
.contact-sound-btn svg{width:100%;height:auto}
.contact-sound-btn:active svg{transform:scale(.97)}
.contact-sound-label{align-self:end;padding-bottom:.8rem;font-size:1.4rem;font-weight:600;text-align:center}

/* ===================================================================
   HIRE PAGE — conversational chat-style intake form, light pastel
   background (the reference breaks from the site's dark theme here).
   Colors sampled directly off the reference screenshot (soft cream to
   cool grey, ~210-235 RGB throughout, barely saturated).
   =================================================================== */
/* body.svc-page .nav is deliberately transparent+white-text (line
   ~1281) for every OTHER .svc-page sub-page, because they all sit over
   genuinely dark hero imagery there — this page still needs .svc-page
   for its shared shell/nav positioning rules, but its own content is
   light, so the nav needs dark icons/text back. Background STAYS
   transparent though (unlike index.html's own opaque #f2f2f2 nav) —
   this page has its own full-bleed gradient (.hire-bg) that should
   show straight through behind the sidebar too, not get interrupted
   by a solid panel. */
body.svc-page.hire-page .nav{background:transparent}
body.svc-page.hire-page .nav-top-name,
body.svc-page.hire-page .nav-item,
body.svc-page.hire-page .nav-top-tagline,
body.svc-page.hire-page .nav-foot{color:var(--fg-mute)}
body.svc-page.hire-page .nav-item{color:var(--fg-default)}
/* white pill, not black — matches index.html's own hover treatment
   (the only other page with dark-on-light nav icons); black made the
   icon disappear into its own hover highlight */
body.svc-page.hire-page .nav-icon-bg{background:#fff}
body.svc-page.hire-page .lang,
body.svc-page.hire-page .terms{color:var(--fg-mute)}

.hire-bg{position:fixed;inset:0;z-index:-1;background:
	radial-gradient(ellipse 80% 60% at 15% 10%,#ece3cd 0%,transparent 60%),
	radial-gradient(ellipse 70% 60% at 85% 20%,#e6d9d2 0%,transparent 55%),
	radial-gradient(ellipse 80% 70% at 30% 90%,#dbe0d3 0%,transparent 60%),
	linear-gradient(135deg,#e8e4da 0%,#d9d8d2 100%);
}
/* justify-content:center, not left-anchored — the reference centers
   the whole chat block in the content area to the right of the nav,
   not pins it against the nav's own edge. .hire-main already sits in
   .svc-shell's own second grid column (confirmed: starts at x:128,
   right after the nav's 120px track) so padding here only needs to be
   symmetric, not additionally compensated for nav-width again. */
.hire-main{min-height:100%;display:flex;align-items:center;justify-content:center;padding:4rem 2.4rem}
@media (min-width:800px){.hire-main{padding:4rem 6rem}}

.hire-chat{width:100%;max-width:62rem;display:flex;flex-direction:column;gap:2rem}
.hire-persona{display:flex;align-items:center;gap:1.2rem}
.hire-avatar{width:4rem;height:4rem;border-radius:50%;background:#1a1512;color:#fff;display:flex;align-items:center;justify-content:center;font-size:1.4rem;font-weight:600;flex-shrink:0}
.hire-persona-text{display:flex;flex-direction:column;font-size:1.4rem;line-height:1.3}
.hire-persona-text strong{font-weight:600;color:rgba(0,0,0,.9)}
.hire-persona-text span{color:rgba(0,0,0,.5)}

.hire-thread{display:flex;flex-direction:column;gap:1rem}
.hire-msg{max-width:72%;padding:1.2rem 1.6rem;border-radius:1.4rem;font-size:1.6rem;line-height:1.4;opacity:0;transform:translateY(.8rem);animation:hireMsgIn .4s var(--ease) forwards}
@keyframes hireMsgIn{to{opacity:1;transform:translateY(0)}}
.hire-msg-bot{align-self:flex-start;background:rgba(0,0,0,.08);color:rgba(0,0,0,.85);border-bottom-left-radius:.4rem}
.hire-msg-me{align-self:flex-end;background:#1a1512;color:#fff;border-bottom-right-radius:.4rem}
.hire-msg-summary{align-self:flex-start;background:none;color:rgba(0,0,0,.55);font-size:1.4rem;padding:0 .4rem;max-width:100%}
.hire-msg-summary b{color:rgba(0,0,0,.8);font-weight:600}

.hire-input-row{display:flex;align-items:center;gap:1.2rem;background:rgba(255,255,255,.55);border:1px solid rgba(0,0,0,.08);border-radius:1.2rem;padding:.6rem .6rem .6rem 1.6rem;backdrop-filter:blur(1rem)}
.hire-input-row[hidden]{display:none}
.hire-input{flex:1;min-width:0;background:none;border:none;font:inherit;font-size:1.6rem;color:rgba(0,0,0,.9);padding:.8rem 0}
.hire-input::placeholder{color:rgba(0,0,0,.35)}
.hire-input:focus{outline:none}
.hire-ok{flex-shrink:0;background:#1a1512;color:#fff;font-size:1.4rem;font-weight:600;padding:1rem 2rem;border-radius:.8rem;transition:opacity .2s var(--ease)}
.hire-ok:hover{opacity:.85}
.hire-ok:disabled{opacity:.4;cursor:default}

.hire-done{font-size:1.6rem;line-height:1.5;color:rgba(0,0,0,.75);max-width:48rem}
