/* ============================================================================
   HOME COVERS — the "Selected work" cards on index.html. Ported out of
   lab-cover-layout.html (TASK-216) after TASK-199…206; the lab file is deleted.
   The numbers below are Pavlo's, read off the Design Layer tool `home-covers`
   (serve.py --port 8811, key C) over four rounds; the comments say whose call
   each one is. Losing variants (waves `redraw`, the hatched island, the pill,
   the App Store render, fan of three) are gone with the lab.
   ========================================================================= */

:root {
  /* Pavlo's numbers, read off the TASK-199 panel — the line he sent:
     layout=bleed side=right mat=60% gap=48 h=460 over=0 name=1.70
     hover=on hdur=260→540 fan=44 corners=off */
  --mat: 60;      /* % of the card width taken by the material zone */
  --zgap: 48;     /* px between the two zones */
  --cardh: 460;   /* px minimum card height */
  --namek: 1.7;   /* multiplier on the case name's type size */
  --hdur: 400;    /* ms — how long the card takes to become a picture (Pavlo, 04.09, from his panel) */
  --fan: 44;      /* px — how far the extra screens ride out from under the plate */
  --head-h: 56;   /* px — the hover header: client + case name above the picture (Pavlo, 03.09) */
  --phone-h: 90;  /* % of the zone height for the front phone — "чтобы над ними был спейсинг сверху и снизу" (Pavlo, 04.09: 90) */
  --aid-h: 30;    /* % of the zone height for the hearing aid — "уменьшить, может быть, в кружочек" */
  /* the Light callout, in % of the front phone's own width and height —
     read off the old App Store render so it keeps its proportions: the plate
     was 106 % of the body width and stood 3 % below its top. Here it starts
     just under the island, over the October row it magnifies. */
  --plate-w: 108; --plate-y: 7; --plate-x: 0;
  --plate-lift: 100;  /* % — how high the plate floats (the size of its shadow) */
  --plate-shade: 100; /* % — how dark that shadow reads */
  /* — the drawn phone body (Pavlo, 06.09: "дай мне какие-то контролы, чтобы я
     мог эту толщину рамки регулировать, которая белая до чёрной идёт… чуть-чуть
     больше контроля"): the bezel is the paper ring between the ink edge and
     the screen, in % of the body width (Apple's 17 pt of 427 = 3.98); the
     edge is the ink line; the island is a share of Apple's 125 × 37 pt. */
  --bezel: 2.7;    /* % of the body width — the ring (Pavlo, 07.09) */
  --edge: 1.25;     /* px — the outline of the body */
  --corner: 100;    /* % of Apple's 55 pt display radius */
  /* 07.09 (Pavlo: "телефоны слишком чёрные… или аутлайнами, или более серенькими,
     конечно, чтобы я управлял"). The body line is no longer ink: --body-tone
     mixes it from paper-grey (0) to ink (100), --body-fill greys the bezel ring
     itself, so "outline" and "a grey object" are the two ends of one slider and
     not two variants. The numbers below are his own, off the tool the same day:
     an all but invisible line (tone 14) around a grey body (fill 60), the bezel
     2.1 → 2.7 % of the body ("рамки слишком маленькие получились, кропнул
     слишком сильно"). */
  --body-tone: 14;  /* % — 0 paper-grey line, 100 ink */
  --body-fill: 60;  /* % — how grey the bezel ring itself reads */
  /* — the Dynamic Island, back as an outline (Pavlo, 07.09: "островок сделать
     аккуратненьким аутлайном, просто таким сереньким… и внутри него ещё камеру").
     Apple: 125 × 37.33 pt on a 427 pt body, 11 pt below the screen top. Width 0
     takes it off the phone entirely. */
  --isl-w: 29;      /* % of the body width (Apple 125/427 = 29.3) */
  --isl-y: 1.4;     /* % of the body width, down from the top of the screen */
  --isl-edge: 1;    /* px — its own line */
  --isl-tone: 36;   /* % — same ramp as the body line */
  --isl-fill: 30;   /* % — 0 hollow, 100 a grey pill */
  --isl-cam: 32;    /* % of the island height — the camera dot, 0 for none */
  /* — the fan's far phones (Pavlo, 04.09: "из пустоты красиво проявлялись") — */
  --fan-delay: 30;  /* % of --hdur before the far phones begin to show */
  --fan-start: 90;  /* % — their scale relative to the side phones as they start */
  /* — the McKinsey waves, variant `scale` (Pavlo, 04.09: "растянуть, чтобы за
     края экрана сильно вылазили… и чуть-чуть двигать по ховеру") — */
  --wv-scale: 1.6; --wv-x: 0; --wv-y: -1; --wv-shift: 3; --wv-tone: 80;
  /* — the Collibra dot field, rebuilt 06.09. Pavlo killed the lava-lamp:
     "точечку сделать помельче… зелёный цвет всё-таки не нравится, можно просто
     чуть потемнее сделать местами… и чуть помельче сеточку саму… крутилки
     багово работают, она расслаивается — переделай полностью, мне нужно чтобы
     аккуратненько сеточка была, и когда водишь мышкой чтобы точки чуть
     увеличивались". So: no SVG clusters, no green, no cross-fade. One even
     grid drawn by a repeating radial-gradient, a couple of darker patches on
     the same grid, and a third copy with bigger dots revealed by a soft round
     mask that follows the cursor. */
  --dots-pitch: 20;  /* px — the step of the grid */
  --dots-size: 65;   /* % of the 1.5px base dot */
  --dots-tone: 22;   /* % — how dark the field reads against the paper */
  --dots-dark: 170;  /* % — how much darker the patches are */
  --dots-halo: 230;  /* px — the radius the cursor magnifies within */
  --dots-grow: 180;  /* % — how much bigger a dot gets under the cursor */
  /* the field clears itself under the hover header (Pavlo, 06.09: "не нравится,
     когда на ховере невозможно прочитать надписи, потому что там точки по фону…
     чтобы под ней точек особо не было, плавное затухание в этот угол, кругом
     вокруг… потому что читается плохо"). A soft round hole, not a rectangle:
     the corner keeps the paper texture, only the line of type sits on white. */
  --dots-clear-r: 300;  /* px — how far the hole reaches at its widest */
}


