/*
Theme Name: Portfolio DK
Theme URI:
Author: Дарья Кочергина
Author URI:
Description: Кастомная тема портфолио-сайта Дарьи Кочергиной. Вёрстка по макетам из Figma.
Version: 0.1.0
Requires at least: 6.4
Requires PHP: 8.0
Text Domain: portfolio-dk
*/

/* ---------- Fonts ---------- */
/* Self-hosted (assets/fonts/) instead of fonts.googleapis.com — one fewer
   third-party origin per page load. Split into cyrillic + latin subsets
   only (that's the entirety of what this site's copy uses — Russian text
   plus the odd Latin abbreviation like "MVP"/"iOS"); the fetched Google
   sheet's other subsets (cyrillic-ext, latin-ext, vietnamese) never render
   here, so those files were skipped. unicode-range values below are copied
   verbatim from Google's own sheet, which is how each browser decides which
   file it actually needs to download. */
@font-face {
	font-family: "Onest";
	font-style: normal;
	font-weight: 300;
	font-display: swap;
	src: url("assets/fonts/onest-300-cyrillic.woff2") format("woff2");
	unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}

@font-face {
	font-family: "Onest";
	font-style: normal;
	font-weight: 300;
	font-display: swap;
	src: url("assets/fonts/onest-300-latin.woff2") format("woff2");
	unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
	font-family: "Unbounded";
	font-style: normal;
	font-weight: 400;
	font-display: swap;
	src: url("assets/fonts/unbounded-400-cyrillic.woff2") format("woff2");
	unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}

@font-face {
	font-family: "Unbounded";
	font-style: normal;
	font-weight: 400;
	font-display: swap;
	src: url("assets/fonts/unbounded-400-latin.woff2") format("woff2");
	unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
	font-family: "Unbounded";
	font-style: normal;
	font-weight: 500;
	font-display: swap;
	src: url("assets/fonts/unbounded-500-cyrillic.woff2") format("woff2");
	unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}

@font-face {
	font-family: "Unbounded";
	font-style: normal;
	font-weight: 500;
	font-display: swap;
	src: url("assets/fonts/unbounded-500-latin.woff2") format("woff2");
	unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* ---------- Tokens ---------- */
:root {
	--color-bg: #202020;
	--color-border: #424242;
	--color-text: #f3f4f7;
	--color-text-50: rgba(243, 244, 247, 0.5);
	--color-surface-8: rgba(243, 244, 247, 0.08);
	--color-status-green: #63e3b1;
	--color-pattern-hover: #1c1c1c;

	--font-display: "Unbounded", sans-serif;
	--font-text: "Onest", sans-serif;

	--transition-hover: 400ms ease-in-out;

	/* Real height fallback for the fixed header (40px CV pill + 24px top/bottom
	   padding, in rem); smooth-scroll.js overwrites this to the header's actual
	   offsetHeight (a real rendered px value, already scale-correct at any
	   root font-size) on load/resize. */
	--header-height: 5.5rem;

	/* Figma's image-placeholder fill: the pattern's diagonal-tick vector
	   (source node 426:30217, 6×12, corner-to-corner from (0,12) to (6,0)) —
	   line direction (1,-2), so the repeat period perpendicular to the
	   stripes is 12/√5 ≈ 5.3666px at angle atan2(2,-1) ≈ 116.565deg.
	   Any RASTER tiling of this (CSS background-repeat of an SVG image, or
	   even a native SVG <pattern>) still composites a separate anti-aliased
	   bitmap per repeat, leaving a visible seam at every tile edge and going
	   soft/pixelated when the container size doesn't match the raster's
	   cached resolution. A repeating-linear-gradient has neither problem —
	   it's evaluated analytically per pixel, so it's always crisp and truly
	   seamless at any size/zoom, with no image asset at all. In rem so the
	   stripe scales with the proportional-zoom breakpoints below. */
	--pattern-tile: repeating-linear-gradient(
		116.565deg,
		var(--color-border) 0px,
		var(--color-border) 0.0625rem,
		transparent 0.0625rem,
		transparent 0.3354125rem
	);
}

/* ---------- Root scale ---------- */
/* 16px is the Figma desktop baseline. Everything else in this file is sized
   in rem against it, so the two media queries below can scale the ENTIRE
   design as one unit — a proportional shrink down to 1024px, or proportional
   growth from 1920px up — instead of reflowing, matching the "Адаптивность"
   spec's breakpoint bands. Between 1024 and 1920 root stays fixed at 16px;
   that band gets its "резиновый" (fluid) behavior for free from the existing
   %/flex-based layout stretching, not from this scale.
   `100vw / 1440px` (a length divided by a length) is a unitless ratio — the
   parens are load-bearing, `calc` evaluates left-to-right otherwise and
   `16px * 100vw` (length × length) is invalid.
   700–1023 (tablet) and ≤699 (mobile) are their own breakpoint tiers with
   their own Figma frames — see the tablet and mobile blocks below. */
html {
	font-size: 16px;
	/* Only `body` had a background before — `html` (and, on iOS, the strip
	   a browser's own toolbar/viewport transform briefly exposes) stayed
	   whatever the browser's own default is (white/transparent). That
	   default is what showed through as a gap/flash during a toolbar
	   animation, not necessarily a sizing bug in any specific element. */
	background: var(--color-bg);
}

@media (min-width: 1024px) and (max-width: 1439px) {
	html {
		font-size: calc(16px * (100vw / 1440px));
	}
}

@media (min-width: 1921px) {
	html {
		font-size: calc(16px * (100vw / 1920px));
	}
}

/* ---------- Reset ---------- */
*,
*::before,
*::after {
	box-sizing: border-box;
}

html,
body,
h1,
h2,
h3,
h4,
p,
ul,
figure {
	margin: 0;
	padding: 0;
}

/* Custom cursor (2026-09-24, per Дарья's Figma asset — node 1306:448477,
   "Cursor": a thin black arrow + white outline + faint drop-shadow, the
   look of Figma's own canvas pointer). `2 1` is the hotspot — the arrow's
   actual tip in the SVG's own 22.2483×22.2483 viewBox coords, so clicks
   land exactly where the tip points, not the image's top-left corner.
   Set on `html`, not `body`: `cursor` is inherited, so this alone covers
   every plain element on the page. Deliberately NOT set on `a`/`button`
   (2026-09-24, Дарья's follow-up) — clickable elements should still turn
   into the system hand/pointer on hover, same as any other site, so the
   custom arrow is scoped to non-interactive content only. `a`/`button`
   already get `cursor: pointer` for free from the browser's own UA
   stylesheet without any rule here — nothing to add. */
html {
	cursor: url("assets/icons/cursor.svg") 2 1, auto;
}

ul {
	list-style: none;
}

img {
	display: block;
	max-width: 100%;
}

a {
	color: inherit;
	text-decoration: none;
}

button {
	font: inherit;
	color: inherit;
	background: none;
	border: none;
	padding: 0;
	cursor: pointer;
}

body {
	background: var(--color-bg);
	color: var(--color-text);
	font-family: var(--font-text);
	font-weight: 300;
	font-size: 1rem;
	line-height: 1.5rem;
}

/* ---------- Layout ---------- */
/* GSAP ScrollSmoother wraps everything it scrolls; the header sits outside
   it as a genuine `position: fixed` element (see .site-header below), so the
   in-flow content needs padding-top standing in for the space the header
   used to occupy when it was `position: sticky`. */
#smooth-wrapper {
	overflow: hidden;
}

#smooth-content {
	overflow: visible;
	width: 100%;
}

.site-content {
	display: flex;
	flex-direction: column;
	align-items: flex-start;
	width: 100%;
	padding: var(--header-height) 1rem 0;
}

/* Every top-level block here (hero, sections, images, card lists) draws its
   own full frame — `outline` with offset -1px, i.e. on the block's own
   outermost pixel row. Stacked edge to edge, block A's bottom row and block
   B's top row are two DIFFERENT adjacent pixels → a 2px line at every seam
   (confirmed by pixel sampling 2026-09-24). Figma strokes are centered, so
   neighbors there share one line; overlapping each block onto the previous
   one by exactly 1px reproduces that: both frames land on the same row. */
.site-content > * + * {
	margin-top: -1px;
}

/* Native-anchor fallback (JS-disabled, or a jump smooth-scroll.js doesn't
   intercept) — keeps the section's top clear of the fixed header even
   without GSAP's own offset math in scrollToTarget(). */
.hero,
.cases,
.concepts,
.contacts {
	scroll-margin-top: var(--header-height);
}

/* ---------- Header ---------- */
.site-header {
	position: fixed;
	/* Extends past the visible top/sides by exactly the device's own
	   unsafe-area inset (0 on anything without a notch/pill, so this is a
	   no-op there) so its solid background paints behind the iOS
	   status bar/notch instead of leaving it to whatever's scrolled
	   there — that's what showed through on some in-app webviews whose
	   own chrome goes translucent there. Padding grows by the same
	   amount so the logo/menu themselves stay exactly where they were. */
	top: calc(-1 * env(safe-area-inset-top));
	left: calc(-1 * env(safe-area-inset-left));
	right: calc(-1 * env(safe-area-inset-right));
	z-index: 3;
	display: flex;
	align-items: center;
	justify-content: space-between;
	width: 100%;
	padding: calc(1.5rem + env(safe-area-inset-top)) calc(4rem + env(safe-area-inset-right)) 1.5rem calc(4rem + env(safe-area-inset-left));
	background: var(--color-bg);
}

/* Figma strokes are center-aligned (straddle the edge) — CSS `border` draws
   fully inside instead, which doubles up wherever two bordered boxes touch.
   A thin absolutely-positioned bar centered on the edge reproduces the
   Figma look and self-overlaps cleanly with the neighbor's own edge line. */
.site-header::after {
	content: "";
	position: absolute;
	left: 0;
	right: 0;
	/* -1px: exactly the first block's own top frame row (.site-content's
	   padding-top is this same --header-height), so both snap to the same
	   pixel. -0.5px only coincided when the header height was a whole
	   number (1440px); in the rem-scaled 1024–1439 range it split into a
	   2px line. */
	bottom: -1px;
	height: 1px;
	background: var(--color-border);
}

.site-logo {
	font-family: var(--font-display);
	font-weight: 500;
	font-size: 1.25rem;
	line-height: 1.5rem;
	transition: color var(--transition-hover);
}

.site-logo:active {
	color: var(--color-text-50);
}

.site-menu {
	display: flex;
	align-items: center;
	gap: 2rem;
}

.site-menu__link {
	position: relative;
	display: inline-flex;
	flex-direction: column;
	height: 1.5rem;
	overflow: hidden;
}

.site-menu__link-stack {
	display: flex;
	flex-direction: column;
	transition: transform var(--transition-hover);
}

.site-menu__link:active .site-menu__link-stack {
	transform: translateY(-50%);
}

.site-menu__link-line {
	flex-shrink: 0;
	font-size: 1rem;
	line-height: 1.5rem;
	white-space: nowrap;
}

.site-menu__link-line--default {
	color: var(--color-text);
}

.site-menu__link-line--muted {
	color: var(--color-text-50);
}

/* Icon glyphs are exported tight-cropped to their own artwork —
   center them at native size instead of stretching to fill the box */
.icon-16 {
	display: inline-flex;
	flex-shrink: 0;
	align-items: center;
	justify-content: center;
	width: 1rem;
	height: 1rem;
}

.icon-24 {
	display: inline-flex;
	flex-shrink: 0;
	align-items: center;
	justify-content: center;
	width: 1.5rem;
	height: 1.5rem;
}

/* Icon rendered as a solid-color mask instead of a raster <img> so it
   always matches `color` exactly (incl. through hover transitions) —
   used where the icon must track the surrounding text color 1:1 */
