/* v4 draft – Figma "Personal Site" › v.4 › Main-desktop (mIyPbPaVfWfKErzuHUWd3L, 696:15445) */
:root {
  --bg: #fafafa;
  --primary: #100e0e;
  --secondary: #7f7e7e;
  --line: #e2e2e2;
  --background: var(--bg); /* prod base.css name; prod page styles on ported cases use it */
}

*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html { -webkit-font-smoothing: antialiased; scrollbar-gutter: stable; } /* short tabs drop the scrollbar; reserve it so the centred layout doesn't shift */
body {
  font-family: 'Geist', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  font-size: 15px;
  line-height: 20px;
  background: var(--bg);
  color: var(--primary);
  overflow-wrap: break-word;
}
a { color: inherit; text-decoration: none; }
a:hover { text-decoration: underline; text-underline-offset: 2px; }
img { display: block; }
hr { border: 0; }
.label { font-size: 13px; line-height: 16px; color: var(--secondary); }

/* ── Layout: 1280 frame, 128 gutters, sidebar 288 + gap 80 + main 560 ── */
.page {
  display: grid;
  grid-template-columns: 288px 560px;
  grid-template-areas: "side main";
  column-gap: 80px;
  max-width: 1280px;
  margin: 0 auto;
  padding: 0 clamp(16px, 10vw, 128px) 64px; /* top 32 lives in .side / .topbar so the tabs can stick at 0 */
}

/* ── Sidebar ── */
.side { grid-area: side; position: sticky; top: 0; align-self: start; padding-top: 32px; }
.avatar { width: 88px; height: 88px; border-radius: 8px; object-fit: cover; }
.side-head { display: flex; align-items: flex-start; justify-content: space-between; margin-bottom: 24px; }
.side-blocks { display: grid; gap: 20px; }
.side-block { display: grid; gap: 4px; }
.rule { height: 1px; background: var(--primary); opacity: .1; margin: 24px 0; }
.contacts { display: grid; gap: 10px; }
.contact { display: grid; gap: 4px; }
.contact-row { display: flex; align-items: center; gap: 6px; }
/* copy email: icon only, no plate. Hover lightens the icon; on copy the icon shrinks out and a
   check draws itself in (lucide copy from Figma 699:46101, lucide check), then swaps back. */