/* ══ the list ═════════════════════════════════════════════════════════════
   The frame the live section already uses: a 2px ink box with 2px ink rules
   between the cards. In one column every card is a full-width row. */
.cv-list {
  --pad: clamp(20px, 2.5vw, 36px);
  --gap: calc(var(--zgap) * .5px);
  list-style: none; margin: 0; padding: 0;
  border: var(--rule-bold) solid var(--rule);
  background: var(--white);
}
.cv-card { display: block; position: relative; }
.cv-card + .cv-card { border-top: var(--rule-bold) solid var(--rule); }

.cv-link {
  display: grid;
  grid-template-rows: auto 1fr auto;
  grid-template-areas: "top" "copy" "foot";
  column-gap: var(--gap);
  padding: var(--pad);
  min-height: calc(var(--cardh) * 1px);
  text-decoration: none; color: inherit;
  transition: background var(--dur-fast) var(--ease);
}
/* The selection ring lies ON the carrying line: the link's edge is flush with
   the 2px contour on every side, so a 2px outset ring covers that line exactly
   and the reader sees one line, not two. */
.cv-link:hover,
.cv-link:focus-visible {
  outline: none; position: relative; z-index: 2;
  background: var(--bg);
  box-shadow: 0 0 0 var(--rule-bold) var(--accent);
}

/* ══ the text zone — three tiers, one weight per line ══════════════════════
   Pavlo, 02.09: "Company и Калибра написаны разной плотностью — хаотично";
   "полезно Enterprise и Data Governance — домейн и тип ситуации"; "Feature
   redesign — о чём они говорят вообще?". So: the top row names the client and
   the years in one register; the name and the description stay as they were;
   the bottom row is domain + platform, two tags, no case type, no "via". */
.cv-top {
  grid-area: top;
  display: flex; align-items: baseline; justify-content: flex-start;
  gap: var(--sp-3) var(--sp-5); flex-wrap: wrap;
  padding-bottom: var(--sp-3); border-bottom: var(--rule-hair) solid var(--rule-muted);
}
.cv-copy { grid-area: copy; display: block; margin-block: var(--sp-5); }
.cv-foot {
  grid-area: foot;
  display: flex; align-items: center; justify-content: flex-start;
  gap: 6px var(--sp-5); flex-wrap: wrap;
  padding-top: var(--sp-3); border-top: var(--rule-hair) solid var(--rule-muted);
}
/* qualified by the link because `.label` sets its colour later in styles.css */
.cv-link .cv-client,
.cv-link .cv-years { color: var(--text); font-weight: var(--wt-med); }
.cv-link .cv-name { margin: 0; font-size: calc(var(--fs-h3) * var(--namek)); }
.cv-desc { margin: var(--sp-4) 0 0; font-size: var(--fs-small); color: var(--text-muted); max-width: 46ch; }
/* "там, где я фаундер, добавить не плашку, а пил типа, капсулку" — and then the
   square chip won (Pavlo, 03.09): the pill was the one rounded thing on a
   radius-0 site. */
.cv-role {
  display: inline-block; padding: 3px 12px 4px;
  font-family: var(--font-label); font-size: var(--fs-caption);
  letter-spacing: var(--tr-label); text-transform: uppercase; line-height: 1.2;
  background: var(--chip-bg); color: var(--chip-fg); border-radius: 0;
}

/* ══ the material zone — two families, one scene ══════════════════════════
   Pavlo, 02.09: "два типа тайлов: где мобильные апки и где платформы". A scene
   is a box that fills the material zone; every shot is centred in it and moved
   out by its own width (--sx) and height (--sy). Percentages, not pixels, so
   the same numbers hold at 1440 and on a phone. On hover the zone widens to
   the whole card (see below), the scene drifts with it and the fan opens:
   "они подъезжают дальше… и появляются ещё дополнительные экраны". */