.icon-mask {
	background-color: currentColor;
	-webkit-mask-image: var(--icon-mask-src);
	mask-image: var(--icon-mask-src);
	-webkit-mask-repeat: no-repeat;
	mask-repeat: no-repeat;
	-webkit-mask-position: center;
	mask-position: center;
	/* native artwork size, not "contain" — the glyph is exported tight-cropped
	   to its own bounds, so scaling to fill the box stretches it oversized */
	-webkit-mask-size: var(--icon-mask-size, contain);
	mask-size: var(--icon-mask-size, contain);
}

/* Pill buttons (CV / Dribbble): default + hover face stacked vertically,
   revealed by sliding the stack up on hover (matches Figma prototype) */
.pill-button {
	position: relative;
	display: inline-flex;
	flex-direction: column;
	border-radius: 6.25rem;
	overflow: hidden;
	background: var(--color-surface-8);
}

.pill-button--cv {
	height: 2.5rem;
}

.pill-button--dribbble {
	height: 3rem;
}

.pill-button__stack {
	display: flex;
	flex-direction: column;
	transition: transform var(--transition-hover);
}

.pill-button:active .pill-button__stack {
	transform: translateY(-50%);
}

.pill-button__face {
	display: flex;
	flex-shrink: 0;
	align-items: center;
	gap: 0.5rem;
	white-space: nowrap;
	border-radius: 6.25rem;
}

.pill-button__face--default {
	color: var(--color-text);
}

.pill-button__face--hover {
	background: var(--color-text);
	color: var(--color-bg);
}

/* Figma's hover face reuses the light download icon as-is (no dark
   variant exists in the file) — darken it here so it stays visible
   on the light hover background */
.pill-button__face--hover .icon-16 img {
	filter: brightness(0);
}

/* `height` set explicitly here (2026-09-24, per Дарья's live iPad 11th gen
   trace, landscape/"Request Desktop Website") instead of leaving it to sum
   from `padding + line-height` (0.5rem + 1.5rem + 0.5rem, same 2.5rem
   total on paper) — at the fractional page-zoom iPadOS renders a desktop
   layout at in that mode, WebKit rounds that 3-value sum to device pixels
   independently of how it rounds .pill-button--cv's own plain `height:
   2.5rem`, and the two didn't always land on the same physical pixel. The
   ~fraction-of-a-px gap that opened up let the top edge of the hidden
   .pill-button__face--hover (light background, stacked directly below by
   .pill-button__stack, normally fully clipped by .pill-button's
   `overflow: hidden`) show through as a hairline across the pill's bottom
   edge. Confirmed by contrast with .icon-button, which uses the exact
   same stacked-face + overflow:hidden pattern but sets `width`/`height`
   directly on .icon-button__face (no summed padding/line-height) and
   never showed the leak — not reproducible in a devtools emulated
   viewport either way, only real WebKit compositing at that scale.
   Declaring the same `2.5rem`/`3rem` value here as on .pill-button--cv/
   --dribbble (rather than deriving it) makes both rows round identically,
   since it's the same source value being rounded the same way. */
.pill-button--cv .pill-button__face {
	height: 2.5rem;
	padding: 0.5rem 1.25rem;
	font-size: 1rem;
	line-height: 1.5rem;
}

.pill-button--dribbble .pill-button__face {
	height: 3rem;
	padding: 0.625rem 1.25rem;
	font-size: 1.25rem;
	line-height: 1.75rem;
}

/* "Скоро" — disabled state of the case-card button (cases 2/3, not built
   yet). Deliberately NOT a `.pill-button` — no stack, no hover face, no
   overflow:hidden: this is a non-interactive label, not a button, so it
   gets no hover feedback at all (2026-09-08, Дарья's call). A static span
   sized by its own padding/line-height also sidesteps the stack-height
   clipping the two-face version had if its two faces' combined height ever
   drifted from the outer `.pill-button`'s fixed height by even 1px. */
.pill-button-static {
	display: inline-flex;
	align-items: center;
	border-radius: 6.25rem;
	background: var(--color-surface-8);
	white-space: nowrap;
}

.pill-button-static--soon {
	padding: 0.5rem 1.25rem;
	font-size: 1rem;
	line-height: 1.5rem;
	color: var(--color-text-50);
}

/* Round icon-only button (case card arrow): same stacked slide-reveal pattern */
.icon-button {
	position: relative;
	display: inline-flex;
	flex-direction: column;
	width: 2.5rem;
	height: 2.5rem;
	border-radius: 6.25rem;
	overflow: hidden;
	background: var(--color-surface-8);
	flex-shrink: 0;
}

.icon-button__stack {
	display: flex;
	flex-direction: column;
	transition: transform var(--transition-hover);
}

.icon-button:not(.burger-button):active .icon-button__stack,
.case-card:active .icon-button__stack {
	transform: translateY(-50%);
}

.icon-button__face {
	display: flex;
	flex-shrink: 0;
	align-items: center;
	justify-content: center;
	width: 2.5rem;
	height: 2.5rem;
	border-radius: 6.25rem;
}

.icon-button__face--hover {
	background: var(--color-text);
}

/* Mobile burger button — same icon-button shape, hidden until the mobile
   breakpoint (below). The icon is inline SVG using currentColor (not an
   <img>), so the hover face just needs `color` set to go dark — no need for
   the brightness(0) raster hack Блок 1 used for the CV icon's own missing
   dark variant. */
.burger-button {
	display: none;
}

/* Full-screen mobile menu panel — hidden outside the mobile breakpoint the
   same way .burger-button is; see the ≤699px block for its actual layout. */
.mobile-menu {
	display: none;
}

/* ---------- Preloader ---------- */
/* PROTOTYPE (2026-09-08, not yet confirmed), homepage only — Figma reference
   "preloader-v1" (node 929:86884), pulled via get_design_context/
   get_motion_context for exact values. See preloader.js for the actual
   progress→transform/text wiring; this is just the visual shell.
   Mechanic (from Figma's own keyframe, not a guess): the hatch
   (--pattern-tile, same one already used behind case/concept images) sits
   on the pill from frame 0 — what actually animates is an opaque cover the
   same color as the page, sliding away left-to-right (translateX 0%→100%)
   to progressively reveal it. Not an opacity fade. */
.preloader {
	position: fixed;
	top: 0;
	left: 0;
	right: 0;
	/* Explicit height, not `inset:0`/`bottom:0`: those size the box against
	   the LAYOUT viewport, which on iOS doesn't shrink/grow as the
	   browser's own toolbar (Safari's address bar, or a different bar
	   entirely in Chrome/other iOS browsers — all of them are really
	   WebKit under the hood, just with their own chrome around it) shows
	   or hides — leaving a gap the panel doesn't cover once the toolbar
	   changes. `dvh` is the unit built specifically to track whatever the
	   CURRENTLY visible area actually is, continuously, at the rendering
	   engine level — no JS/resize-event involved, so nothing to go stale.
	   `100vh` first as a plain fallback for browsers without `dvh`. */
	height: 100vh;
	height: 100dvh;
	z-index: 1000;
	display: flex;
	align-items: center;
	justify-content: center;
	background: var(--color-bg);
	transition: opacity 0.6s ease;
}

.preloader--hidden {
	opacity: 0;
	pointer-events: none;
}

.preloader__pill {
	position: relative;
	display: flex;
	align-items: center;
	justify-content: center;
	width: min(54.5rem, calc(100vw - 2rem)); /* 872px in Figma */
	height: 10rem; /* 160px */
	border-radius: 62.5rem; /* Figma: 1000px — same "fully round" value as .case-card--featured .img-box */
	/* A `border`, not `outline` + a negative `outline-offset`: the original
	   `outline-offset: -0.03125rem` (2026-09-24, per Дарья's live iPad 11th
	   gen trace, landscape/"Request Desktop Website") is half a device
	   pixel — fine on a normal integer-DPR screen, but iPadOS renders its
	   desktop-site layout at a fractional page zoom to fit it into the
	   physical viewport, which divides the effective DPR by that same
	   fraction. At the resulting scale, WebKit rounded that half-pixel
	   offset down to nothing on the very first paint, and it only turned
	   up once something else forced a repaint — in practice the moment
	   preloader.js starts moving .preloader__cover. Not reproducible in a
	   devtools emulated viewport, since that never emulates a fractional
	   page zoom. A `border` needs no offset math at all — box-sizing is
	   border-box globally (see the top of this file), so it draws right
	   along the same `border-radius` curve without shrinking the pill, at
	   whatever width rounds cleanly for the device it's on. (The
	   .preloader__clip split below is kept regardless — clipping the
	   pattern/cover in their own layer instead of on this box is still the
	   more correct structure, just wasn't the actual fix.) */
	border: 1px solid var(--color-border);
}

/* Clips the pattern + sliding cover to the pill's rounded shape, kept
   separate from .preloader__pill's own border/background so the two don't
   interact. */
.preloader__clip {
	position: absolute;
	inset: 0;
	border-radius: inherit;
	overflow: hidden;
	background-image: var(--pattern-tile);
}

.preloader__cover {
	position: absolute;
	inset: -1px;
	background: var(--color-bg);
	transform: translateX(0%);
}

.preloader__count {
	position: relative;
	font-family: var(--font-display);
	font-weight: 400;
	font-size: 3.5rem; /* Figma: 56px — same scale as .concepts__title/.contacts__title */
	line-height: 4.25rem; /* Figma: 68px */
	font-variant-numeric: tabular-nums;
	color: var(--color-text);
}

@media (prefers-reduced-motion: reduce) {
	.preloader {
		transition: none;
	}
}

.burger-button .icon-button__face--hover {
	color: var(--color-bg);
}

/* Bars start closed (3 horizontal lines); toggleMobileMenu() in
   smooth-scroll.js animates them into an X with GSAP on open, and back on
   close. `transform-box: fill-box` doesn't resolve percentage origins
   correctly on an SVG <line> in Chrome (its fill bounding box has zero
   height, and `50% 50%` computed to `0px 0px` instead of the bar's own
   center) — so the pivot is set explicitly in the <line>'s own viewBox
   coordinates instead: each bar's actual midpoint. */
.burger-icon__bar--top {
	transform-origin: 8px 4px;
}

.burger-icon__bar--mid {
	transform-origin: 8px 8px;
}

.burger-icon__bar--bottom {
	transform-origin: 8px 12px;
}

/* ---------- Media (case/concept images) ---------- */
/* Border stays on the OUTER container, never on the photo itself: a 0.5px
   padding keeps the image inset from the line, with the diagonal pattern
   showing in that gap and behind any transparent artwork. `.img-box` is the
   inner clipping frame — round variants put the pill radius on it, not on
   the (always rectangular) outer border. */
.img-box {
	position: relative;
	width: 100%;
	height: 100%;
	overflow: hidden;
}

.img-box img {
	/* <img> is a replaced element: absolutely-positioned with inset alone
	   and no width/height, it sizes itself from its OWN intrinsic pixels
	   instead of filling the box (unlike a plain div) — so width/height:100%
	   is required here. Zooming is done via `transform: scale()` instead of
	   `inset`, since transform-origin centers it for free without hitting
	   that replaced-element sizing quirk. */
	position: absolute;
	inset: 0;
	width: 100%;
	height: 100%;
	object-fit: cover;
	pointer-events: none;
	transform: scale(1);
	transition: transform var(--transition-hover);
}

/* Hovering the card zooms the photo in from the center, still clipped by
   the (overflow: hidden) frame around it. Not applied to .concepts__cell —
   those images aren't clickable, so no hover feedback (2026-09-08). */
.case-card:active .img-box img {
	transform: scale(1.04);
}