.copy { display: grid; place-items: center; width: 20px; height: 20px; padding: 0; border: 0; background: none; color: var(--secondary); cursor: pointer; transition: color .15s; }
.copy:hover { color: #b3b2b2; }
.copy svg { grid-area: 1 / 1; transition: opacity .18s ease, scale .18s ease; }
.copy .i-check { opacity: 0; scale: .6; }
.copy .i-check path { stroke-dasharray: 1; stroke-dashoffset: 1; transition: stroke-dashoffset .28s ease .08s; }
.copy.is-copied .i-copy { opacity: 0; scale: .6; }
.copy.is-copied .i-check { opacity: 1; scale: 1; }
.copy.is-copied .i-check path { stroke-dashoffset: 0; }
/* "Copied" tip: appears once the check has drawn (.32s delay), right of the icon */
.copy { position: relative; }
.copy-tip {
  position: absolute; left: calc(100% + 6px); top: 50%;
  padding: 2px 6px; border-radius: 4px;
  background: var(--primary); color: var(--bg);
  font-size: 12px; line-height: 16px; white-space: nowrap;
  pointer-events: none;
  opacity: 0; translate: -4px -50%;
  transition: opacity .15s ease, translate .15s ease;
}
.copy.is-copied .copy-tip { opacity: 1; translate: 0 -50%; transition-delay: .32s; }
.copy-tip:empty { visibility: hidden; }
@media (prefers-reduced-motion: reduce) {
  .copy svg, .copy .i-check path, .copy-tip { transition: none; }
}

/* ── Main ── */
.main { grid-area: main; }
.topbar { position: sticky; top: 0; z-index: 5; display: flex; justify-content: space-between; align-items: center; padding: 32px 0; background: var(--bg); } /* 32 above the keys, 32 from the keys to the content (Figma, 2026-10-28) */
.tabs { display: flex; gap: 6px; }

/* ── Key: a physical push button (Figma 699:39203 / 699:39206), tab text one step above Figma (13/18).
   Plinth 32px r8: 1px rim on top/sides, 6px side wall at the bottom that darkens smoothly
   (a gradient, not a hard stop: a hard edge shows as a step behind the face's rounded corners).
   Face 25px, r7 top / r8 bottom, a dished keycap: soft top-to-bottom shade + highlight on the top edge.
   Press = the face travels 4px down onto the wall; nothing else moves or deforms.
   Chosen by Eduard 2026-10-28 from the draft variants (side wall + keycap face). ── */
.key {
  --travel: 4px;
  display: flex; flex-direction: column; justify-content: flex-start;
  height: 32px; padding: 1px 1px 6px;
  border: 0; border-radius: 8px;
  background: linear-gradient(to bottom, rgba(16,14,14,.2) calc(100% - 14px), rgba(16,14,14,.34));
  color: var(--secondary);
  font: inherit; font-size: 13px; line-height: 18px;
  cursor: pointer; -webkit-tap-highlight-color: transparent;
}
.key.is-active { background: linear-gradient(to bottom, var(--primary) calc(100% - 14px), #000); color: var(--primary); }
.key[data-tab="cases"]      { width: 60px; }  /* integer widths so the 1px rim never lands on a half pixel */
.key[data-tab="experience"] { width: 88px; }
.key-face {
  display: block; height: 25px; padding: 3px 0 4px;
  border-radius: 7px; /* concentric with the plinth: 8 - 1px rim */
  /* inactive: barely-there dish */
  background: linear-gradient(#fff, #fafafa);
  box-shadow: inset 0 1px 0 #fff, inset 0 -1px 0 rgba(16,14,14,.025);
  text-align: center;
  /* the trapezoid outline of both the plinth and the face is set as clip-path in v4.js (keyShapes) */
  /* no will-change: a separate layer gets pixel-snapped apart from the plinth and eats the 1px rim */
  transition: translate .09s cubic-bezier(.3, 0, .7, 1);
}
.key.is-active .key-face {
  background: linear-gradient(#fff, #f2f2f2);
  box-shadow: inset 0 1px 0 #fff, inset 0 -1px 0 rgba(16,14,14,.06);
}
.key:active .key-face { translate: 0 var(--travel); transition-duration: .05s; }
/* release: ease-out with a hair of overshoot, so it reads as a spring, not a slide */
.key.is-released .key-face { animation: key-release .22s cubic-bezier(.2, .8, .3, 1.1) both; }
@keyframes key-release {
  0%   { translate: 0 var(--travel); }
  60%  { translate: 0 -.5px; }
  100% { translate: 0 0; }
}
@media (prefers-reduced-motion: reduce) {
  .key-face { transition: none; }
  .key.is-released .key-face { animation: none; }
}

/* CV: same key construction and shape as the tabs, in its own colour – it's an action, not a tab.
   Every shade comes from --cv. Kept as flat as the tabs: a solid face and one even darker wall. */
.key--cv {
  --cv: #e8480a; /* keycap orange sampled from Eduard's keyboard reference (esc key), 2026-10-28 */
  width: 52px;
  background: color-mix(in srgb, var(--cv) 78%, #000);
}
.key--cv .key-face {
  display: flex; align-items: center; justify-content: center; gap: 3px;
  padding: 3px 2px 4px 4px;
  background: var(--cv);
  box-shadow: none;
  color: var(--bg);
}
.key--cv:hover { text-decoration: none; }

.panel { display: none; }
.panel.is-active { display: block; }
#cases { display: none; gap: 24px; }
#cases.is-active { display: grid; }

/* tab switch: children rise in with a short stagger (first 6 only, the rest are below the fold) */
#cases.is-entering > *,
#experience.is-entering .exp-list > * { animation: panel-in .32s cubic-bezier(.2, .7, .3, 1) both; }
#cases.is-entering > :nth-child(2), #experience.is-entering .exp-list > :nth-child(2) { animation-delay: .04s; }
#cases.is-entering > :nth-child(3), #experience.is-entering .exp-list > :nth-child(3) { animation-delay: .08s; }
#cases.is-entering > :nth-child(4), #experience.is-entering .exp-list > :nth-child(4) { animation-delay: .12s; }
#cases.is-entering > :nth-child(5), #experience.is-entering .exp-list > :nth-child(5) { animation-delay: .16s; }
#cases.is-entering > :nth-child(n+6), #experience.is-entering .exp-list > :nth-child(n+6) { animation-delay: .2s; }
@keyframes panel-in {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  #cases.is-entering > *, #experience.is-entering .exp-list > * { animation: none; }
}

/* ── Case card: 560×320 frame (560×389 for phone art) + caption ── */
.case { display: grid; gap: 10px; }
.case:hover { text-decoration: none; }
.case > .case-media { /* home cards only: the bare class is also the prototype embed of components/case-media (gave the terminal a light frame) */
  position: relative;
  aspect-ratio: 560 / 320;
  border: .5px solid #e8e8e8;
  border-radius: 12px;
  overflow: hidden;
  background: linear-gradient(to bottom, rgba(243,243,243,.5), rgba(237,237,237,.5));
}
.art { position: absolute; transition: transform .4s ease; }
.case:hover .art { transform: scale(1.05); }
/* baked composites (prod assets) fill the frame */
.art--baked { inset: 0; width: 100%; height: 100%; object-fit: cover; object-position: center top; }
.case > .case-media--tall { aspect-ratio: 560 / 320; } /* phone cards match the MegaFon card (Eduard, 2026-10-28); was 560/389 per Figma */
.case > .case-media--square { aspect-ratio: 1 / 1; } /* mobile-app video previews (series spec, 2026-10-30); web cases stay 560×320 */
/* video previews (js/case-video.js plays them in view); the video already moves, a hover zoom only blurs the frame */
.art--video { inset: 0; width: 100%; height: 100%; object-fit: cover; display: block; }
.case:hover .art--video { transform: none; }
/* positions from the Figma frame: phone 299 wide @ top 24 (in 560×389), desktop 488 wide @ top 22 (in 560×320) */
.art--phone   { left: 50%; width: 39.04%; margin-left: -19.52%; top: 10.03%; } /* bezel = the MegaFon card's baked phone (211px wide, 35px down in 560×320); the art has ~4% shadow padding, so 39% image width — same values as prod */
.art--desktop { left: 50%; width: 87.1%; margin-left: -43.55%; top: 6.9%; }
.case-caption { padding: 0 12px; font-size: 13px; line-height: 16px; }
.soon {
  position: absolute; top: 16px; right: 16px; z-index: 1;
  padding: 2px 8px; border-radius: 32px;
  background: #fff34f; font-size: 12px; line-height: 16px;
}

@media (prefers-reduced-motion: reduce) {
  .art { transition: none; }
  .case:hover .art { transform: none; }
}

/* ── Responsive: stack below ~1000px (no mobile frame in Figma yet) ── */
@media (max-width: 1000px) {
  .page { grid-template-columns: minmax(0, 560px); grid-template-areas: "side" "main"; row-gap: 0; justify-content: center; padding-left: 16px; padding-right: 16px; } /* 16px gutters on stacked layouts, as on prod (was 10vw ≈ 38px on a phone); no row gap: the sticky topbar already pads 32 above the keys (40 + 32 read as a hole) */
  .side { position: static; }
}

/* ── Experience / About: ported from prod home.css, re-scaled to the v4 type (15/20) ── */
.exp-list { display: grid; gap: 28px; }
.exp-case { display: grid; gap: 6px; }
.exp-case[data-href] { cursor: pointer; }
.exp-head { display: flex; flex-wrap: wrap; align-items: center; gap: 0 8px; }
.exp-logo { width: 20px; height: 20px; flex-shrink: 0; background: center / contain no-repeat; }
.exp-date { margin-left: auto; padding-left: 16px; white-space: nowrap; color: var(--secondary); }
.exp-desc { color: var(--secondary); }
.exp-readmore { display: inline-flex; align-items: center; gap: 2px; justify-self: start; transition: opacity .15s; }
.exp-readmore:hover { opacity: .7; text-decoration: none; }

/* floating case preview (filled by ../js/case-preview.js) */
.case-float {
  position: fixed; top: 0; left: 0; z-index: 90;
  width: 280px; aspect-ratio: 3 / 2;
  border-radius: 8px; overflow: hidden; background: #ebebeb;
  box-shadow: 0 8px 24px rgba(0,0,0,.12), 0 2px 6px rgba(0,0,0,.06);
  pointer-events: none; opacity: 0; transform: translateY(6px);
  transition: opacity .18s ease, transform .18s ease;
}
.case-float.is-visible { opacity: 1; transform: translateY(0); }
.case-float img { width: 100%; height: 100%; object-fit: cover; }

/* ── Bio-tip tooltips + sketch overlay: copied from prod home.css ── */
.sketch-overlay {
  position: fixed;
  top: 0;
  left: 50%;
  width: 1280px;
  height: 100vh;
  margin-left: -640px;
  pointer-events: none;
  z-index: 50;
  opacity: 0;
  transition: opacity 0.25s ease;
}

.sketch-overlay.is-visible {
  opacity: 1;
}

.sketch-overlay-img {
  position: absolute;
  display: block;
  user-select: none;
  -webkit-user-drag: none;
}

/* ─── Bio hover tooltip ───────────────────────── */

.bio-tip {
  /* cursor:pointer + tap-highlight: iOS Safari ignores tap-to-toggle on a
     plain <span> without a click affordance — these make it a real tap target. */
  cursor: pointer;
  -webkit-tap-highlight-color: rgba(16, 14, 14, 0.06);
  transition: opacity 0.15s;
}

/* Gated by hover capability — without this iOS keeps the 0.7 opacity stuck
   after a tap (sticky :hover). */
@media (hover: hover) {
  .bio-tip:hover {
    opacity: 0.7;
  }
}

#bio-tooltip {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 100;
  pointer-events: none;

  border-radius: 4px;
  overflow: hidden;
  box-shadow: 0 8px 24px rgba(0,0,0,0.12), 0 2px 6px rgba(0,0,0,0.06);

  opacity: 0;
  transform: translateY(6px) scale(0.97);
  transition: opacity 0.18s ease, transform 0.18s ease;
  will-change: transform, opacity;
}

#bio-tooltip.btip--visible {
  opacity: 1;
  transform: translateY(0) scale(1);
  pointer-events: auto;
}

img.btip-photo {
  display: block;
  width: 200px;
  height: 300px;
  object-fit: cover;
  object-position: center 50%;
}

.btip-video-wrap {
  position: relative;
  display: block;
  line-height: 0;
}

video.btip-photo {
  display: block;
  width: 325px;
  height: auto;
  aspect-ratio: 480 / 270;
  background: #000;
}

.btip-playbtn {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 40px;
  height: 40px;
  border-radius: 50%;
  border: none;
  background: rgba(0, 0, 0, 0.45);
  backdrop-filter: blur(4px);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  transition: opacity 0.18s, background 0.15s, transform 0.15s;
}

.btip-video-wrap.is-paused .btip-playbtn,
.btip-video-wrap:hover .btip-playbtn {
  opacity: 1;
}

.btip-playbtn:hover {
  background: rgba(0, 0, 0, 0.65);
  transform: translate(-50%, -50%) scale(1.08);
}

.btip-closebtn {
  position: absolute;
  top: 4px;
  right: 4px;
  width: 28px;
  height: 28px;
  border: none;
  background: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  transition: opacity 0.18s;
}

.btip-closebtn svg {
  opacity: 0.75;
}

.btip-video-wrap.is-playing .btip-closebtn {
  opacity: 1;
}

.btip-closebtn:hover svg {
  opacity: 1;
}

.btip-progress {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  height: 3px;
  background: rgba(255, 255, 255, 0.2);
}

.btip-progress-bar {
  height: 100%;
  width: 0%;
  background: rgba(255, 255, 255, 0.85);
}

@media (max-width: 768px) {
  @keyframes btip-slide-up {
    from {
      opacity: 0;
      transform: translate(-50%, calc(-50% + 20px));
    }
    to {
      opacity: 1;
      transform: translate(-50%, -50%);
    }
  }

  #bio-tooltip.btip--centered {
    position: fixed;
    transform: translate(-50%, -50%);
    transition: none;
    opacity: 0;
    pointer-events: none;
  }

  #bio-tooltip.btip--centered.btip--visible {
    animation: btip-slide-up 0.22s ease forwards;
    pointer-events: auto;
  }

  #bio-tooltip .btip-closebtn {
    opacity: 1;
  }

  video.btip-photo {
    width: calc(100vw - 32px);
    height: auto;
    aspect-ratio: 480 / 270;
  }
}

/* ── Case in the shell (Figma 699:51151): the case replaces the list in the content column ── */
.key:hover { text-decoration: none; } /* tabs are links on case pages */
.case-article { display: flex; flex-direction: column; font-size: 14px; line-height: 20px; }
.case-article > * + * { margin-top: 24px; }       /* default rhythm, overridden by .mt-* below (values from the Figma frame) */
.case-article > .mt-8  { margin-top: 8px; }
.case-article > .mt-12 { margin-top: 12px; }
.case-article > .mt-16 { margin-top: 16px; }
.case-article > .mt-20 { margin-top: 20px; }
.case-article > .mt-28 { margin-top: 28px; }
.case-article > .mt-32 { margin-top: 32px; }
.case-article > .mt-64 { margin-top: 64px; }
.case-back { display: inline-flex; align-items: center; gap: 4px; align-self: flex-start; font-size: 14px; line-height: 16px; color: var(--secondary); }
.case-back:hover { color: var(--primary); text-decoration: none; }
.case-article > .cs-head { margin-top: 24px; }
.cs-head h1 { font-size: 20px; line-height: 24px; font-weight: 500; letter-spacing: -.4px; }
.cs-head p { margin-top: 12px; }
.cs-block { display: grid; gap: 4px; }
.cs-block h2, .cs-h2 { font-size: 16px; line-height: 24px; font-weight: 600; letter-spacing: -.4px; }
.cs-block p + p, .cs-text p + p, .cs-sub p + p { margin-top: 8px; }
.cs-block h2 + p { margin-top: 0; }
.cs-sub { display: grid; }
.cs-sub h3 { font-size: 14px; line-height: 20px; font-weight: 600; margin-bottom: 8px; }
.cs-sub .cs-gap { margin-top: 20px; }               /* 12 between text groups + 8 paragraph gap in Figma */
.cs-sub ul { margin: 8px 0 0 21px; display: grid; gap: 4px; }
.cs-roles { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
.cs-role { display: flex; gap: 10px; align-items: flex-start; }
.cs-chip { display: grid; place-items: center; padding: 6px; border-radius: 8px; background: #ececec; flex-shrink: 0; }
.cs-role p { display: grid; gap: 2px; padding: 2px 0; }
.cs-role p span { color: var(--secondary); }
.cs-phone { display: grid; justify-items: center; gap: 16px; }
.cs-phone-frame { width: 254px; height: 520px; border: 3px solid #1e1e1e; border-radius: 24px; overflow: hidden; background: var(--bg); box-shadow: 0 3px 18.6px rgba(0,0,0,.06); }
.cs-phone-frame img, .cs-phone-frame video { display: block; width: 100%; height: auto; }
.cs-phone figcaption, .case-article .slide-gallery-caption { font-size: 12px; line-height: 16px; color: var(--secondary); text-align: center; }
.cs-note { font-style: italic; color: var(--secondary); }
.case-article .slide-gallery-slide img { object-fit: cover; width: 100%; }
@media (max-width: 520px) { .cs-roles { grid-template-columns: 1fr; } }

/* ── Page-to-page transition (main ↔ case): only the content column animates; the sidebar and
   the keys are identical on both pages, so they stay put. Chrome / Safari 18.2+; elsewhere a normal load. ── */
@view-transition { navigation: auto; }
.side { view-transition-name: v4-side; }
.tabs { view-transition-name: v4-tabs; }
.key--cv { view-transition-name: v4-cv; }  /* own name → the browser slides it to its new spot instead of cross-fading */
.main > .panel.is-active, .case-article { view-transition-name: v4-content; }
/* a view-transition-name makes the element a stacking context, which trapped the fullscreen prototype
   (case-media --active, z 1001) under the body::before dim overlay (z 1000) and the sticky keys */
body.case-media-modal-open .case-article { view-transition-name: none; }
::view-transition-old(root), ::view-transition-new(root) { animation: none; }
::view-transition-old(v4-content) { animation: vt-out .14s ease both; }
::view-transition-new(v4-content) { animation: vt-in .32s cubic-bezier(.2, .7, .3, 1) .06s both; }
::view-transition-group(v4-content) { animation: none; } /* no morph between list and case boxes */
::view-transition-group(v4-cv) { animation-duration: .42s; animation-timing-function: cubic-bezier(.2, .8, .3, 1); }
@keyframes vt-out { to { opacity: 0; } }
@keyframes vt-in { from { opacity: 0; transform: translateY(8px); } }
@media (prefers-reduced-motion: reduce) {
  ::view-transition-old(v4-content), ::view-transition-new(v4-content) { animation: none; }
  ::view-transition-group(v4-cv) { animation: none; }
}

/* ── Prod case markup inside the v4 shell (.case-article--prod, e.g. cases/alps2alps): restyle the prod
   classes to the v4 case type (same as CopyTrading: h2 16/24 600, h3 14/20 600, body 14/20) ── */
.case-article--prod .case-meta { display: grid; gap: 6px; }
.case-article--prod .case-meta-row { display: grid; grid-template-columns: 120px 1fr; gap: 12px; }
.case-article--prod .case-meta-label { color: var(--secondary); }
.case-article--prod .case-meta-value a, .case-article--prod .case-section-text a { text-decoration: underline; text-underline-offset: 2px; text-decoration-color: rgba(16,14,14,.3); }
.case-article--prod .case-meta-value a:hover { text-decoration-color: currentColor; }
.case-article--prod .case-link-arrow { vertical-align: -1px; margin-left: 2px; }
.case-article--prod .case-body { display: grid; gap: 32px; }
.case-article--prod .case-section > h2 { font-size: 16px; line-height: 24px; font-weight: 600; letter-spacing: -.4px; margin-bottom: 4px; }
.case-article--prod .case-section-text p + p { margin-top: 8px; }
/* spacing as on prod (2026-10-29): text → toggle pills / gallery 20, text or gallery → subheading 24 */
.case-article--prod .case-subsections { display: grid; gap: 24px; margin-top: 20px; }
.case-article--prod .case-subsection:first-child > .case-subsection-header:first-child { margin-top: 4px; } /* section text → first subheading 24 */
.case-article--prod .case-subsection-header + :is(.case-viewer, .slide-gallery, .case-media) { margin-top: 4px; } /* subheading text → pills 20 (gap 16 + 4) */
.case-article--prod .case-subsection { display: grid; gap: 16px; scroll-margin-top: 112px; } /* clear the sticky keys on #anchor jumps */
.case-article--prod .case-subsection h3 { font-size: 14px; line-height: 20px; font-weight: 600; margin-bottom: 8px; }
.case-article--prod .cv-caption { font-size: 12px; line-height: 16px; color: var(--secondary); }

/* Case pages: the content column widens 560 → 720 (Eduard, 2026-10-28) by eating into the right
   gutter, so the sidebar stays exactly where it is and only the CV key travels right. */
@media (min-width: 1001px) {
  .page--wide { grid-template-columns: 288px minmax(0, 720px); padding-right: clamp(16px, 3vw, 48px); } /* ≤49px leaves the full 720 at a 1280 window with its 15px scrollbar */
}
.case-article--prod > .case-title, .case-article--prod .case-header .case-title,
.case-article--prod .case-title { font-size: 20px; line-height: 24px; font-weight: 500; letter-spacing: -.4px; margin: 0; }
.case-article--prod > .case-back + .case-title, .case-article--prod > .case-back + .case-header { margin-top: 24px; } /* Index → title, as on CopyTrading */
.case-article--prod > .case-title + *, .case-article--prod .case-title + p { margin-top: 12px; }
/* grid/flex items default to min-width:auto, so a wide filmstrip (slide-gallery track) would stretch its
   section past the column; let them shrink and let the track scroll instead */
.case-article > *, .case-article--prod .case-body > *, .case-article--prod .case-subsections > * { min-width: 0; }
/* CopyTrading Outcome platform viewer – page rules copied from prod cases/copytrading (inline <style>);
   the prod 720px break-out rule is dropped: the v4 case column is already 720 */
.case-article .ct-outcome[data-platform="web"] .cv-platform { left: 50%; transform: translateX(-50%); }
.case-article .ct-outcome[data-platform="web"] .case-viewer-frame { width: 100%; height: 480px; border: 4px solid #d9d9d9; border-radius: 24px; box-shadow: none; }

/* Prod case-page.css pieces the Experience pages rely on (case-page.css itself isn't loaded in v4) */
.case-article--prod .case-subsection-header { display: grid; }
.case-article--prod .case-subsection-text { display: grid; gap: 8px; }
.case-article--prod .case-deflist { display: grid; gap: 8px; }
.case-article--prod .case-defitem { display: grid; gap: 4px; }
.case-article--prod .case-defterm { color: var(--secondary); }
.case-article--prod .case-subsection-text a, .case-article--prod .case-defdesc a { text-decoration: underline; text-underline-offset: 2px; text-decoration-color: rgba(16,14,14,.3); }
.case-article--prod a:has(.case-link-arrow) { text-decoration-color: transparent; transition: text-decoration-color .18s; }
.case-article--prod a:has(.case-link-arrow):hover { text-decoration-color: var(--secondary); }
/* NDA-redacted metric (XXX): dotted underline + hover bubble */
.nda { position: relative; cursor: help; border-bottom: 1px dotted var(--secondary); }
.nda::after, .nda::before { position: absolute; left: 50%; opacity: 0; pointer-events: none; transform: translateX(-50%) translateY(4px); transition: opacity .15s ease, transform .15s ease; z-index: 60; }
.nda::after { content: attr(data-tip); bottom: calc(100% + 10px); padding: 6px 10px; border-radius: 6px; background: var(--primary); color: var(--bg); font-size: 12px; line-height: 1.4; white-space: nowrap; }
.nda::before { content: ""; bottom: 100%; border: 5px solid transparent; border-top-color: var(--primary); }
.nda:hover::after, .nda:hover::before { opacity: 1; transform: translateX(-50%) translateY(0); }
/* Case meta table (Company / Role / Period / Problem / Result) is hidden on v4 case pages (Eduard, 2026-10-28);
   markup stays so the generator output matches prod */
.case-article .case-meta { display: none; }
/* megafon-main-screen page.css gives .case-header margin-bottom:24 for the (now hidden) meta table;
   flex margins don't collapse, so it doubled the gap to the body */
.case-article.case-article--prod > .case-header { margin-bottom: 0; }
/* megafon-main-screen Outcome video: prod frames it at a real-device 375×812; in v4 match the phone
   size of the other cases' viewers (262 wide, same 4px frame / r32). Specificity beats its page.css. */
.case-article.case-article--prod .case-main-screen { width: min(262px, 100%); }
/* Validation phone (video): no fixed height – the frame follows the clip's own ratio (564×1126), sized like
   the other cases' viewer phones (262 wide, 4px frame, r32); a fixed 520 left a 19px empty band under the video */
.cs-phone-frame:has(video) { width: 262px; height: auto; border-width: 4px; border-radius: 32px; }
/* Dark phone frames: the light #fafafa frame background showed through the anti-aliased seam between the
   screen's 28px clip and the 4px #1e1e1e bezel – a light arc in the top corners on dark screens (splash).
   Background = bezel colour makes the seam invisible. Grey desktop frames are left as they are. */
.case-article .case-viewer--app .case-viewer-frame,
.case-article .case-viewer--platform[data-platform="app"] .case-viewer-frame,
.case-article .cs-phone-frame { background: #1e1e1e; }
/* …but the slide is white: a shorter screenshot sliding in under a taller frame (before it shrinks) leaves a
   white strip, not a black one. The screenshot covers the slide, so the corner seam still meets the dark frame. */
.case-article .case-viewer--app .case-viewer-slide,
.case-article .case-viewer--platform[data-platform="app"] .case-viewer-slide { background: #fff; }
/* phones shrink to a short screenshot (550px stays the max), so there is no empty band under it;
   read by components/case-viewer.js, prod does not set it */
.case-article .case-viewer { --cv-shrink-phone: 1; }
/* web viewers in mobile mode: black phone bezel like the app phones (prod keeps grey) */
.case-article .case-viewer.is-mobile .case-viewer-frame { border-color: #1e1e1e; }

/* ─── Hoops mini-game (bio "Basketball") ───────── */
@media (hover: hover) {
  .bio-tip[data-tip="basketball"] { position: relative; }
  /* a little basketball. The seams are an unrolled strip (one turn = 28px) scrolling sideways under
     a shading layer, which reads as a spin around the vertical axis */
  .bio-tip[data-tip="basketball"]::after {
    content: ""; position: absolute; left: 50%; bottom: 100%; width: 14px; height: 14px; margin-left: -7px; border-radius: 50%;
    background:
      radial-gradient(circle at 35% 30%, rgba(255, 255, 255, .3), transparent 45%),
      radial-gradient(circle, transparent 45%, rgba(60, 20, 5, .5) 100%),
      url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='28' height='14' viewBox='0 0 28 14'%3E%3Cg fill='none' stroke='%235a2410' stroke-opacity='.55' stroke-width='.8'%3E%3Cpath d='M0 7h28M0 0v14M14 0v14M28 0v14'/%3E%3Ccircle cx='7' cy='7' r='3.7'/%3E%3Ccircle cx='21' cy='7' r='3.7'/%3E%3C/g%3E%3C/svg%3E") 0 0 / 28px 14px repeat-x,
      #d4672a;
    opacity: 0; pointer-events: none;
  }
  /* the word dims via colour, not opacity, so the ball stays solid */
  .bio-tip[data-tip="basketball"]:hover { opacity: 1; color: color-mix(in srgb, currentColor 70%, transparent); }
  .bio-tip[data-tip="basketball"]:hover::after { opacity: 1; animation: hoops-dribble .9s infinite, hoops-spin 1.2s linear infinite; }
}
/* one dribble: falls (accelerating, stretched), squashes on the floor, springs back up */
@keyframes hoops-dribble {
  0%   { transform: translateY(-14px) scale(1, 1); animation-timing-function: ease-in; }
  40%  { transform: translateY(-2px) scale(.92, 1.08); }
  50%  { transform: translateY(1.4px) scale(1.2, .8); }
  60%  { transform: translateY(-2px) scale(.95, 1.05); animation-timing-function: ease-out; }
  100% { transform: translateY(-14px) scale(1, 1); }
}
@keyframes hoops-spin { to { background-position: 0 0, 0 0, -28px 0; } }
@media (prefers-reduced-motion: reduce) { .bio-tip[data-tip="basketball"]:hover::after { animation: none; } }

.hoops { position: fixed; inset: 0; z-index: 300; background: rgba(250, 250, 250, 0.6); }
.hoops canvas { display: block; width: 100%; height: 100%; touch-action: none; }
/* arena scoreboard: LED digits on a dark panel, the hint pill hangs under it */
.hoops-board { position: absolute; top: 16px; left: 50%; transform: translateX(-50%); display: grid; justify-items: center; gap: 2px; min-width: 96px; padding: 6px 14px 8px; border-radius: 10px; background: #0b0a0a; box-shadow: inset 0 0 0 2px #2b2828, 0 4px 12px rgba(16, 14, 14, 0.2); }
.hoops-board span { color: #8f8a86; font-size: 10px; line-height: 12px; letter-spacing: .12em; text-transform: uppercase; }
.hoops-board b { color: #ffb21f; font: 500 30px/32px ui-monospace, "SF Mono", Menlo, monospace; font-variant-numeric: tabular-nums; letter-spacing: .04em; text-shadow: 0 0 8px rgba(255, 178, 31, 0.7); }
.hoops-hud { position: absolute; top: 88px; left: 50%; transform: translateX(-50%); display: flex; align-items: center; gap: 12px; padding: 6px 6px 6px 16px; border-radius: 999px; background: var(--primary); color: var(--bg); font-size: 14px; line-height: 20px; }
.hoops-hint { color: var(--secondary); }
.hoops-hint:empty { display: none; }
/* labelled exit, so it still reads once the hint is gone */
.hoops-hud:has(.hoops-hint:empty) { padding-left: 6px; }
.hoops-close { display: flex; align-items: center; gap: 6px; height: 28px; padding: 0 6px 0 12px; border: 0; border-radius: 999px; background: rgba(255, 255, 255, 0.12); color: var(--bg); font: inherit; font-size: 14px; cursor: pointer; }
.hoops-close:hover { background: rgba(255, 255, 255, 0.2); }
.hoops-close kbd { padding: 0 5px; border-radius: 4px; background: rgba(255, 255, 255, 0.16); color: rgba(255, 255, 255, 0.7); font: inherit; font-size: 11px; line-height: 18px; }

/* Shared components that default to Source Serif 4, which v4 does not load (the browser fell back to
   Times bold, e.g. "Enter interactive mode" on trading-terminal) → page font. Prod does the same in case-page.css. */
/* body-prefixed: v4.css loads before the component css, so plain selectors would lose */
body .case-media-scroll-btn,
body .case-gallery-caption, body .case-gallery-counter,
body .platform-toggle-tab,
body .case-mockup-description,
body .scroll-to-top { font-family: inherit; }

/* ── Contacts: inline in the sidebar on desktop; on stacked layouts (≤1000) a centred modal
   opened by the "Contacts" key next to the avatar. One <dialog> serves both, so no duplicate markup. */
.contacts-open { width: 76px; color: var(--primary); }
@media (min-width: 1001px) {
  .contacts-open { display: none; }
  .contacts-dialog { display: block; position: static; width: auto; max-width: none; max-height: none; margin: 0; padding: 0; border: 0; background: none; color: inherit; overflow: visible; }
  .contacts-dialog-head { display: none; }
}
@media (max-width: 1000px) {
  .contacts-dialog { margin: auto; /* the global reset zeroes it, which kills the dialog centring */ width: min(343px, calc(100vw - 32px)); padding: 20px; border: .5px solid var(--line); border-radius: 16px; background: var(--bg); color: var(--primary); box-shadow: 0 16px 48px rgba(16,14,14,.14); }
  .contacts-dialog[open] { animation: contacts-in .24s cubic-bezier(.2, .7, .3, 1); }
  .contacts-dialog::backdrop { background: rgba(16,14,14,.32); } /* no backdrop blur: Chrome bled the blurred page through the dialog */
  .contacts-dialog .rule { display: none; }
  .contacts-dialog-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 16px; font-weight: 500; }
  .contacts-close { display: grid; place-items: center; width: 28px; height: 28px; margin: -6px -6px -6px 0; padding: 0; border: 0; border-radius: 8px; background: none; color: var(--secondary); cursor: pointer; }
  .contacts-close:hover { color: var(--primary); }
  /* each contact is one tap target over both lines (label + value): the link's ::after covers the row;
     the copy button sits above it at the right edge, centred on the row, with a 32px hit area */
  .contacts-dialog .contact { position: relative; padding-right: 40px; }
  .contacts-dialog .contact a::after { content: ""; position: absolute; inset: -4px 0; }
  .contacts-dialog .copy-tip { left: auto; right: calc(100% + 2px); } /* at the modal's right edge the tip would be clipped – open it to the left */
  .contacts-dialog .copy { position: absolute; z-index: 1; top: 50%; right: -6px; width: 32px; height: 32px; margin-top: -16px; display: grid; place-items: center; }
}
@keyframes contacts-in { from { opacity: 0; transform: translateY(8px) scale(.98); } }
@media (prefers-reduced-motion: reduce) { .contacts-dialog[open] { animation: none; } }

/* ── Mobile (stacked, ≤1000): Experience header on one wrapping line: Company // Role // Date ── */
@media (max-width: 1000px) {
  .exp-head { display: block; }
  .exp-logo { display: inline-block; vertical-align: -4px; margin-right: 8px; }
  .exp-sep { margin: 0 8px; }
  .exp-date { margin: 0; padding: 0; color: inherit; }
  .exp-date::before { content: "//"; margin: 0 8px; }
}

/* Case pages on stacked layouts: no bio blocks, so the case starts on the first screen (prod case pages have no bio at all);
   the avatar row keeps the Contacts key, the full bio stays on the main page */
@media (max-width: 1000px) {
  .page--wide .side-blocks { display: none; }
  .page--wide { row-gap: 0; }
}