:root {
  /* — the phone fan (Light, Sonova) — */
  --spread-rest: 50;   /* % of a phone's width the side screens stand out at rest */
  --spread-hover: 130; /* …and under the cursor */
  --tilt: 3.5;         /* deg — the side screens lean away (Pavlo, 04.09) */
  --side-scale: 90;    /* % — the side phones are a little behind (Pavlo, 04.09) */
  --decor-delay: 140;  /* ms — the decor arrives after the screens */
  --over: 0;           /* px — the front screen crossing the card's own line; Pavlo set 0 (03.09) */
  /* — the desktop stack (Collibra, McKinsey). Pavlo, 06.09: "эти хаотичные
     стопки мне не нравятся… выровненные по горизонтальной центральной линии,
     без ховера один экран, и за ним чуть прячется второй справа… когда я
     разворачиваю, чтобы три разворачивалось: один посередине побольше и два
     по краям поменьше, может быть тоже чуть-чуть веером". So the stack is now
     the SAME machine as the phone fan — one centre screen, two symmetric
     sides — and not three planes each with its own pair of coordinates. Every
     plane sits on one horizontal centre line; only the rest state is
     asymmetric (the second screen peeks out to the right). */
  --dk-front-h: 74;     /* % of the zone height — the centre screen */
  --dk-side-scale: 86;  /* % — the two side screens, of the centre one */
  --dk-spread-rest: 22; /* % of its own width the second screen stands out at rest */
  --dk-spread-hover: 56;/* …and under the cursor, both sides, symmetric */
  --dk-tilt: 2;         /* deg — the fan's lean */
  --dk-y: 0;            /* % — the whole stack down off the centre line */
  --dk-rest-y: -5;      /* % — the peeking screen above (−) or below the line at rest */
  --dk-up: 100;         /* % of the header's height the stack rises into on hover
                           ("чтобы наверх вылазило в этот заголовок") */
}
/* overflow: hidden, not clip-path — clip-path hides the paint but leaves the
   layout overflow, and the page grew to 1789px wide (measured). The overhang
   ("экран вылазит за уголком за линию", the legacy Light trick, finding 6) is
   made by extending the zone itself past the card's line by --over px in the
   wide layout below; z-index 1 puts the zone above the next card's rule, so
   the crossing screen lies ON the line, not under it.
   The clipping sits on the SCENE, not on the zone: the back layer below has to
   reach above the zone on hover (§2), so the zone itself must not clip. */
.cv-mat { grid-area: mat; display: block; min-width: 0; position: relative; z-index: 1; }
.cv-scene { position: absolute; inset: 0; overflow: hidden; }
/* the decor lives in a back layer clipped to the card box (the zone itself is
   taller by --over on each side): a screen may cross the card's rule, a
   circle or a colour field never does — the hairline stays whole.
   Pavlo, 04.09: "кружок фоновый не кропать, а прям поверх хедера… чтобы он
   прям шёл в край листа" — so under the cursor the layer grows upward by the
   header's height, to the card's own top line; the header text is a sibling
   of the zone with a higher z, so it reads over the circle. */
.cv-back { position: absolute; inset: calc(var(--over) * 1px) 0; overflow: hidden; z-index: 0; --up: 0px; top: calc(var(--over) * 1px - var(--up)); }
.cv-decor { z-index: 0; }
.cv-shot, .cv-decor {
  position: absolute; top: 50%; left: 50%;
  --sx: 0; --sy: 0; --rot: 0; --sc: 1; --op: 1;
  transform: translate(-50%, -50%) translate(calc(var(--sx) * 1%), calc(var(--sy) * 1%))
             rotate(calc(var(--rot) * 1deg)) scale(var(--sc));
  opacity: var(--op);
}
.cv-shot {
  background: var(--bg);
  /* a 3px paper gap lifts a front screen off the one behind it — print depth,
     no shadow, no blur (the site has neither) */
  box-shadow: 0 0 0 3px var(--bg);
}
.cv-shot img { display: block; width: 100%; height: 100%; object-fit: cover; object-position: top; border: var(--edge-rule); box-sizing: border-box; }
/* height-driven, so the same numbers hold at every width; max-width keeps the
   front screen inside a narrower zone (at 1200 it overflowed the left edge by
   66 px, measured) — the image covers, the crop just tightens.
   ONE aspect ratio per PACK (Pavlo, 04.09: "должны быть одинаковые экраны в
   пачке… если не размеры, то хотя бы пропорции — это помогает чувствовать
   плановость"; 06.09 he re-cut every screen in Figma, so a pack is now uniform
   at the source and nothing is cropped): Collibra 1600 × 1021, McKinsey
   1600 × 1138, each card carrying its own --shot-ar. */
.cv-mat--desk  .cv-shot { aspect-ratio: var(--shot-ar, 1600 / 1138); max-width: 96%; }
.cv-mat--desk  .cv-shot--front { height: calc(var(--dk-front-h) * 1%); }
.cv-mat--desk  .cv-shot--side,
.cv-mat--desk  .cv-shot--extra { height: calc(var(--dk-front-h) * var(--dk-side-scale) / 100 * 1%); }
/* z: back layer, then extras and the back screen behind the front one; in
   the fan the far phones stand BELOW the side ones (Pavlo, 04.09: "как будто
   на первый план вылазят от второго телефона, из-под прозрачности" — they used
   to be z 2 over z 1, measured) */
.cv-shot--side  { z-index: 1; }
.cv-shot--front { z-index: 3; }
.cv-shot--extra { z-index: 2; --op: 0; }
.cv-mat--phone .cv-shot--extra { z-index: 0; }
.cv-mat--phone .cv-shot--side { --sc: calc(var(--side-scale) / 100); }