/* ---------- Hero ---------- */
.hero {
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: space-between;
	width: 100%;
	height: 37.5rem;
	outline: 1px solid var(--color-border);
	outline-offset: -1px;
}

.hero__headline {
	width: 100%;
	padding: 2.5rem 3rem;
	font-family: var(--font-display);
	font-weight: 400;
	font-size: 5rem;
	line-height: 5.5rem;
	color: var(--color-text);
}

.hero__row {
	display: flex;
	align-items: flex-end;
	width: 100%;
}

.hero__text {
	/* Not `flex: 1 0 0` — with box-sizing:border-box, a flex-basis of 0 still
	   adds the item's own padding on top before growth is distributed, so the
	   side WITH padding here silently claims more than its 50% share (704px
	   in Figma, 429:30277) at the other side's expense. `width: 50%` sizes
	   both from the container directly, sidestepping that entirely. */
	width: 50%;
	flex-shrink: 0;
	padding: 3rem;
	font-size: 1.25rem;
	line-height: 1.75rem;
}

/* Top and left edges of the table are internal to .hero (the table sits in
   its bottom-right corner), so .hero's own outline doesn't draw them —
   only right/bottom coincide with it. The table draws those two itself. */
.hero__table {
	width: 50%;
	flex-shrink: 0;
	border-top: 1px solid var(--color-border);
	border-left: 1px solid var(--color-border);
	display: grid;
	grid-template-columns: repeat(4, minmax(0, 1fr));
	grid-template-rows: 3.75rem 3.75rem 3.75rem; /* fixed, matching Figma (429:30506 etc.) — "auto" let a wrapped cell grow the row instead of surfacing the real fit problem */
}

/* Fixed 9-cell layout (see .hero__table): cell 1 is the 2×2 merged name
   cell, cells 2–9 are the 8 plain cells auto-placed around it. Each of the
   ~10 internal grid lines used to be drawn by BOTH cells that touch it
   (every cell had its own full outline) — the classic browser-vs-Figma
   float-rounding mismatch (same root cause fixed the same way throughout
   this stylesheet, see .case-insights): the two copies didn't always land
   on the same physical pixel, so lines came out as a soft ~2px smear
   instead of 1px (Дарья caught this 2026-09-24, checked at 1512px — a
   width neither of the "anchor" breakpoints this site scales exactly at).
   Fixed by having each line drawn exactly once: `.hero`'s own outline is
   the perimeter, and every cell draws only its own right/top internal
   edge — EXCEPT the col-1/col-2 line (cells 2/4/6 | cells 3/5/7), which
   turned out to hit a SEPARATE, narrower Chromium bug: a `border-right`
   (or even `box-shadow` inset) on an auto-placed grid item paints as
   nothing whenever that exact grid line isn't touched by any explicitly-
   positioned item — confirmed by pixel-sampling (1px invisible, 2px+
   renders fine, and switching the SAME line to a `border-left` on the
   cell to its right paints correctly at 1px). Every other internal line
   here (col-2/col-3, col-3/col-4) sits on a grid line the name cell's own
   `grid-column: 3/span 2` placement DOES touch, so those paint fine as a
   plain `border-right`. Only the col-1/col-2 line needed the border-left
   workaround — cells 3/5/7 draw both (left for that line, right for the
   next one over), cell 8 still just draws its own right. */
.hero__cell {
	display: flex;
	align-items: center;
	justify-content: flex-start;
	padding: 1.125rem 1rem;
	font-size: 1rem;
	line-height: 1.5rem;
}

.hero__cell:nth-child(3),
.hero__cell:nth-child(5),
.hero__cell:nth-child(7) {
	border-left: 1px solid var(--color-border);
}

.hero__cell:nth-child(3),
.hero__cell:nth-child(5),
.hero__cell:nth-child(7),
.hero__cell:nth-child(8) {
	border-right: 1px solid var(--color-border);
}

.hero__cell:nth-child(n + 4) {
	border-top: 1px solid var(--color-border);
}

.hero__cell--name {
	grid-column: 3 / span 2;
	grid-row: 1 / span 2;
	justify-content: center;
	text-align: center;
	font-size: 1.25rem;
	line-height: 1.75rem;
}

.hero__cell--center {
	justify-content: center;
	text-align: center;
}

.hero__cell--status {
	justify-content: center;
	gap: 0.5rem;
}

.status-dot {
	width: 0.375rem;
	height: 0.375rem;
	border-radius: 50%;
	background: var(--color-status-green);
	flex-shrink: 0;
	filter: drop-shadow(0 0 0.15625rem var(--color-status-green));
	animation: status-pulse 2.5s ease-in-out infinite;
}

@keyframes status-pulse {
	0%,
	100% {
		opacity: 1;
	}
	50% {
		opacity: 0.4;
	}
}

/* ---------- Cases ---------- */
/* .cases' own outline is the perimeter of the whole block (featured card +
   the 2-across row below it combined) — individual cards used to each draw
   their own full outline instead, which doubled up wherever two of them
   touched (same root cause as everywhere else in this file with this
   comment, see .case-insights). Every internal line is now drawn exactly
   once: .case-card__content's border-top separates it from its own image
   above; .case-row's images/content need their own border-top to separate
   from the featured card above THEM; and within a row, the 2nd card's
   image+content draw border-left (not the 1st card's border-right —
   "the later element owns the shared edge" is the direction that's
   actually reliable, see the .hero__cell Chromium-bug note above). */
.cases {
	display: flex;
	flex-direction: column;
	align-items: stretch;
	width: 100%;
	outline: 1px solid var(--color-border);
	outline-offset: -1px;
	/* Same reason as .concepts: .case-card__image is position:relative and
	   would otherwise paint over this outline wherever it's flush with it. */
	padding: 1px;
}

.case-row {
	display: flex;
	align-items: stretch;
	width: 100%;
}

.case-row .case-card__image {
	border-top: 1px solid var(--color-border);
}

.case-row .case-card:not(:first-child) .case-card__image,
.case-row .case-card:not(:first-child) .case-card__content {
	border-left: 1px solid var(--color-border);
}

.case-card {
	display: flex;
	flex-direction: column;
	flex: 1 0 0;
	min-width: 0;
}

.case-card__image {
	width: 100%;
	aspect-ratio: 1408 / 792;
	padding: 0.5px;
	background-image: var(--pattern-tile);
	background-color: transparent;
	position: relative;
	transition: background-color var(--transition-hover);
}

.case-card--featured .case-card__image .img-box {
	border-radius: 62.5rem;
}

/* Hovering anywhere on the card (image or info box) darkens both — the
   pattern behind the photo stays visible, it just sits on a darker fill */
.case-card:active .case-card__image {
	background-color: var(--color-pattern-hover);
}

.case-card__content {
	display: flex;
	flex: 1 0 0;
	align-items: flex-start;
	justify-content: space-between;
	width: 100%;
	padding: 3rem;
	border-top: 1px solid var(--color-border);
	background-color: transparent;
	transition: background-color var(--transition-hover);
}

.case-card:active .case-card__content {
	background-color: var(--color-pattern-hover);
}

.case-card__info {
	display: flex;
	flex-direction: column;
	gap: 1rem;
	flex: 1 0 0;
	min-width: 0;
	max-width: 61.375rem;
	padding-right: 2rem;
}

.case-card__tags {
	display: flex;
	flex-wrap: wrap;
	gap: 0.125rem 0.375rem;
	font-size: 1.25rem;
	line-height: 1.75rem;
}

/* :nth-child(...of :not(...)) excludes .case-text__tags-break (see
   portfolio_dk_case_overline_tags) from the odd/even count — otherwise a
   forced line break shifts every span after it by one position, flipping
   which words render dimmed vs full color (hit on the driver-app case's
   "Команда" row, 2026-09-24: QA and everything after it came out dimmed). */
.case-card__tags span:nth-child(odd of :not(.case-text__tags-break)) {
	color: var(--color-text);
}

.case-card__tags span:nth-child(even of :not(.case-text__tags-break)) {
	color: var(--color-text-50);
}

.case-card__title {
	font-family: var(--font-display);
	font-weight: 400;
	font-size: 2.25rem;
	line-height: 3rem;
}

.case-card__desc {
	font-size: 1.25rem;
	line-height: 1.75rem;
	color: var(--color-text-50);
}

/* ---------- Concepts ---------- */
.concepts {
	width: 100%;
	outline: 1px solid var(--color-border);
	outline-offset: -1px;
	/* .concepts__cell is position:relative (+ its own pattern fill), so it
	   paints ABOVE this outline — flush against the edge it hid the right
	   frame line and the bottom one shared with .contacts. 1px of padding
	   keeps the cells off the outline row. */
	padding: 1px;
}

.concepts__head {
	display: flex;
	align-items: center;
	justify-content: space-between;
	padding: 2.5rem 3rem;
}

.concepts__title {
	font-family: var(--font-display);
	font-weight: 400;
	font-size: 3.5rem;
	line-height: 4.25rem;
}

/* Every row/wide-cell after the head needs a top divider from whatever
   came before it; within a row, the 2nd square cell needs a left divider
   from the 1st. Same single-line-per-edge fix as everywhere else in this
   file (see .case-insights) — cells used to each draw a full outline of
   their own, which could double up into a soft/missing line at the shared
   edge instead of collapsing cleanly into one. */
.concepts > .concepts__grid-row,
.concepts > .concepts__cell--wide {
	border-top: 1px solid var(--color-border);
}

.concepts__grid-row {
	display: flex;
	width: 100%;
}

.concepts__cell--square:not(:first-child) {
	border-left: 1px solid var(--color-border);
}

.concepts__cell {
	position: relative;
	padding: 0.5px;
	background-image: var(--pattern-tile);
	background-color: transparent;
}

.concepts__cell--square {
	flex: 1 0 0;
	min-width: 0;
	aspect-ratio: 1 / 1;
}

/* Sharp-corner cells get a plain fill, not the pattern: the pattern is only
   there to fill the rounded image's corners (.concepts__cell--wide). Under
   a square image it just peeked out of the 0.5px padding as a dashed
   1-device-pixel strip along every frame line on Retina, which read as a
   broken line (Дарья, 2026-09-24). Same rule as .case-media vs
   .case-media--round on the case pages. */
.concepts__cell--square,
.case-card:not(.case-card--featured) .case-card__image {
	/* No padding either: with nothing to fill, the 0.5px gap only ever
	   showed as a strip (pattern, or the hover fill) beside the line. */
	padding: 0;
	background-image: none;
	background-color: var(--color-bg);
}

.concepts__cell--wide {
	width: 100%;
	aspect-ratio: 1408 / 792;
}

.concepts__cell--wide .img-box {
	border-radius: 62.5rem;
}

/* ---------- Contacts ---------- */
.contacts {
	display: flex;
	align-items: stretch;
	width: 100%;
	outline: 1px solid var(--color-border);
	outline-offset: -1px;
}

.contacts__title-box {
	display: flex;
	/* Not `flex: 1 0 0` — same gotcha as .hero__text/.hero__table (see there):
	   with box-sizing:border-box, a flex-basis of 0 still adds this box's own
	   padding on top before growth is distributed, so the padded side here
	   silently claimed more than its 50% share at .contacts__list's expense
	   (padding lives on .contact-card__info instead, one level down).
	   `width: 50%` sizes both from the container directly, sidestepping it. */
	width: 50%;
	flex-shrink: 0;
	align-items: flex-start;
	justify-content: flex-start;
	padding: 2.5rem 3rem;
}

.contacts__title {
	flex: 1 0 0;
	font-family: var(--font-display);
	font-weight: 400;
	font-size: 3.5rem;
	line-height: 4.25rem;
}

/* border-left once here instead of every card's own left edge, and
   border-top on every card but the first instead of every card's own
   full outline — same single-line-per-edge fix as .case-cards__list /
   .case-card-glass (see there for why: two independently-drawn outlines
   sharing an edge don't reliably collapse into one crisp line). */
.contacts__list {
	display: flex;
	flex-direction: column;
	width: 50%;
	flex-shrink: 0;
	border-left: 1px solid var(--color-border);
}

.contact-card:not(:first-child) {
	border-top: 1px solid var(--color-border);
}

.contact-card {
	display: flex;
	align-items: stretch;
	width: 100%;
	background-color: var(--color-bg);
	transition: background-color var(--transition-hover);
}

.contact-card:active {
	background-color: var(--color-pattern-hover);
}

.contact-card__icon-box {
	position: relative;
	display: flex;
	align-items: center;
	justify-content: center;
	flex-shrink: 0;
	aspect-ratio: 1 / 1;
	padding: 2rem;
}

.contact-card__icon-box::after {
	content: "";
	position: absolute;
	top: 0;
	bottom: 0;
	right: -0.5px;
	width: 1px;
	background: var(--color-border);
}

.contact-card__icon {
	display: flex;
	align-items: center;
	justify-content: center;
	width: 4rem;
	height: 4rem;
	border-radius: 6.25rem;
	background: var(--color-surface-8);
}

.contact-card__info {
	display: flex;
	flex-direction: column;
	align-items: flex-start;
	justify-content: center;
	gap: 0.25rem;
	flex: 1 0 0;
	min-width: 0;
	padding: 2.5rem 3rem;
}

.contact-card__label {
	font-size: 1.25rem;
	line-height: 1.75rem;
	color: var(--color-text-50);
}

.contact-card__value {
	display: inline-flex;
	flex-direction: column;
	align-items: flex-start;
	font-family: var(--font-display);
	font-weight: 400;
	font-size: 1.5rem;
	line-height: 2.25rem;
}

/* Underline is collapsed by default and grows from the link's start to its
   end on hover — matches the text's own width (not the card's), since
   `.contact-card__value` now shrink-wraps instead of stretching full-width.
   Plain CSS background bar, not an SVG `<img>` — the original underline.svg
   export had a near-zero viewBox width (0.0001) stretched via
   `preserveAspectRatio="none"`, which Chrome tolerates but Safari renders
   as an invisible line (WebKit doesn't reliably scale a stroke inside such
   an extreme non-uniform viewBox transform). */
.contact-card__underline {
	width: 100%;
	height: 1px;
	background: var(--color-text);
	transform: scaleX(0);
	transform-origin: left;
	transition: transform var(--transition-hover);
}

.contact-card:active .contact-card__underline {
	transform: scaleX(1);
}

/* ---------- Footer ---------- */
/* Grid, not flex — mobile needs to regroup the same 4 pieces (copy, contacts,
   divider, back-to-top) into a different visual cluster than desktop (see
   footer.php), and grid-template-areas can reposition each of them
   independently by name without any wrapper nesting. */
.site-footer {
	position: relative;
	display: grid;
	grid-template-columns: 1fr auto auto auto;
	grid-template-areas: "copy contacts divider backtotop";
	align-items: center;
	column-gap: 1.5rem;
	width: 100%;
	padding: 1.5rem 3.5rem;
}

.site-footer::before {
	content: "";
	position: absolute;
	left: 0;
	right: 0;
	/* -1px: sits exactly on the last block's own bottom frame row (see
	   .site-content > * + *), so the two coincide instead of doubling. */
	top: -1px;
	height: 1px;
	background: var(--color-border);
}

.site-footer__copy {
	grid-area: copy;
	font-size: 1rem;
	line-height: 1.5rem;
	color: var(--color-text-50);
}

.site-footer__contacts {
	grid-area: contacts;
	display: flex;
	align-items: center;
	gap: 1.5rem;
}

.site-footer__link {
	font-size: 1rem;
	line-height: 1.5rem;
	color: var(--color-text-50);
	transition: color var(--transition-hover);
}

.site-footer__link:active {
	color: var(--color-text);
}

.site-footer__divider {
	grid-area: divider;
	width: 1px;
	height: 1rem;
	background: var(--color-border);
}

.back-to-top {
	grid-area: backtotop;
	display: inline-flex;
	align-items: center;
	gap: 0.25rem;
	font-size: 1rem;
	line-height: 1.5rem;
	color: var(--color-text-50);
	transition: color var(--transition-hover);
}

.back-to-top:active {
	color: var(--color-text);
}

.back-to-top .icon-mask {
	transform: rotate(180deg);
}

/* ---------- Adaptive: below 1024px ---------- */
/* Below 374px: proportional shrink of the 375 mobile baseline (same root-
   scaling technique as the desktop 1024–1439/1921+ bands above — see
   "Root scale"). 375–699 is the mobile Figma frame's own fluid range (root
   stays flat 16px, exactly like 1440–1920 does for desktop). */

/* ==========================================================================
   Case page (desktop) — flexible body constructor, see inc/case-blocks.php.
   ========================================================================== */

/* ---------- Case hero ---------- */
/* .hero__table/.hero__cell* from the homepage hero are reused as-is below —
   the case hero table in Figma is pixel-identical (same padding, same type
   sizes, same name-cell span), just with different field labels. */
.case-hero {
	display: flex;
	flex-direction: column;
	justify-content: space-between;
	width: 100%;
	height: 25rem;
	outline: 1px solid var(--color-border);
	outline-offset: -1px;
}

.case-hero__headline {
	width: 100%;
	padding: 2.5rem 3rem;
	font-family: var(--font-display);
	font-weight: 400;
	font-size: 3.5rem;
	line-height: 4.25rem;
}

.case-hero__row {
	display: flex;
	align-items: flex-end;
	width: 100%;
}

/* width:50% + flex-shrink:0, not flex:1 0 0 — same box-sizing:border-box
   gotcha as .hero__text/.hero__table on the homepage: with a flex-basis of
   0, the side that carries its OWN padding (.case-hero__text does,
   .case-hero__table doesn't — its cells carry theirs) silently claims more
   than its fair share of the row. width:50% sizes both from the container
   directly instead, sidestepping it. Same fix applies to every other
   title/content or title/cards split on this page (.case-section__title
   vs __content, .case-cards__title-box vs __list). */
.case-hero__text {
	width: 50%;
	flex-shrink: 0;
	padding: 3rem;
	font-size: 1.25rem;
	line-height: 1.75rem;
}

.case-hero__table {
	width: 50%;
	flex-shrink: 0;
}

/* ---------- Case section (title + stacked subsections) ---------- */
.case-section {
	display: flex;
	align-items: flex-start;
	width: 100%;
	padding: 2.5rem 3rem 3rem;
	outline: 1px solid var(--color-border);
	outline-offset: -1px;
}

/* Empty when a section continues after an image split (see
   portfolio_dk_case_section_start) — stays in the flow so the content
   column keeps its half-width slot instead of stretching full width. */
.case-section__title {
	width: 50%;
	flex-shrink: 0;
	font-family: var(--font-display);
	font-weight: 400;
	font-size: 2.25rem;
	line-height: 3rem;
}

.case-section__content {
	width: 50%;
	flex-shrink: 0;
	display: flex;
	flex-direction: column;
	gap: 2rem; /* 32px — between .case-text subsections (О проекте/Роль и команда style) */
	padding-top: 0.5rem;
}

/* Sections built from .case-block subsections (Исследования/Структура и
   логика/Процесс style — a display-font subtitle over each block, not a
   small overline) space out wider: 40px, not the 32px above. Scoped with
   `:has()` instead of a second PHP-side modifier class — a section's
   content column is always ALL .case-text or ALL .case-block, never mixed
   (see case-content/cometa.php), so this reliably follows which one it is
   without touching every call site. Increased 2026-09-24 per Дарья's
   updated Figma spacing pass (node 636:329372). */
.case-section__content:has(> .case-block) {
	gap: 2.5rem; /* 40px */
}

/* ---------- Subsection: overline + text / overline + tags ---------- */
.case-text {
	display: flex;
	flex-direction: column;
	gap: 0.75rem; /* 12px — was 4px, increased 2026-09-24 per Дарья's updated Figma spacing pass (node 636:329372) */
	width: 100%;
}

.case-text__overline {
	font-size: 1rem;
	line-height: 1.5rem;
	color: var(--color-text-50);
}

.case-text__body {
	font-size: 1.25rem;
	line-height: 1.75rem;
}

.case-text__tags {
	width: 100%;
}

/* Zero-size flex item forcing a wrap at an exact point (see the '' sentinel
   in portfolio_dk_case_overline_tags) — standard flex-wrap line-break
   trick, not visible itself. */
.case-text__tags-break {
	flex-basis: 100%;
	width: 0;
	height: 0;
}

/* Breakpoint-specific breaks ('@desktop'/'@compact' sentinels) — the
   compact one is switched on inside the tablet/mobile blocks. */
.case-text__tags-break--compact {
	display: none;
}

/* Tablet/mobile-only line break in the hero name cell ("\n" in $name,
   see portfolio_dk_case_hero) — desktop keeps the name on one line. */
.hero__name-br {
	display: none;
}

/* Tablet-only title break ("|" in portfolio_dk_case_section_start). */
.case-section__br--tablet {
	display: none;
}

/* ---------- Subsection: subtitle + text + optional logos ---------- */
.case-block {
	display: flex;
	flex-direction: column;
	gap: 0.5rem;
	width: 100%;
}

.case-block__subtitle {
	font-family: var(--font-display);
	font-weight: 400;
	font-size: 1.5rem;
	line-height: 2.25rem;
}

.case-block__text {
	font-size: 1.25rem;
	line-height: 1.75rem;
	color: var(--color-text-50);
}

.case-block__logos {
	display: flex;
	align-items: flex-start;
	align-self: flex-start; /* hugs its 80px tiles like Figma, not the whole column */
	gap: 0;
	/* margin, not padding: the spacing belongs OUTSIDE the frame — as
	   padding it sat between the outline and the tiles, so the dividers
	   stopped 8px short of the top line. */
	margin: 0.5rem 0 0.25rem;
	outline: 1px solid var(--color-border);
	outline-offset: -1px;
}

/* border-left instead of every tile's own full outline — same
   single-line-per-edge fix as everywhere else in this file (see
   .case-insights); the row's own outline above is the perimeter. */
.case-block__logo:not(:first-child) {
	border-left: 1px solid var(--color-border);
}

.case-block__logo {
	display: flex;
	align-items: center;
	justify-content: center;
	width: 5rem;
	height: 5rem;
	flex-shrink: 0;
	overflow: hidden;
}

.case-block__logo img {
	/* Actual width/height set inline per logo (see
	   portfolio_dk_case_subtitle_text) — these icons are genuinely different
	   sizes in Figma, not a uniform grid. max-width/height just keeps any
	   future logo from overflowing its 80px tile. */
	max-width: 80%;
	max-height: 80%;
	object-fit: contain;
}

/* Cursor-following tooltip for the benchmarking logos (2026-09-24, per
   Дарья's Figma reference "Comment cursor", node 1306:448461 — same
   custom-cursor arrow, node 1306:448477, stacked directly above this
   pill, no gap). One shared element, created/positioned by
   logo-tooltip.js on hover of any .case-block__logo[data-tooltip] — never
   rendered server-side since its position depends entirely on live mouse
   coordinates. `position: fixed` (not absolute) so it tracks
   `clientX`/`clientY` directly regardless of GSAP ScrollSmoother's
   transform on the scrolled content, same reasoning as .site-header/
   .preloader already being fixed outside #smooth-wrapper. `top:0;left:0`
   is just the base — logo-tooltip.js moves it with GSAP's `x`/`y`
   (a `transform`, not `top`/`left` per mousemove) so the follow motion and
   fade can be eased instead of snapping every event.
   `pointer-events: none` so it can never itself intercept the hover/click
   it's only supposed to describe. Desktop-only per Дарья's explicit call
   (hover isn't a tablet/mobile concept here anyway) — logo-tooltip.js
   gates showing it to ≥1024px, and the media query below is a second,
   CSS-only guarantee if that JS check is ever wrong. */