/* — the phone fan: 3 at rest, 5 under the cursor (Pavlo, 04.09: five) — */
.cv-mat--phone .cv-shot--left  { --sx: calc(-1 * var(--spread-rest)); --rot: calc(-1 * var(--tilt)); }
.cv-mat--phone .cv-shot--right { --sx: var(--spread-rest); --rot: var(--tilt); }
/* at rest the far phones stand exactly behind the side ones — same x, a
   little smaller — so under the cursor they come out from behind a phone,
   not from a transparent nowhere (Pavlo, 04.09: "из пустоты красиво
   проявлялись") */
.cv-mat--phone .cv-shot--far-left  { --sx: calc(-1 * var(--spread-rest)); --rot: calc(-1 * var(--tilt)); --sc: calc(var(--side-scale) / 100 * var(--fan-start) / 100); }
.cv-mat--phone .cv-shot--far-right { --sx: var(--spread-rest); --rot: var(--tilt); --sc: calc(var(--side-scale) / 100 * var(--fan-start) / 100); }
/* the front phone is a share of the zone height (the zone is already extended
   by --over px each side, so 100 % is exactly "card + overhang") */
.cv-mat--phone .cv-shot { height: calc(var(--phone-h) * 1%); }
/* the phone's own width in px, for the bezel and the island below. A % bezel
   is taken of the containing block — the scene — so at rest it was 31 px on a
   233 px phone, and on hover, the zone widening 762 → 1306 px, it grew and the
   picture inside shrank 171 → 127 px (measured, TASK-205). cqw did not resolve
   on a shot whose width comes from aspect-ratio, hence the arithmetic: the zone
   height is the card height (--cardh) plus the overhang, the body is 427/886. */
:root { --zone-h: calc(var(--cardh) * 1px + var(--over) * 2px); }
.cv-mat--phone .cv-shot { --pw: calc(var(--zone-h) * var(--phone-h) / 100 * 0.4819); }

/* — the desktop stack at rest: ONE screen on the centre line, the second
     hiding behind it to the right, a touch off the line; the third stands
     exactly behind the centre one, invisible, so under the cursor it comes out
     from behind a screen and not from nowhere (the same trick as the far
     phones). The scene rises into the header on hover, see below. */
.cv-mat--desk .cv-shot--front { --sx: 0; --sy: var(--dk-y); }
.cv-mat--desk .cv-shot--side  { --sx: var(--dk-spread-rest); --sy: calc(var(--dk-y) + var(--dk-rest-y)); --rot: var(--dk-tilt); }
.cv-mat--desk .cv-shot--extra { --sx: 0; --sy: var(--dk-y); --rot: calc(-1 * var(--dk-tilt)); --op: 0; }

/* — decor (colour register won, Pavlo 03.09) — */
.cv-decor img, .cv-decor svg { display: block; width: 100%; height: 100%; }
.cv-decor--circle { height: 108%; aspect-ratio: 1; --sx: 26; --sy: -14; }
/* the aid sits in the lower right corner of the zone and stays there while the
   zone grows leftwards; its size is a slider; it lives in a white disc
   (Pavlo, 03.09: "уменьшить, может быть, в кружочек" → 04.09: circle) */
.cv-decor--aid { height: calc(var(--aid-h) * 1%); aspect-ratio: 1; top: auto; left: auto; right: 5%; bottom: 7%; transform: none; z-index: 4; border-radius: 50%; box-shadow: 0 0 0 1px var(--rule-muted); }
.cv-decor--aid img { object-fit: contain; }
/* the line field covers the whole zone: behind a stack at the right it was
   invisible — the screens covered every pixel of it (measured, TASK-205), and
   at 1× the dashes were too thin to read at all (Pavlo, 04.09: "фоновые волны
   их вообще не видно"). So the same Figma lines are blown up past the zone by
   --wv-scale and nudged under the cursor (variant `scale`, his pick 06.09;
   the redrawn arcs lost and are gone from the SVG).
   Tone is a share between --text-muted and --text. */
.cv-decor--lines  { inset: 0; top: 0; left: 0; width: 100%; height: 100%; transform: none; --op: 1; }
.cv-decor--lines svg { width: 100%; height: 100%; display: block; }
.cv-decor--lines .wv {
  transform-box: fill-box;
  color: color-mix(in srgb, var(--text) calc(var(--wv-tone) * 1%), var(--text-muted));
  transform: translate(calc(var(--wv-x) * 1%), calc(var(--wv-y) * 1%)) scale(var(--wv-scale));
  transform-origin: 50% 50%;
}
/* Collibra's drawn decor — the dot field. Three layers on ONE grid, so
   nothing can ever come apart when a knob moves: the pitch is a single
   variable and all three read it.
     base   — the even field, in ink at --dots-tone;
     dark   — the same grid, darker, shown only where two soft patches are
              ("чуть потемнее местами"), so the field has weight without a
              second colour;
     cursor — the same grid with fatter dots, masked to a disc of --dots-halo
              around the pointer; a plain magnifier, no drift, no fade.
   The pointer's place arrives as --mx/--my in px from the script below; with
   no pointer the mask sits off the field and nothing shows. */