.logo-tooltip {
	position: fixed;
	top: 0;
	left: 0;
	z-index: 10;
	display: none;
	min-width: 2rem;
	padding: 0.375rem 0.75rem;
	border-radius: 1rem;
	background: var(--color-text);
	border: 0.5px solid rgba(32, 32, 32, 0.2);
	box-shadow: 0 0.0625rem 0.0625rem rgba(15, 15, 15, 0.1), 0 0.125rem 0.25rem rgba(15, 15, 15, 0.21);
	font-family: var(--font-text);
	font-size: 0.875rem;
	line-height: 1.25rem;
	color: var(--color-bg);
	white-space: nowrap;
	pointer-events: none;
}

.logo-tooltip--visible {
	display: block;
}

/* Its own element, not the pill's own opacity, so the label can crossfade
   (fade out → swap text → fade in, all via GSAP in logo-tooltip.js) while
   the pill itself stays put and fully opaque as the mouse glides from one
   logo straight into the next. */
.logo-tooltip__text {
	display: inline-block;
}

@media (max-width: 1023px) {
	.logo-tooltip {
		display: none !important;
	}
}

/* ---------- Media blocks (full-width / square pair) ---------- */
/* Same visual recipe as .case-card__image/.concepts__cell (outline, 0.5px
   inset, hover-darken) kept as its own class since that's the existing
   precedent — each component repeats the small pattern under its own
   semantic name rather than cross-referencing another component's class.
   Pattern background is opt-in (.case-media--round only): these photos are
   PNGs with real alpha transparency (device-mockup renders on a
   transparent canvas, not a plain rectangular photo), and the diagonal
   pattern is only meant to show through that transparency for the rounded
   variant, where it fills the gap between the round photo clip and the
   square outer border — on a sharp-corner image it has nothing to fill and
   just shows through the photo's own transparent margins looking broken.
   Sharp-corner images get a plain solid fill instead (same as page bg). */
.case-media {
	position: relative;
	outline: 1px solid var(--color-border);
	outline-offset: -1px;
	/* 1px, not 0.5px: .img-box is position:relative, so it paints ABOVE this
	   element's outline — any overlap and the image hides the frame (was
	   happening on the right edge of every image). A full pixel of padding
	   keeps the image clear of the outline row. */
	padding: 1px;
	background-color: var(--color-bg);
}

.case-media--round {
	background-image: var(--pattern-tile);
	background-color: transparent;
}

/* ---------- Hover (real pointer devices only) ----------
   Touch has no real "hover" — mobile browsers fake it in by matching
   :hover on tap and then leaving it matched until the next tap lands
   somewhere else, which read as every button/card getting stuck in its
   hover look after a tap instead of releasing. :active alone (defined
   above, right next to each element's own rules) already does the right
   thing everywhere — it matches only while actually pressed, mouse or
   touch — so gating :hover to devices that can really hover leaves touch
   with just that correct press/release feedback, and changes nothing for
   an actual mouse. The burger button is excluded outright: opening it is
   a deliberate tap-triggered morph (see smooth-scroll.js), not a
   hover/press face-swap. */
@media (hover: hover) and (pointer: fine) {
	.site-logo:hover {
		color: var(--color-text-50);
	}

	.site-menu__link:hover .site-menu__link-stack {
		transform: translateY(-50%);
	}

	.pill-button:hover .pill-button__stack {
		transform: translateY(-50%);
	}

	.icon-button:not(.burger-button):hover .icon-button__stack,
	.case-card:hover .icon-button__stack {
		transform: translateY(-50%);
	}

	.case-card:hover .img-box img {
		transform: scale(1.04);
	}

	.case-card:hover .case-card__image {
		background-color: var(--color-pattern-hover);
	}

	.case-card:hover .case-card__content {
		background-color: var(--color-pattern-hover);
	}

	.contact-card:hover {
		background-color: var(--color-pattern-hover);
	}

	.contact-card:hover .contact-card__underline {
		transform: scaleX(1);
	}

	.site-footer__link:hover {
		color: var(--color-text);
	}

	.back-to-top:hover {
		color: var(--color-text);
	}

}

.case-media--wide {
	width: 100%;
	aspect-ratio: 1408 / 792;
}

.case-media--square {
	flex: 1 0 0;
	min-width: 0;
	aspect-ratio: 1 / 1;
}

.case-media-row {
	display: flex;
	width: 100%;
}

/* Same 1px overlap as .site-content's blocks, horizontally: the two
   squares' own frames share the middle line instead of doubling it. */
.case-media-row > * + * {
	margin-left: -1px;
}

.img-box--round {
	border-radius: 62.5rem;
}

/* ---------- Card-list section (hypotheses/results style) ---------- */
.case-cards {
	display: flex;
	align-items: flex-start;
	width: 100%;
	outline: 1px solid var(--color-border);
	outline-offset: -1px;
}

.case-cards__title-box {
	width: 50%;
	flex-shrink: 0;
	display: flex;
	padding: 2.5rem 3rem;
}

.case-cards__title {
	font-family: var(--font-display);
	font-weight: 400;
	font-size: 2.25rem;
	line-height: 3rem;
}

/* border-left drawn once here (one continuous line, full list height)
   instead of by every card's own left edge — see the .case-insights note
   further down for why per-item outlines don't reliably collapse into a
   single crisp line. .case-cards' own outline already covers every other
   outer edge (top/bottom/right of the whole title+list row), so this is
   the only perimeter piece the list needs to contribute itself. */
.case-cards__list {
	width: 50%;
	flex-shrink: 0;
	display: flex;
	flex-direction: column;
	border-left: 1px solid var(--color-border);
}

/* Card kind 1 — vertical, small round number badge. Was "frosted glass"
   (backdrop-filter:blur) — dropped site-wide 2026-09-24 (Дарья's call,
   after it caused a visible seam on adjacent-bordered cards elsewhere, see
   .case-block__stat/.case-insight-card below): invisible against this
   site's flat background regardless (nothing behind the card to blur), so
   there was nothing to lose by removing it everywhere, including here
   where it never visibly misbehaved. */
.case-card-glass {
	display: flex;
	align-items: flex-start;
	gap: 2.5rem;
	width: 100%;
	padding: 2.5rem 2.5rem 2.5rem 3rem;
}

/* Divider between stacked cards drawn once, by the lower card's own top
   edge — never both cards' touching edges at once (that's what produced
   the soft/doubled seam, same root cause as .case-insights below). First
   card needs none: its top is .case-cards' own perimeter. */
.case-card-glass:not(:first-child) {
	border-top: 1px solid var(--color-border);
}

.case-card-glass__content {
	flex: 1 0 0;
	min-width: 0;
	display: flex;
	flex-direction: column;
	gap: 0.75rem;
}

.case-card-glass__text {
	font-size: 1.25rem;
	line-height: 1.75rem;
}

.case-card-glass__number {
	flex-shrink: 0;
	width: 3rem;
	height: 3rem;
	border-radius: 6.25rem;
	background-color: var(--color-surface-8);
	display: flex;
	align-items: center;
	justify-content: center;
}

.case-card-glass__number span {
	font-family: var(--font-display);
	font-weight: 400;
	font-size: 1.125rem;
	line-height: 1.75rem;
	color: var(--color-text-50);
}

/* Card kind 2 — horizontal, big number box sharing the card's full height */
.case-card-h {
	display: flex;
	align-items: stretch;
	width: 100%;
}

.case-card-h:not(:first-child) {
	border-top: 1px solid var(--color-border);
}

/* border-right instead of outline — same single-line-per-edge fix as
   everywhere else on this page. .case-card-h__content needs no matching
   border-left: that would just be the same pixel drawn twice again. */
.case-card-h__number-box {
	flex-shrink: 0;
	display: flex;
	align-items: center;
	justify-content: center;
	padding: 3rem;
	border-right: 1px solid var(--color-border);
}

.case-card-h__number {
	width: 4rem;
	height: 4rem;
	border-radius: 6.25rem;
	background-color: var(--color-surface-8);
	display: flex;
	align-items: center;
	justify-content: center;
}

.case-card-h__number span {
	font-family: var(--font-display);
	font-weight: 400;
	font-size: 1.5rem;
	line-height: 2.25rem;
	color: var(--color-text-50);
}

.case-card-h__content {
	flex: 1 0 0;
	min-width: 0;
	display: flex;
	align-items: center;
	padding: 2rem 3rem;
}

.case-card-h__text {
	font-size: 1.25rem;
	line-height: 1.75rem;
}

/* ---------- .case-block trailing stat row (driver-app case, "Интервью с
   водителями" style — a subtitle_text block ending in 2+ small stat cards
   instead of, or alongside, logos) ---------- */
.case-block__stats {
	display: flex;
	align-items: stretch;
	width: 100%;
	/* margin, not padding — same as .case-block__logos: padding put the gap
	   inside the frame, so the dividers didn't reach the top line. */
	margin-top: 0.5rem;
	/* Perimeter drawn once, here, instead of by the edge cards — see the
	   note on .case-insights below for why. */
	outline: 1px solid var(--color-border);
	outline-offset: -1px;
}

.case-block__stat {
	flex: 1 0 0;
	min-width: 0;
	display: flex;
	flex-direction: column;
	gap: 1.5rem;
	padding: 1rem 1.25rem;
	/* No backdrop-filter — with 3 cards sharing edges in a row, blurring
	   each one's own border independently smeared the shared seams into a
	   visibly thicker/lighter line at every junction (Дарья caught this
	   2026-09-24, the reason blur got dropped site-wide). */
}

/* Each internal divider drawn by exactly ONE card (its own right edge),
   never two overlapping outlines — see .case-insights below, same fix,
   same reason. Last card needs none: its right edge is the row's own
   perimeter, already drawn by .case-block__stats' outline above. */
.case-block__stat:not(:last-child) {
	border-right: 1px solid var(--color-border);
}

.case-block__stat-number {
	font-family: var(--font-display);
	font-weight: 400;
	font-size: 1.5rem;
	line-height: 2.25rem;
}

.case-block__stat-label {
	font-size: 1rem;
	line-height: 1.5rem;
	color: var(--color-text-50);
}

/* ---------- "Ключевые инсайты"-style section — 2-col card grid, title in
   the first cell (see portfolio_dk_case_insights_start) ---------- */
/* Every internal grid line used to be drawn TWICE — once by each of the two
   cards sharing it — via the same per-item `outline` + -0.5px-offset trick
   used everywhere else on this page. That trick only produces a crisp 1px
   line when both copies land on the exact same physical pixel, which broke
   at some viewport widths/pixel ratios (confirmed 2026-09-24, Дарья's
   report + pixel-sampled): every shared edge, and especially the 4-way
   junctions between cards, rendered as a soft ~2px blended smear instead of
   a hairline. Fixed by drawing every line exactly ONCE: the grid's own
   `outline` draws the perimeter, and each cell draws only its own
   right/top internal edge (never both, never left/bottom) — no two
   elements ever paint the same pixel, so there's nothing left to
   misalign. */
.case-insights {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	width: 100%;
	outline: 1px solid var(--color-border);
	outline-offset: -1px;
}

/* Targets every direct child (title-box AND cards) so the divider between
   the title cell and the first card is drawn the same way as every other
   internal line. Column 1 = odd children (title-box is always #1), column
   2 needs no right border — that edge is the grid's own perimeter. */
.case-insights > *:nth-child(odd) {
	border-right: 1px solid var(--color-border);
}

/* Row 1 is always children 1–2 (title-box + first card) and needs no top
   border — that edge is the grid's own perimeter. Every child from the
   3rd on is row 2+, regardless of total card count (odd counts just leave
   the grid's last cell empty, uneffected by this rule). */
.case-insights > *:nth-child(n + 3) {
	border-top: 1px solid var(--color-border);
}

.case-insights__title-box {
	display: flex;
	align-items: flex-start;
	align-self: stretch;
	padding: 2.5rem 3rem;
}

.case-insights__title {
	font-family: var(--font-display);
	font-weight: 400;
	font-size: 2.25rem;
	line-height: 3rem;
}

.case-insight-card {
	display: flex;
	flex-direction: column;
	justify-content: space-between;
	align-self: stretch;
	height: 19rem;
	padding: 3rem;
	/* No backdrop-filter — same reasoning as .case-block__stat above: this
	   grid has 4-way border junctions between cards, and blurring each
	   card's own edge independently made those junctions read as a visibly
	   thicker/lighter cross instead of a clean line. */
}

.case-insight-card__content {
	display: flex;
	flex-direction: column;
	gap: 0.75rem;
	width: 100%;
}

.case-insight-card__text {
	font-size: 1.25rem;
	line-height: 1.75rem;
}

/* ---------- "Другие кейсы" (bottom of every case page) ---------- */
.case__other-cases-title-box {
	display: flex;
	align-items: center;
	width: 100%;
	padding: 2.5rem 3rem;
	outline: 1px solid var(--color-border);
	outline-offset: -1px;
}

.case__other-cases-title {
	font-family: var(--font-display);
	font-weight: 400;
	font-size: 3.5rem;
	line-height: 4.25rem;
}

@media (max-width: 374px) {
	html {
		font-size: calc(16px * (100vw / 375px));
	}
}

/* Mobile layout (≤699px) — structural reflow to the 375 mobile Figma frame
   (node 562:325014). Sizing values are that frame's own px/16, not scaled
   down from the desktop ones: hover mechanics, borders, the diagonal
   pattern, button anatomy etc. all carry over unchanged from the desktop
   pass (per Дарья) — only layout and type scale differ here. */