/* --dots-clear is the radius of the hole under the hover header, and it has to
   GROW as the header arrives. A mask-image cannot be transitioned, and an
   unregistered custom property inside one only ever jumps; registered as a
   number it interpolates, and the mask is recomputed each frame. */
@property --dots-clear { syntax: "<number>"; inherits: true; initial-value: 0; }
.cv-decor--dots {
  inset: 0; top: 0; left: 0; width: 100%; height: 100%; transform: none; --op: 1;
  --dot-r: calc(1.5px * var(--dots-size) / 100);
  --ink: color-mix(in srgb, var(--text) calc(var(--dots-tone) * 1%), transparent);
  --ink-dark: color-mix(in srgb, var(--text) calc(var(--dots-tone) * var(--dots-dark) / 100 * 1%), transparent);
  --mx: -999px; --my: -999px;
  --dots-clear: 0;
  /* an ellipse, because the thing it clears is a LINE of type: wide, low, its
     centre on the header's own baseline band and a --pad in from the card's
     left line — the very place the client and the case name stand. At radius 0
     the gradient collapses and the whole field is opaque, so at rest there is
     no hole at all and nothing changes. */
  -webkit-mask-image: radial-gradient(calc(var(--dots-clear) * 1px) calc(var(--dots-clear) * .5px)
      at calc(var(--pad) + 160px) calc(var(--head-h) * .5px),
      transparent 0%, transparent 34%, #000 100%);
          mask-image: radial-gradient(calc(var(--dots-clear) * 1px) calc(var(--dots-clear) * .5px)
      at calc(var(--pad) + 160px) calc(var(--head-h) * .5px),
      transparent 0%, transparent 34%, #000 100%);
}
.cv-decor--dots::before, .cv-decor--dots::after,
.cv-decor--dots .dots-hi {
  content: ""; position: absolute; inset: 0;
  background-position: 0 0;
  background-size: calc(var(--dots-pitch) * 1px) calc(var(--dots-pitch) * 1px);
}
/* base */
.cv-decor--dots::before {
  background-image: radial-gradient(circle at 50% 50%, var(--ink) 0 var(--dot-r), transparent var(--dot-r));
}
/* the darker patches — two soft ellipses of the same grid */
.cv-decor--dots::after {
  background-image: radial-gradient(circle at 50% 50%, var(--ink-dark) 0 var(--dot-r), transparent var(--dot-r));
  -webkit-mask-image: radial-gradient(58% 78% at 22% 34%, #000 0%, transparent 72%),
                      radial-gradient(46% 62% at 78% 76%, #000 0%, transparent 70%);
          mask-image: radial-gradient(58% 78% at 22% 34%, #000 0%, transparent 72%),
                      radial-gradient(46% 62% at 78% 76%, #000 0%, transparent 70%);
}
/* the cursor magnifier */
.cv-decor--dots .dots-hi {
  background-image: radial-gradient(circle at 50% 50%, var(--ink-dark) 0 calc(var(--dot-r) * var(--dots-grow) / 100), transparent calc(var(--dot-r) * var(--dots-grow) / 100));
  -webkit-mask-image: radial-gradient(circle calc(var(--dots-halo) * 1px) at var(--mx) var(--my), #000 0%, rgba(0,0,0,.55) 45%, transparent 100%);
          mask-image: radial-gradient(circle calc(var(--dots-halo) * 1px) at var(--mx) var(--my), #000 0%, rgba(0,0,0,.55) 45%, transparent 100%);
}

/* — device: the outline phone (Pavlo, 03.09) —
   The Dynamic Island was dropped on 06.09 ("он чёрный, очень слишком
   контрастный" — the hatched variant lost too) and comes back on 07.09 drawn
   the way he asked for: a grey hairline pill with a camera dot inside, on the
   same tone ramp as the body line.
   No licence-clean photoreal PNG exists (TASK-202: Meta and Apple resources are
   non-commercial, the rest paywalled or unlicensed), so the body is drawn in
   CSS on Apple's own numbers: screen 393×852 pt, display radius 55, bezel ≈17,
   Dynamic Island 125×37 at 11 pt. Body = 427×886. The bezel is taken of the
   phone's own width (--pw), so it stays the same at rest and under the cursor. */
.cv-mat--phone .cv-shot {
  aspect-ratio: 427 / 886; padding: calc(var(--pw) * var(--bezel) / 100); box-sizing: border-box;
  border-radius: calc(16.9% * var(--corner) / 100) / calc(8.13% * var(--corner) / 100);
  /* the line and the ring, both on the grey→ink ramp; --gray-6 is the paper end */
  --body-line: color-mix(in srgb, var(--text) calc(var(--body-tone) * 1%), var(--gray-6));
  border: calc(var(--edge) * 1px) solid var(--body-line);
  background: color-mix(in srgb, var(--gray-6) calc(var(--body-fill) * 1%), var(--bg));
}
.cv-mat--phone .cv-shot img { border: 0; border-radius: calc(14% * var(--corner) / 100) / calc(6.45% * var(--corner) / 100); }
/* the island: sizes in px off the phone's own width (--pw), so it holds at rest,
   under the cursor and on a narrow screen alike. The camera is a dot painted
   inside it — a pseudo-element cannot hold a child, and one more absolute span
   per shot would be four spans of markup for a 3 px circle.
   min(1, --isl-w) is the off switch: at width 0 the line and the dot go too,
   instead of leaving a 2 px blob under the status bar. */
.cv-mat--phone .cv-shot::before {
  content: ''; position: absolute; z-index: 2;
  --isl-h: calc(var(--pw) * var(--isl-w) / 100 / 3.348);
  --isl-line: color-mix(in srgb, var(--text) calc(var(--isl-tone) * 1%), var(--gray-6));
  --cam-r: calc(var(--isl-h) * var(--isl-cam) / 200);
  left: 50%; top: calc(var(--pw) * (var(--bezel) + var(--isl-y)) / 100);
  width: calc(var(--pw) * var(--isl-w) / 100); height: var(--isl-h);
  box-sizing: border-box; transform: translateX(-50%);
  border-radius: 999px;
  border: calc(var(--isl-edge) * 1px * min(1, var(--isl-w))) solid var(--isl-line);
  background-color: color-mix(in srgb, var(--gray-6) calc(var(--isl-fill) * 1%), transparent);
  background-image: radial-gradient(circle at calc(100% - var(--isl-h) * .5) 50%,
    var(--isl-line) 0 calc(var(--cam-r) * min(1, var(--isl-w))),
    transparent calc(var(--cam-r) * min(1, var(--isl-w)) + .5px));
}
/* ── the callout plate (Light) ─────────────────────────────────────────────
   Pavlo, 06.09: "в Light на экране с календарём была летающая плашка, на
   которой подписан увеличенный кусок интерфейса… примерно в таких же
   пропорциях, но уже более чисто, с телефоном таким же, как везде". So the
   App Store render is gone — the front phone is the same drawn frame as
   everywhere — and the plate is his own Figma frame ("floating zoomed
   interfaced") riding on the body: a zoom of the October summary, standing
   over the very row it magnifies. It is a plate of this site's kind, a
   hairline edge and a 3 px paper gap, not the render's soft blur.
   Its size and place are sliders in the Design Layer tool. */
.cv-plate {
  position: absolute; z-index: 5;
  left: 50%; top: calc(var(--plate-y) * 1%);
  width: calc(var(--plate-w) * 1%);
  transform: translateX(calc(-50% + var(--plate-x) * 1%));
  aspect-ratio: 1136 / 237;
  background: var(--white); box-sizing: border-box;
  /* The one place the system gives up its flatness (Pavlo, 06.09: "это тот
     случай, где не надо по брутализму… это elevated элемент и нам придётся
     тут нарушить брутализм"). Two shadows: a tight contact one that holds the
     edge, and a wide soft one that lifts the plate off the screen. No border —
     the hairline read as a drawn card, not as a floating one; and the frame's
     own dark bottom strip ("чёрное подчёркивание") is cropped out of the PNG. */
  border: 0;
  box-shadow:
    0 0 0 .5px rgba(0, 0, 0, calc(.07 * var(--plate-shade) / 100)),
    0 calc(1px * var(--plate-lift) / 100) calc(2px * var(--plate-lift) / 100) rgba(0, 0, 0, calc(.10 * var(--plate-shade) / 100)),
    0 calc(7px * var(--plate-lift) / 100) calc(18px * var(--plate-lift) / 100) rgba(0, 0, 0, calc(.26 * var(--plate-shade) / 100));
}
/* it is not a screen: the phone's own radius and edge must not reach it */
.cv-mat--phone .cv-shot .cv-plate img { border: 0; border-radius: 0; }
.cv-plate img { display: block; width: 100%; height: 100%; object-fit: cover; }

/* ══ the wide layout ══════════════════════════════════════════════════════
   Wrapped in a min-width query on purpose (finding 5): below 761px none of
   this exists, and the phone block wins without a specificity fight. */
@media (min-width: 761px) {
  /* one expression, no JS: the percentage is taken of the card's content box
     and each column gives up half the gap */
  .cv-link {
    grid-template-columns:
      calc((100% - var(--gap)) * (100 - var(--mat)) / 100)
      calc((100% - var(--gap)) * var(--mat) / 100);
    grid-template-areas: "top mat" "copy mat" "foot mat";
  }
  /* "встык": the material runs into three of the card's own rules and is
     bounded by them; only the side facing the copy carries a picture edge.
     Reference #9 (Saint Urbain): frames edge to edge, the grid doing the framing. */
  .cv-mat { margin-block: calc(-1 * (var(--pad) + var(--over) * 1px)); margin-right: calc(var(--pad) * -1); }
  /* first and last cards keep their edge: crossing the list's outer 2px box
     reads as a bug, not as depth (TASK-199) */
  .cv-card:first-child .cv-mat, .cv-card:last-child .cv-mat { --over: 0; }
  /* the overhang is a phone gesture: a desktop screen crossing the rule shows
     a flat strip of its toolbar in the neighbour's card and reads as a glitch */
  .cv-mat--desk { --over: 0; }

  /* ══ HOVER · the card gives itself to the picture ════════════════════════
     Pavlo, 02.09: "на хавер вообще должно исчезать весь текст… чтобы вся эта
     область была занята чистым изображением". The plate does not grow, it
     RIDES OVER the copy with negative margins (finding 8: animating the grid
     columns squeezes the text into a gutter on the way). The copy column
     relates to the material column as (100 − mat) : mat, so one formula. */
  .cv-mat { --eat: 0px; --edgeout: 0px; --blockout: 0px; }
  .cv-mat {
    transition: margin calc(var(--hdur) * 1ms) var(--ease);
  }
  /* the copy is gone before the screens land on it: with a transparent scene
     there is no plate to hide it, so the fade runs at 60% of the ride. On the
     way BACK it waits: the card's height returns first, the copy comes after
     (Pavlo, 04.09: "как будто прощёлкивает верхняя часть" — measured, the
     header held opacity 1 for 200 ms while the copy was already coming in). */
  .cv-copy, .cv-top, .cv-foot {
    transition: opacity calc(var(--hdur) * .5ms) var(--ease) calc(var(--hdur) * .5ms);
  }
  .cv-link:hover .cv-copy, .cv-link:hover .cv-top, .cv-link:hover .cv-foot,
  .cv-link:focus-visible .cv-copy, .cv-link:focus-visible .cv-top, .cv-link:focus-visible .cv-foot {
    transition: opacity calc(var(--hdur) * .6ms) var(--ease);
  }
  .cv-back { transition: top calc(var(--hdur) * 1ms) var(--ease); }
  .cv-link:hover .cv-back, .cv-link:focus-visible .cv-back { --up: calc(var(--head-h) * 1px); }
  .cv-link:hover .cv-mat,
  .cv-link:focus-visible .cv-mat {
    /* + --pad: the plate goes to the card's own line, not to the copy's edge */
    --eat: calc(100% * (100 - var(--mat)) / var(--mat) + var(--gap) + var(--pad));
    --edgeout: calc(-1 * var(--pad));
    --blockout: calc(-1 * (var(--pad) + var(--over) * 1px));
    z-index: 3;
    margin-block: var(--blockout);
    margin-left: calc(-1 * var(--eat)); margin-right: var(--edgeout);
  }
  /* the copy fades slightly before the plate reaches it */
  .cv-link:hover .cv-copy, .cv-link:hover .cv-top, .cv-link:hover .cv-foot,
  .cv-link:focus-visible .cv-copy, .cv-link:focus-visible .cv-top, .cv-link:focus-visible .cv-foot { opacity: 0; }

  /* ══ HOVER HEADER · the card keeps its name ══════════════════════════════
     Pavlo, 03.09: "странно, что полностью исчезает информация… надо карточку
     расширять, чтобы вверху появлялось пространство, где можно название кейса
     и клиента показывать". One line above the picture: client + case name,
     arriving after the copy has gone. The room is made by GROW (Pavlo, 04.09):
     the card grows by --head-h and the list below moves — his words; "band"
     (the picture starting lower in a card of the same height) lost. */
  .cv-link { position: relative; transition: background var(--dur-fast) var(--ease), padding-top calc(var(--hdur) * 1ms) var(--ease), min-height calc(var(--hdur) * 1ms) var(--ease); }
  .cv-hh {
    position: absolute; left: var(--pad); right: var(--pad); top: 0;
    height: calc(var(--head-h) * 1px);
    display: flex; align-items: center; gap: var(--sp-5);
    opacity: 0; visibility: hidden; z-index: 4; pointer-events: none;
    /* the way out: the header goes first and fast, no delay (§6) */
    transition: opacity calc(var(--hdur) * .25ms) var(--ease),
                visibility 0s linear calc(var(--hdur) * .25ms);
  }
  .cv-link .cv-hh-client { color: var(--text-faint); }
  .cv-link .cv-hh-name   { color: var(--text); font-weight: var(--wt-med); }
  .cv-link:hover .cv-hh, .cv-link:focus-visible .cv-hh { opacity: 1; visibility: visible; transition: opacity calc(var(--hdur) * .5ms) var(--ease) calc(var(--hdur) * .35ms), visibility 0s linear calc(var(--hdur) * .35ms); }
  .cv-link:hover,
  .cv-link:focus-visible { padding-top: calc(var(--pad) + var(--head-h) * 1px); min-height: calc((var(--cardh) + var(--head-h)) * 1px); }
  /* the scene under the cursor: the fan opens, the extras arrive, the decor
     follows a beat later ("появляются ещё дополнительные экраны и, может,
     декоративные элементы") */
  .cv-shot, .cv-decor {
    transition: transform calc(var(--hdur) * 1ms) var(--ease),
                opacity calc(var(--hdur) * 1ms) var(--ease);
  }
  .cv-decor { transition-delay: calc(var(--decor-delay) * 1ms); }
  .cv-link:hover .cv-mat--phone .cv-shot--left,  .cv-link:focus-visible .cv-mat--phone .cv-shot--left  { --sx: calc(-1 * var(--spread-hover)); }
  .cv-link:hover .cv-mat--phone .cv-shot--right, .cv-link:focus-visible .cv-mat--phone .cv-shot--right { --sx: var(--spread-hover); }
  /* the far phones: out from behind the side ones, the opacity waiting
     --fan-delay of the ride and then taking half of it; on the way back they
     fade first, quickly, while riding home behind the side phones */
  .cv-mat--phone .cv-shot--extra {
    transition: transform calc(var(--hdur) * 1ms) var(--ease),
                opacity calc(var(--hdur) * .4ms) var(--ease);
  }
  .cv-link:hover .cv-mat--phone .cv-shot--extra, .cv-link:focus-visible .cv-mat--phone .cv-shot--extra {
    transition: transform calc(var(--hdur) * 1ms) var(--ease),
                opacity calc(var(--hdur) * .5ms) var(--ease) calc(var(--hdur) * var(--fan-delay) / 100 * 1ms);
  }
  .cv-link:hover .cv-mat--phone .cv-shot--far-left,  .cv-link:focus-visible .cv-mat--phone .cv-shot--far-left  { --sx: calc(-2 * var(--spread-hover)); --rot: calc(-1.6 * var(--tilt)); --op: 1; }
  .cv-link:hover .cv-mat--phone .cv-shot--far-right, .cv-link:focus-visible .cv-mat--phone .cv-shot--far-right { --sx: calc(2 * var(--spread-hover)); --rot: calc(1.6 * var(--tilt)); --op: 1; }
  .cv-link:hover .cv-decor--circle, .cv-link:focus-visible .cv-decor--circle { --sc: 1.18; --sx: 4; --sy: -6; }
  .cv-link:hover .cv-decor--aid,    .cv-link:focus-visible .cv-decor--aid    { --sx: 225; --sy: 44; --sc: 1.4; }
  .cv-decor--lines .wv { transition: transform calc(var(--hdur) * 1ms) var(--ease) calc(var(--decor-delay) * 1ms); }
  .cv-link:hover .cv-decor--lines .wv, .cv-link:focus-visible .cv-decor--lines .wv {
    transform: translate(calc((var(--wv-x) + var(--wv-shift)) * 1%), calc((var(--wv-y) - var(--wv-shift) * .5) * 1%)) scale(var(--wv-scale));
  }
  /* the stack opens into three on ONE line: the centre screen holds, the two
     sides ride out symmetrically and lean by --dk-tilt. The left one fades in
     as it leaves the centre screen, on the fan's own delay. */
  .cv-mat--desk .cv-shot--extra {
    transition: transform calc(var(--hdur) * 1ms) var(--ease),
                opacity calc(var(--hdur) * .4ms) var(--ease);
  }
  .cv-link:hover .cv-mat--desk .cv-shot--extra, .cv-link:focus-visible .cv-mat--desk .cv-shot--extra {
    transition: transform calc(var(--hdur) * 1ms) var(--ease),
                opacity calc(var(--hdur) * .5ms) var(--ease) calc(var(--hdur) * var(--fan-delay) / 100 * 1ms);
  }
  .cv-link:hover .cv-mat--desk .cv-shot--side,  .cv-link:focus-visible .cv-mat--desk .cv-shot--side  { --sx: var(--dk-spread-hover); --sy: var(--dk-y); }
  .cv-link:hover .cv-mat--desk .cv-shot--extra, .cv-link:focus-visible .cv-mat--desk .cv-shot--extra { --sx: calc(-1 * var(--dk-spread-hover)); --sy: var(--dk-y); --op: 1; }
  /* the field clears under the header as the header arrives — same delay, so
     the type never lands on dots */
  .cv-decor--dots { transition: --dots-clear calc(var(--hdur) * .5ms) var(--ease) calc(var(--hdur) * .35ms); }
  .cv-link:hover .cv-decor--dots, .cv-link:focus-visible .cv-decor--dots { --dots-clear: var(--dots-clear-r); }
  /* the stack rises into the header, so the air above and below it reads the
     same (Pavlo, 06.09: "снизу маленький отступ, а сверху большой") */
  .cv-mat--desk .cv-scene { transition: top calc(var(--hdur) * 1ms) var(--ease); }
  .cv-link:hover .cv-mat--desk .cv-scene, .cv-link:focus-visible .cv-mat--desk .cv-scene { top: calc(-1 * var(--head-h) * var(--dk-up) / 100 * 1px); }
}
@media (prefers-reduced-motion: reduce) {
  .cv-mat, .cv-copy, .cv-top, .cv-foot, .cv-shot, .cv-decor, .cv-link, .cv-hh, .cv-back, .cv-scene, .cv-decor--dots, .cv-decor--lines .wv { transition-duration: 0.01ms !important; transition-delay: 0ms !important; }
}

/* ══ the phone ════════════════════════════════════════════════════════════
   Below 760px there is no room for two zones side by side: material first,
   the copy under it. No hover state on a phone (Pavlo, 02.09: "только покой,
   статично"). The plate still runs into the card's rules, across the top. */
@media (max-width: 760px) {
  .cv-link {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: "mat" "top" "copy" "foot";
    grid-template-rows: auto auto auto auto;
    min-height: 0; padding: var(--sp-5);
  }
  .cv-mat  { display: block; margin: calc(var(--sp-5) * -1) calc(var(--sp-5) * -1) var(--sp-5); }
  .cv-mat  { aspect-ratio: 4 / 3; }
  :root { --zone-h: 75vw; }
  .cv-shot--extra { display: none; }
  .cv-hh { display: none; }
  .cv-copy { margin-block: var(--sp-5); }
  .cv-link .cv-name { font-size: var(--fs-h4); }
}