@media (max-width: 699px) {
	/* Preloader — own (smaller) mobile design, not a scaled-down desktop
	   pill (Figma node 958:86949, "preloader-mobile"): 96px tall, full
	   viewport width minus a 16px gutter each side, 36px/48px type instead
	   of the desktop pill's 160px/56px/68px. */
	.preloader__pill {
		width: calc(100vw - 2rem);
		height: 6rem;
	}

	.preloader__count {
		font-size: 2.25rem;
		line-height: 3rem;
	}

	/* Outer gutter to the border lines drops from 16px to 8px on mobile
	   (Figma: hero/cases/concepts/contacts sit at x=8 in a 375 frame) */
	.site-content {
		padding-left: 0.5rem;
		padding-right: 0.5rem;
	}

	/* Header */
	.site-header {
		padding: calc(1rem + env(safe-area-inset-top)) calc(2rem + env(safe-area-inset-right)) 1rem calc(2rem + env(safe-area-inset-left));
	}

	.site-logo {
		font-size: 1.125rem;
		line-height: 1.5rem;
	}

	.site-menu {
		gap: 0.5rem;
	}

	.site-menu__link {
		/* Nav links move into the full-screen .mobile-menu panel (below)
		   instead of showing inline here. */
		display: none;
	}

	.burger-button {
		display: inline-flex;
	}

	/* Hero — text-then-table stacked instead of side by side, table drops
	   from 4 columns to 2 (cell positions reassigned by nth-child since the
	   DOM order matches the desktop layout, not the mobile one) */
	.hero {
		height: auto;
	}

	.hero__headline {
		padding: 1.5rem;
		font-size: 1.75rem;
		line-height: 2.25rem;
	}

	.hero__row {
		flex-direction: column;
		align-items: stretch;
	}

	.hero__text {
		width: 100%;
		padding: 1.5rem;
		font-size: 1rem;
		line-height: 1.5rem;
	}

	.hero__table {
		width: 100%;
		grid-template-columns: repeat(2, minmax(0, 1fr));
		grid-template-rows: repeat(5, auto);
		border-left: none; /* full width here — its left edge is .hero's own */
	}

	/* Desktop/tablet's border-right/border-top rules (see the base
	   .hero__cell block) assume the desktop 4-col grid shape — mobile
	   rearranges every cell into a 2-col/5-row grid (see the repositioning
	   rules below), so which edges are "internal" is different here and
	   needs its own version, not just smaller values. Reset first, then
	   redeclare for this grid's actual shape: col 1 is the name cell (row
	   1–2) + cells 2/4/6 (rows 3–5) — those 4 need border-right (col 2 is
	   the last column, needs none). Every cell except the two in row 1
	   (name, and the date cell via .hero__cell--center) needs border-top. */
	.hero__cell {
		padding: 1rem 1.5rem;
		font-size: 0.875rem;
		line-height: 1.25rem;
	}

	/* The reset has to match the base rules' own `:nth-child` specificity —
	   a plain `.hero__cell { border-*: none }` loses to them, which left
	   the desktop border-left (cells 3/5/7) and border-top/right (cell 8)
	   painting over this grid: a doubled center line in rows 3–5 and a
	   doubled top edge on the date cell (caught by pixel audit 2026-09-24). */
	.hero__cell:nth-child(n) {
		border-left: none;
		border-right: none;
		border-top: none;
	}

	.hero__cell:nth-child(1),
	.hero__cell:nth-child(2),
	.hero__cell:nth-child(4),
	.hero__cell:nth-child(6) {
		border-right: 1px solid var(--color-border);
	}

	.hero__cell:nth-child(n):not(.hero__cell--name):not(.hero__cell--center) {
		border-top: 1px solid var(--color-border);
	}

	.hero__cell--name {
		grid-column: 1 / span 1;
		grid-row: 1 / span 2;
		/* Figma left-aligns every cell on mobile, incl. this one — the
		   desktop centering above doesn't carry over. This cell alone needs
		   flex-direction:column since it spans 2 rows and its content
		   centers vertically (not horizontally) within that taller box. */
		flex-direction: column;
		align-items: flex-start; /* base .hero__cell sets align-items:center, which — once flex-direction flips to column here — centers the HORIZONTAL axis instead of the vertical one */
		justify-content: center;
		text-align: left;
		gap: 0.125rem;
		font-size: 1rem;
		line-height: 1.5rem;
	}

	.hero__cell--center {
		grid-column: 2;
		grid-row: 1;
		justify-content: flex-start;
		text-align: left;
	}

	.hero__cell--status {
		justify-content: flex-start;
	}

	.hero__cell--status {
		grid-column: 2;
		grid-row: 2;
	}

	/* the 6 plain label/value cells (Опыт, 7 лет, Сегмент, B2B…, Платформа,
	   Web…) have no individual class — they're the 2nd–7th .hero__cell in
	   DOM order, reflowed here to the mobile grid's rows 3–5 */
	.hero__cell:nth-child(2) {
		grid-column: 1;
		grid-row: 3;
	}

	.hero__cell:nth-child(3) {
		grid-column: 2;
		grid-row: 3;
	}

	.hero__cell:nth-child(4) {
		grid-column: 1;
		grid-row: 4;
	}

	.hero__cell:nth-child(5) {
		grid-column: 2;
		grid-row: 4;
	}

	.hero__cell:nth-child(6) {
		grid-column: 1;
		grid-row: 5;
	}

	.hero__cell:nth-child(7) {
		grid-column: 2;
		grid-row: 5;
	}

	/* Cases — the 2-across row just stacks; the featured/plain image-shape
	   split (rounded only on the first card) is unchanged from desktop */
	.case-row {
		flex-direction: column;
	}

	/* The 2nd card's border-left (see the base .case-row rule) was for
	   sitting beside the 1st card — stacked here, it'd just be a stray
	   vertical line down the left edge. Its own border-top (from the
	   unconditional .case-row .case-card__image rule) already provides
	   the divider from the card above, whichever one that now is. */
	.case-row .case-card:not(:first-child) .case-card__image,
	.case-row .case-card:not(:first-child) .case-card__content {
		border-left: none;
	}

	.case-card__content {
		padding: 1.5rem;
	}

	.case-card__info {
		gap: 0.75rem;
		padding-right: 1.25rem;
	}

	.case-card__tags {
		font-size: 0.875rem;
		line-height: 1.25rem;
	}

	.case-card__title {
		font-size: 1.125rem;
		line-height: 1.5rem;
	}

	.case-card__desc {
		font-size: 0.875rem;
		line-height: 1.25rem;
	}

	.case-card .icon-button {
		width: 2.25rem;
		height: 2.25rem;
	}

	.case-card .icon-button__face {
		width: 2.25rem;
		height: 2.25rem;
	}

	.pill-button-static--soon {
		padding: 0.5rem 1rem;
		font-size: 0.875rem;
		line-height: 1.25rem;
	}

	/* Concepts — single column of equal wide-ratio images instead of the
	   2-square + 1-wide alternating grid; Dribbble button drops to the
	   smaller (CV-button-sized) mobile variant */
	.concepts__head {
		padding: 1.5rem;
	}

	.concepts__title {
		font-size: 1.5rem;
		line-height: 1.75rem;
	}

	.pill-button--dribbble {
		height: 2.5rem;
	}

	/* height set explicitly to match — see the ~line 421 comment on the
	   desktop .pill-button--cv/--dribbble .pill-button__face rules */
	.pill-button--dribbble .pill-button__face {
		height: 2.5rem;
		padding: 0.5rem 1.25rem;
		font-size: 1rem;
		line-height: 1.5rem;
	}

	.concepts__grid-row {
		flex-direction: column;
	}

	/* The 2 square cells stack instead of sitting side by side here (see
	   .concepts__grid-row above) — the divider between them needs to move
	   from the desktop/tablet left edge to a top edge, matching. */
	.concepts__cell--square:not(:first-child) {
		border-left: none;
		border-top: 1px solid var(--color-border);
	}

	/* 2026-08-25: was forced to the wide ratio (1408/792) on mobile — Дарья's
	   updated mobile Figma frame now keeps squares square here too. */
	.concepts__cell--square {
		flex: none;
		width: 100%;
		aspect-ratio: 1 / 1;
	}

	/* Contacts — title stacks above the list instead of sitting beside it */
	.contacts {
		flex-direction: column;
	}

	.contacts__title-box {
		width: 100%;
		padding: 1.5rem;
	}

	.contacts__title {
		font-size: 1.5rem;
		line-height: 1.75rem;
	}

	.contacts__list {
		width: 100%;
		/* .contacts stacks title-box above the list here instead of beside
		   it — the divider base's border-left exists for doesn't apply,
		   and left uncancelled it'd double with .contacts' own edge (same
		   fix as .case-cards__list, see there). */
		border-left: none;
		/* …but the divider is still needed, just horizontal now: nothing
		   else draws the line between the title box and the first card
		   (Дарья caught it missing on iPhone, 2026-09-24). */
		border-top: 1px solid var(--color-border);
	}

	.contact-card__icon-box {
		padding: 1.5rem;
	}

	.contact-card__icon {
		width: 3rem;
		height: 3rem;
	}

	.contact-card__icon .icon-24 {
		width: 1.25rem;
		height: 1.25rem;
	}

	.contact-card__info {
		padding: 1.5rem;
	}

	.contact-card__label {
		font-size: 0.875rem;
		line-height: 1.25rem;
	}

	.contact-card__value {
		font-size: 1.125rem;
		line-height: 1.5rem;
	}

	/* Footer — regroups into 2 columns (contacts alone left; back-to-top +
	   copy stacked right) instead of desktop's copy-left/cluster-right row;
	   see footer.php for why this is a flat grid, not nested wrappers */
	.site-footer {
		grid-template-columns: 1fr auto;
		grid-template-rows: auto 1fr;
		grid-template-areas:
			"contacts backtotop"
			"contacts copy";
		align-items: start;
		column-gap: 1rem;
		padding: 1.25rem 2rem;
	}

	.site-footer__contacts {
		flex-direction: column;
		align-items: flex-start;
		gap: 0.75rem;
	}

	.site-footer__divider {
		display: none;
	}

	.back-to-top {
		justify-self: end;
	}

	.site-footer__copy {
		align-self: end;
		justify-self: end;
		text-align: right;
	}

	.site-footer__copy,
	.site-footer__link,
	.back-to-top {
		font-size: 0.875rem;
		line-height: 1.25rem;
	}

	/* Full-screen mobile menu (node 746:75325). Fixed below the header, not
	   inside it — same reasoning as .site-header sitting outside
	   #smooth-wrapper. Starts translated fully above the viewport; JS
	   (toggleMobileMenu in smooth-scroll.js) animates it down into view with
	   GSAP, so the "closed" position here only matters as the state GSAP
	   animates FROM before first open / snaps back TO on close. */
	.mobile-menu {
		display: flex;
		position: fixed;
		top: var(--header-height);
		left: 0;
		right: 0;
		/* Explicit height (dvh), not `bottom:0` — same reasoning as
		   .preloader: `bottom:0` sizes against the LAYOUT viewport, which
		   doesn't track the browser's own toolbar hiding/showing as you
		   scroll, so the panel stopped short of the real bottom edge once
		   that happened ("борода" — a sliver of the page showing under
		   it). `dvh` tracks the actual visible area continuously, so
		   there's nothing here left for JS to resync on resize anymore
		   either (still kept, harmless, in smooth-scroll.js, in case some
		   browser's `dvh` support is shakier than its resize events). The
		   safe-area padding keeps the menu links themselves clear of the
		   home-indicator strip — only the background needs to reach it. */
		height: calc(100vh - var(--header-height));
		height: calc(100dvh - var(--header-height));
		z-index: 2;
		background: var(--color-bg);
		padding: 0 0.5rem calc(0.5rem + env(safe-area-inset-bottom));
		transform: translateY(-100%);
		pointer-events: none;
	}

	.mobile-menu--open {
		pointer-events: auto;
	}

	.mobile-menu__panel {
		display: flex;
		flex-direction: column;
		align-items: center;
		justify-content: center;
		gap: 2rem;
		width: 100%;
		/* Explicit, not relying on the parent flex container's default
		   cross-axis stretch — its own border only reached as far as the
		   nav links' content needed, short of the actual bottom of the
		   panel (dvh/JS-driven, see .mobile-menu and
		   syncMobileMenuHeight) whenever that content didn't already fill
		   it: background reached the true edge, this border didn't. */
		height: 100%;
		outline: 1px solid var(--color-border);
		outline-offset: -1px;
		padding: 1.5rem;
	}

	.mobile-menu__link {
		font-family: var(--font-display);
		font-weight: 400;
		font-size: 1.5rem;
		line-height: 1.75rem;
		color: var(--color-text);
		text-align: center;
		transition: color var(--transition-hover);
	}

	.mobile-menu__link:active {
		color: var(--color-text-50);
	}

	@media (hover: hover) and (pointer: fine) {
		.mobile-menu__link:hover {
			color: var(--color-text-50);
		}
	}

	/* ---------- Case page — fully stacked (title above content everywhere,
	   unlike tablet which keeps the two-column title/content split) ---------- */
	.case-hero {
		height: auto;
	}

	.case-hero__headline {
		padding: 1.5rem;
		font-size: 1.75rem;
		line-height: 2.25rem;
	}

	.case-hero__row {
		flex-direction: column;
	}

	.case-hero__text {
		width: 100%;
		padding: 1.5rem;
		font-size: 1rem;
		line-height: 1.5rem;
	}

	.case-hero__table {
		width: 100%;
	}

	.case-section {
		flex-direction: column;
		align-items: stretch;
		gap: 1.25rem;
		padding: 1.5rem;
	}

	/* A section continuing after an image split (see
	   portfolio_dk_case_section_start) renders an empty title — on
	   desktop/tablet that's needed to reserve the column's width, but
	   stacked here it's just a blank box plus an unwanted gap above the
	   content. Collapse it out of the flow entirely on this breakpoint
	   only. */
	.case-section__title:empty {
		display: none;
	}

	.case-section__title {
		width: 100%;
		font-size: 1.5rem;
		line-height: 1.75rem;
	}

	.case-section__content {
		width: 100%;
		gap: 1.25rem;
		padding-top: 0;
	}

	.case-text {
		gap: 0.5rem;
	}

	.case-text__overline {
		font-size: 0.875rem;
		line-height: 1.25rem;
	}

	.case-text__body {
		font-size: 1rem;
		line-height: 1.5rem;
	}

	/* .case-text__tags reuses .case-card__tags (badge-sized text on an
	   actual card) — at this breakpoint that resolves smaller (14px) than
	   the running text around it (.case-text__body, 16px). Figma confirms
	   this tag list should match the body copy exactly, not the card
	   badge size. Comes after .case-card__tags in source order so it wins
	   at equal specificity. */
	.case-text__tags {
		font-size: 1rem;
		line-height: 1.5rem;
	}

	.case-block__subtitle {
		font-size: 1.125rem;
		line-height: 1.5rem;
	}

	.case-block__text {
		font-size: 1rem;
		line-height: 1.5rem;
	}

	.case-block__logos {
		align-self: stretch; /* tiles split the full row evenly here */
		margin: 0.25rem 0;
	}

	.case-block__logo {
		width: auto;
		height: auto;
		flex: 1 0 0;
		aspect-ratio: 1 / 1;
	}

	.case-block__logo img {
		/* Overrides the inline width/height set per logo for desktop (see
		   portfolio_dk_case_subtitle_text) — at this width Figma abandons
		   each logo's own size in favor of evenly dividing the row, so the
		   inline px values need to lose here. */
		width: 68.75% !important;
		height: 68.75% !important;
		max-width: none;
		max-height: none;
	}

	/* ---------- Driver-app responsive pieces (2026-09-24, Figma
	   1300:434456 tablet / 1300:435350 mobile) ---------- */
	.hero__name-br {
		display: inline;
	}

	.case-text__tags-break--desktop {
		display: none;
	}

	.case-text__tags-break--compact {
		display: block;
	}

	/* The desktop 40px (.case-section__content:has(> .case-block)) outranks
	   the plain .case-section__content gap above by specificity, so it
	   needs restating here — Figma is 24px between subtitle blocks. */
	.case-section__content:has(> .case-block) {
		gap: 1.5rem;
	}

	/* Stat row → 2 columns, an odd last card spans the full row (Figma:
	   8 | 48 / 63). Grid, not flex-wrap, so pixel-snap.js can snap the
	   column line (it switches to grid mode on its own, see there). */
	.case-block__stats {
		display: grid;
		grid-template-columns: repeat(2, minmax(0, 1fr));
	}

	.case-block__stat {
		gap: 1.25rem;
	}

	/* :nth-child(n) to outrank the desktop `:not(:last-child)` rule. */
	.case-block__stat:nth-child(n) {
		border-right: none;
	}

	.case-block__stat:nth-child(odd):not(:last-child) {
		border-right: 1px solid var(--color-border);
	}

	.case-block__stat:nth-child(n + 3) {
		border-top: 1px solid var(--color-border);
	}

	.case-block__stat:last-child:nth-child(odd) {
		grid-column: 1 / -1;
	}

	.case-block__stat-number {
		font-size: 1.125rem;
		line-height: 1.5rem;
	}

	.case-block__stat-label {
		font-size: 0.875rem;
		line-height: 1.25rem;
	}

	/* Insights: rows size to their content (desktop's fixed 304px overflowed
	   here); space-between keeps each row's texts bottom-aligned like
	   Figma, gap is the minimum badge→text distance. */
	.case-insights__title-box {
		padding: 1.5rem;
	}

	.case-insight-card {
		height: auto;
		gap: 2rem;
		padding: 1.5rem;
	}

	.case-insight-card__text {
		font-size: 1rem;
		line-height: 1.5rem;
	}

	.case-insights__title {
		font-size: 1.5rem;
		line-height: 1.75rem;
	}

	/* Single column: title on top, cards stacked. Reset with the base
	   rules' own specificity, then every item after the title gets a top
	   divider (the perimeter is the grid's outline). */
	.case-insights {
		grid-template-columns: minmax(0, 1fr);
	}

	.case-insights > *:nth-child(n) {
		border-right: none;
		border-top: none;
	}

	.case-insights > *:nth-child(n + 2) {
		border-top: 1px solid var(--color-border);
	}

	.case-media-row {
		flex-direction: column;
	}

	.case-media-row > * + * {
		margin-left: 0;
		margin-top: -1px;
	}

	/* Pairs flagged $swap_mobile render bottom-first here (Figma's mobile
	   frame orders them that way so two phone mockups don't stack into
	   what reads as one tall phone). The 1px overlap has to follow the
	   visual order, not the DOM one. */
	.case-media-row--swap-mobile > :first-child {
		order: 2;
		margin-top: -1px;
	}

	.case-media-row--swap-mobile > :last-child {
		margin-top: 0;
	}

	/* 2026-08-25: Дарья revised the mobile Figma frame — square pairs now
	   stay square (stacked, still 1/1) instead of stretching to the wide
	   ratio. Kept as an explicit rule rather than relying on the base
	   `flex: 1 0 0` (row-oriented) surviving the switch to column layout —
	   flex-basis:0 on the now-vertical main axis with no defined container
	   height could resolve to 0 before aspect-ratio has a chance to act. */
	.case-media--square {
		flex: none;
		width: 100%;
		aspect-ratio: 1 / 1;
	}

	.case-cards {
		flex-direction: column;
		align-items: stretch;
	}

	.case-cards__title-box {
		width: 100%;
		padding: 1.5rem;
	}

	.case-cards__title {
		font-size: 1.5rem;
		line-height: 1.75rem;
	}

	.case-cards__list {
		width: 100%;
		/* .case-cards stacks title-box above list here instead of beside it
		   (see .case-cards above) — the divider base's border-left exists
		   for doesn't apply in a stacked layout, and left uncancelled it'd
		   draw a redundant vertical line down the left edge, doubling up
		   with .case-cards' own outline. */
		border-left: none;
		/* The title/list divider turns horizontal here — see .contacts__list. */
		border-top: 1px solid var(--color-border);
	}

	.case-card-glass {
		flex-direction: column;
		align-items: flex-start;
		gap: 2rem;
		padding: 1.5rem;
	}

	.case-card-glass__number {
		order: -1;
		width: 2.25rem;
		height: 2.25rem;
	}

	.case-card-glass__number span {
		font-size: 0.875rem;
		line-height: 1.25rem;
	}

	.case-card-glass__text {
		font-size: 1rem;
		line-height: 1.5rem;
	}

	.case-card-h {
		flex-direction: column;
		align-items: stretch;
	}

	.case-card-h__number-box {
		border-right: none;
		padding: 1.5rem 1.5rem 0;
		/* Base rule centers the circle for desktop's square box — here the
		   box is full-width, so that would center it in the middle of the
		   card instead of pinning it to the corner like Figma does. */
		justify-content: flex-start;
	}

	.case-card-h__number {
		width: 2.25rem;
		height: 2.25rem;
	}

	.case-card-h__number span {
		font-size: 0.875rem;
		line-height: 1.25rem;
	}

	.case-card-h__content {
		padding: 1.25rem 1.5rem 1.5rem;
	}

	.case-card-h__text {
		font-size: 1rem;
		line-height: 1.5rem;
	}

	.case__other-cases-title-box {
		padding: 1.5rem;
	}

	.case__other-cases-title {
		font-size: 1.5rem;
		line-height: 1.75rem;
	}
}

/* ---------- Tablet layout (700–1023px) ---------- */
/* Its own Figma frame (node 760:79377, "main-table"), not a scaled mobile
   layout and not a shrunk desktop one. Structurally it mostly reuses
   DESKTOP's arrangement — 4-column hero table, featured card + row of 2 for
   cases, the alternating 2-square/1-wide concepts grid, side-by-side
   contacts, one-row footer — so most of those rules are left untouched here
   (no override = desktop default applies). Only the hero's text+table stack
   vertically like mobile does. Nearly every value that DOES change below
   happens to match what mobile already uses (same padding/font-size
   tokens) — that's Figma's own authoring choice for this frame, confirmed
   against it directly, not an assumption carried over from mobile. */
@media (min-width: 700px) and (max-width: 1023px) {
	/* Preloader — same smaller design as mobile (Figma node 958:86932,
	   "preloader-tablet", is identical to the mobile frame — 96px tall,
	   viewport width minus a 16px gutter each side, 36px/48px type). */
	.preloader__pill {
		width: calc(100vw - 2rem);
		height: 6rem;
	}

	.preloader__count {
		font-size: 2.25rem;
		line-height: 3rem;
	}

	.site-content {
		padding-left: 0.5rem;
		padding-right: 0.5rem;
	}

	/* Header — full nav shows (no burger at this width), just tighter spacing */
	.site-header {
		padding: calc(1rem + env(safe-area-inset-top)) calc(2rem + env(safe-area-inset-right)) 1rem calc(2rem + env(safe-area-inset-left));
	}

	.site-menu {
		gap: 1.5rem;
	}

	/* Hero — text above table (like mobile), but the table itself keeps
	   desktop's 4-column grid and center-aligned name/date cells, just at a
	   shorter row height and smaller type */
	.hero {
		height: auto;
	}

	.hero__headline {
		padding: 1.5rem;
		font-size: 1.75rem;
		line-height: 2.25rem;
	}

	.hero__row {
		flex-direction: column;
		align-items: stretch;
	}

	.hero__text {
		width: 100%;
		padding: 1.5rem;
		font-size: 1rem;
		line-height: 1.5rem;
	}

	.hero__table {
		width: 100%;
		grid-template-rows: repeat(3, 3.5rem);
		border-left: none; /* full width here — its left edge is .hero's own */
	}

	.hero__cell {
		padding: 1.125rem 1.5rem;
		font-size: 0.875rem;
		line-height: 1.25rem;
	}

	.hero__cell--name {
		font-size: 1rem;
		line-height: 1.5rem;
	}

	/* Cases — featured card + a row of 2, same shape as desktop */
	.case-card__content {
		padding: 1.5rem;
	}

	.case-card__info {
		gap: 0.75rem;
		padding-right: 1.25rem;
	}

	.case-card__tags {
		font-size: 0.875rem;
		line-height: 1.25rem;
	}

	.case-card__title {
		font-size: 1.125rem;
		line-height: 1.5rem;
	}

	.case-card__desc {
		font-size: 0.875rem;
		line-height: 1.25rem;
	}

	.case-card .icon-button {
		width: 2.25rem;
		height: 2.25rem;
	}

	.case-card .icon-button__face {
		width: 2.25rem;
		height: 2.25rem;
	}

	.pill-button-static--soon {
		padding: 0.5rem 1rem;
		font-size: 0.875rem;
		line-height: 1.25rem;
	}

	/* Concepts — same alternating 2-square/1-wide grid as desktop */
	.concepts__head {
		padding: 1.5rem;
	}

	.concepts__title {
		font-size: 1.5rem;
		line-height: 1.75rem;
	}

	.pill-button--dribbble {
		height: 2.5rem;
	}

	/* height set explicitly to match — see the ~line 421 comment on the
	   desktop .pill-button--cv/--dribbble .pill-button__face rules */
	.pill-button--dribbble .pill-button__face {
		height: 2.5rem;
		padding: 0.5rem 1.25rem;
		font-size: 1rem;
		line-height: 1.5rem;
	}

	/* Contacts — stays side-by-side like desktop */
	.contacts__title-box {
		padding: 1.5rem;
	}

	.contacts__title {
		font-size: 1.5rem;
		line-height: 1.75rem;
	}

	.contact-card__icon-box {
		padding: 1.5rem;
	}

	.contact-card__icon {
		width: 3rem;
		height: 3rem;
	}

	.contact-card__icon .icon-24 {
		width: 1.25rem;
		height: 1.25rem;
	}

	.contact-card__info {
		padding: 1.5rem;
	}

	.contact-card__label {
		font-size: 0.875rem;
		line-height: 1.25rem;
	}

	.contact-card__value {
		font-size: 1.125rem;
		line-height: 1.5rem;
	}

	/* Footer — stays desktop's single row (copy | contacts | divider | back-to-top) */
	.site-footer {
		padding: 1.25rem 2rem;
		column-gap: 1.25rem;
	}

	.site-footer__copy,
	.site-footer__link,
	.back-to-top {
		font-size: 0.875rem;
		line-height: 1.25rem;
	}

	/* ---------- Case page — keeps the two-column title/content split from
	   desktop (it's a relative 50/50, so it shrinks along for free); only
	   the hero restacks like it does on mobile, and type/spacing sizes drop
	   to the same values mobile uses — confirmed against Figma directly,
	   not assumed from the mobile pass. ---------- */
	.case-hero {
		height: auto;
	}

	.case-hero__headline {
		padding: 1.5rem;
		font-size: 1.75rem;
		line-height: 2.25rem;
	}

	.case-hero__row {
		flex-direction: column;
	}

	.case-hero__text {
		width: 100%;
		padding: 1.5rem;
		font-size: 1rem;
		line-height: 1.5rem;
	}

	.case-hero__table {
		width: 100%;
	}

	.case-section {
		padding: 1.5rem;
	}

	.case-section__title {
		font-size: 1.5rem;
		line-height: 1.75rem;
	}

	.case-section__content {
		gap: 1.25rem;
		padding-top: 0;
	}

	.case-text {
		gap: 0.5rem;
	}

	.case-text__overline {
		font-size: 0.875rem;
		line-height: 1.25rem;
	}

	.case-text__body {
		font-size: 1rem;
		line-height: 1.5rem;
	}

	/* .case-text__tags reuses .case-card__tags (badge-sized text on an
	   actual card) — at this breakpoint that resolves smaller (14px) than
	   the running text around it (.case-text__body, 16px). Figma confirms
	   this tag list should match the body copy exactly, not the card
	   badge size. Comes after .case-card__tags in source order so it wins
	   at equal specificity. */
	.case-text__tags {
		font-size: 1rem;
		line-height: 1.5rem;
	}

	.case-block__subtitle {
		font-size: 1.125rem;
		line-height: 1.5rem;
	}

	.case-block__text {
		font-size: 1rem;
		line-height: 1.5rem;
	}

	.case-block__logos {
		align-self: stretch; /* tiles split the full row evenly here */
		margin: 0.25rem 0;
	}

	.case-block__logo {
		width: auto;
		height: auto;
		flex: 1 0 0;
		aspect-ratio: 1 / 1;
	}

	.case-block__logo img {
		width: 68.75% !important;
		height: 68.75% !important;
		max-width: none;
		max-height: none;
	}

	.case-section__br--tablet {
		display: inline;
	}

	/* ---------- Driver-app responsive pieces (2026-09-24, Figma
	   1300:434456 tablet / 1300:435350 mobile) ---------- */
	.hero__name-br {
		display: inline;
	}

	.case-text__tags-break--desktop {
		display: none;
	}

	.case-text__tags-break--compact {
		display: block;
	}

	/* The desktop 40px (.case-section__content:has(> .case-block)) outranks
	   the plain .case-section__content gap above by specificity, so it
	   needs restating here — Figma is 24px between subtitle blocks. */
	.case-section__content:has(> .case-block) {
		gap: 1.5rem;
	}

	/* Stat row → 2 columns, an odd last card spans the full row (Figma:
	   8 | 48 / 63). Grid, not flex-wrap, so pixel-snap.js can snap the
	   column line (it switches to grid mode on its own, see there). */
	.case-block__stats {
		display: grid;
		grid-template-columns: repeat(2, minmax(0, 1fr));
	}

	.case-block__stat {
		gap: 1.25rem;
	}

	/* :nth-child(n) to outrank the desktop `:not(:last-child)` rule. */
	.case-block__stat:nth-child(n) {
		border-right: none;
	}

	.case-block__stat:nth-child(odd):not(:last-child) {
		border-right: 1px solid var(--color-border);
	}

	.case-block__stat:nth-child(n + 3) {
		border-top: 1px solid var(--color-border);
	}

	.case-block__stat:last-child:nth-child(odd) {
		grid-column: 1 / -1;
	}

	.case-block__stat-number {
		font-size: 1.125rem;
		line-height: 1.5rem;
	}

	.case-block__stat-label {
		font-size: 0.875rem;
		line-height: 1.25rem;
	}

	/* Insights: rows size to their content (desktop's fixed 304px overflowed
	   here); space-between keeps each row's texts bottom-aligned like
	   Figma, gap is the minimum badge→text distance. */
	.case-insights__title-box {
		padding: 1.5rem;
	}

	.case-insight-card {
		height: auto;
		gap: 2rem;
		padding: 1.5rem;
	}

	.case-insight-card__text {
		font-size: 1rem;
		line-height: 1.5rem;
	}

	.case-insights__title {
		font-size: 1.5rem;
		line-height: 2.25rem;
	}

	.case-cards__title-box {
		padding: 1.5rem;
	}

	.case-cards__title {
		font-size: 1.5rem;
		line-height: 1.75rem;
	}

	.case-card-glass {
		flex-direction: column;
		align-items: flex-start;
		gap: 2rem;
		padding: 1.5rem;
	}

	.case-card-glass__number {
		order: -1;
		width: 2.25rem;
		height: 2.25rem;
	}

	.case-card-glass__number span {
		font-size: 0.875rem;
		line-height: 1.25rem;
	}

	.case-card-glass__text {
		font-size: 1rem;
		line-height: 1.5rem;
	}

	.case-card-h {
		flex-direction: column;
		align-items: stretch;
	}

	.case-card-h__number-box {
		border-right: none;
		padding: 1.5rem 1.5rem 0;
		/* Base rule centers the circle for desktop's square box — here the
		   box is full-width, so that would center it in the middle of the
		   card instead of pinning it to the corner like Figma does. */
		justify-content: flex-start;
	}

	.case-card-h__number {
		width: 2.25rem;
		height: 2.25rem;
	}

	.case-card-h__number span {
		font-size: 0.875rem;
		line-height: 1.25rem;
	}

	.case-card-h__content {
		padding: 1.25rem 1.5rem 1.5rem;
	}

	.case-card-h__text {
		font-size: 1rem;
		line-height: 1.5rem;
	}

	.case__other-cases-title-box {
		padding: 1.5rem;
	}

	.case__other-cases-title {
		font-size: 1.5rem;
		line-height: 1.75rem;
	}
}
