/* ============================================================
   Components. Every colour and every type style here comes from
   design-system.css — no raw hex values, font sizes or weights.
   Design artboard: 1440px (Figma UI kit 177:37824).
   ============================================================ */

:root {
  /* Layout — sections run 1376 wide inside a 32px gutter, the hero
     column is narrower still. Both come from design-system.css. */
  --page-max: var(--width-content);
  --wrap-max: var(--width-content);

  /* The serif is drawn at 1440 and shrinks with the viewport below it,
     so a headline keeps its proportion to the layout at any width. */
  --display-scale: clamp(0.5, 100vw / 1440px, 1);
}

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

html {
  scroll-behavior: smooth;
}

body {
  margin: 0;
  min-width: 320px;
  background: var(--color-background-primary);
  color: var(--color-text-primary);
  font-family: var(--font-family-sans);
  font-size: var(--font-size-body);
  line-height: var(--line-height-body);
  letter-spacing: var(--tracking-body);
  font-weight: var(--font-weight-medium);
  -webkit-font-smoothing: antialiased;
}

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

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

ul {
  margin: 0;
  padding: 0;
  list-style: none;
}

em {
  font-style: italic;
}

/* The content column is 1376 inside a 1440 page — so the gutter is what
   is left over, not padding added on top of the column. */
.wrap {
  width: min(100% - 2 * var(--page-gutter), var(--wrap-max));
  margin: 0 auto;
}

/* ——— Header ——— */

/* No fill. The frame carries none — it stands on the hero's own haze —
   and one rule along the bottom, drawn always rather than on scroll.
   The rule is an inset shadow so the bar stays exactly 80 tall.

   With nothing to hide behind, the bar cannot stay put while the page
   moves under it: it scrolls away with the rest. */
header.nav {
  /* Nothing of its own: no plate, no rule. The hero's texture runs up
     under it (hero-pattern draws from the top of the page), and the bar's
     words sit above the texture. */
  position: relative;
  z-index: 2;
  background: transparent;
  box-shadow: none;
}

/* The bar is 80 tall and holds a 40px row — Figma frame 200:94688.
   Three items on space-between: that, and nothing cleverer, is what puts
   the links where the frame puts them. */
header.nav .wrap {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding-top: 20px;
  padding-bottom: 20px;
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  min-width: 0;
}

/* A 40px window onto the portrait: the cut-out sits on its own plate and
   overhangs the frame, which crops it. The overhang is what makes the
   head read as bigger than the circle. */
.brand__avatar {
  position: relative;
  flex-shrink: 0;
  width: 40px;
  height: 40px;
  overflow: hidden;
  border-radius: 900px;
  background: var(--gradient-avatar-plate-nav);
}

.brand__avatar img {
  position: absolute;
  left: -2.2px;
  top: -0.8px;
  width: 45px;
  height: 45px;
  max-width: none;
}

/* Name/logo is narrative → serif italic at body size. */
.brand__name {
  font-family: var(--font-family-serif);
  font-style: italic;
  font-weight: var(--font-weight-regular);
  font-size: var(--font-size-brand);
  line-height: var(--line-height-brand);
  color: var(--color-text-primary);
  white-space: nowrap;
}

nav.links {
  display: flex;
  gap: 16px;
  align-items: center;
}

nav.links a {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 2px;
  font-family: var(--font-family-sans);
  font-weight: var(--font-weight-medium);
  font-size: var(--font-size-label);
  line-height: var(--line-height-label);
  letter-spacing: var(--tracking-label);
  color: var(--color-text-secondary);
  transition: color 0.2s ease;
}

nav.links a:hover {
  color: var(--color-text-primary);
}

/* The mark travels with the link's colour, so hover moves both at once. */
.link__icon {
  flex-shrink: 0;
  width: 16px;
  height: 16px;
}

nav.links a::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -2px;
  height: 1px;
  background: var(--color-accent-primary);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.25s ease;
}

nav.links a:hover::after,
nav.links a:focus-visible::after {
  transform: scaleX(1);
}

/* Availability — Figma frame 200:94702, 32 tall, 16 / 8 / 16 across. */
.availability {
  justify-self: end;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  height: 32px;
  padding: 0 16px;
  /* Inside, like every stroke in the kit — a real border would push the
     pill 2px wider than it is drawn. */
  box-shadow: inset 0 0 0 1px var(--color-border-default);
  border-radius: 900px;
  background: var(--color-surface-primary);
  font-family: var(--font-family-sans);
  font-weight: var(--font-weight-medium);
  font-size: var(--font-size-label);
  line-height: var(--line-height-label);
  letter-spacing: var(--tracking-label);
  color: var(--color-text-secondary);
  white-space: nowrap;
}

/* The pill is a way to the contact block: on hover it darkens like the
   links in the bar, and the ring keeps its colour. */
.availability:hover,
.availability:focus-visible {
  color: var(--color-text-primary);
}

.pulse {
  flex-shrink: 0;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--color-status-positive);
  box-shadow: 0 0 0 0 var(--color-status-positive);
  animation: pulse 2.2s ease-out infinite;
}

@keyframes pulse {
  0% {
    box-shadow: 0 0 0 0 color-mix(
        in srgb,
        var(--color-status-positive) 55%,
        transparent
      );
  }
  70% {
    box-shadow: 0 0 0 8px
      color-mix(in srgb, var(--color-status-positive) 0%, transparent);
  }
  100% {
    box-shadow: 0 0 0 0
      color-mix(in srgb, var(--color-status-positive) 0%, transparent);
  }
}

/* ——— Hero ———
   One centred column, four parts, fixed gaps (Figma 200:94705 and its
   siblings): portrait → 34 → meta → 16 → statement → 16 → subtitle.
   The 63 above and 125 below are what put the rule under the block at
   862, where the projects section starts.

   Only the serif scales below 1440; the gaps and the portrait hold their
   size, so the block keeps its proportions as the type shrinks. */

.hero {
  /* Only the outer fifths navigate; the middle 56% is left to read and
     click. Everywhere else the slider keeps its default halves. */
  --slider-edge: 0.22;
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 100%;
  margin: 0 auto;
  /* The gutter lives on the slides, so the viewport runs the full width
     and an edge zone reaches the edge of the screen. */
  padding: 56px 0 82px;
  text-align: center;
  box-sizing: border-box;
}

/* The hero's light. One rectangle, 862 × 683, sitting where the frame
   puts it (310, 118 — of which 80 is the bar above), white under a 500px
   blur. Nothing is clipped and nothing is faded at the section seam: the
   blur's own tail lifts the page by a point or two, exactly as it does in
   the frame, and the rectangle simply stops. */
.hero__glow {
  position: absolute;
  top: 38px;
  left: 50%;
  width: 862px;
  height: 683px;
  /* The frame centres it 21px right of the page's centre */
  transform: translateX(calc(-50% + 21px));
  background: #fff;
  filter: blur(500px);
  pointer-events: none;
}

/* The field of type (hero-pattern.js) lies between the glow and the
   words: under everything that reads, over the light. */
.hero__pattern {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none; /* hero-pattern.js lifts its top edge under the bar */
}

.hero .slider__dots {
  position: relative;
  z-index: 1;
}

/* The three slides are stacked in one grid cell, so the tallest sets the
   height once and switching can never move the projects section. */
.hero .hero__viewport {
  position: relative;
  z-index: 1;
  inset: auto;
  display: grid;
  width: 100%;
  overflow: hidden;
  border-radius: 0;
}

.hero .hero__slide {
  position: relative;
  inset: auto;
  grid-area: 1 / 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 0 var(--page-gutter);
  box-sizing: border-box;
}

/* The middle gets its cursor back — there is nothing to navigate there. */
.hero[data-zone="none"] .hero__viewport {
  cursor: default;
}

/* Dots in flow under the composition, not floating over imagery —
   Figma places them at 757, twenty under the subtitle. */
.hero .slider__dots {
  position: static;
  transform: none;
  margin-top: 56px;
  background: var(--color-control-pill-on-page);
}

/* A cut-out on the page — no plate, no radius, nothing to crop it. The
   source is square, so `cover` fits it exactly rather than trimming. */
.hero__portrait {
  width: var(--size-hero-portrait);
  height: var(--size-hero-portrait);
  flex-shrink: 0;
}

.hero__portrait img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* ——— Hero meta ———
   A row of facts, not a sentence: upper-case, opened up, and broken by
   rules that are lighter than the words they separate. The words are
   written in sentence case and raised by CSS, so a screen reader still
   hears them as words. */

.hero__meta {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: var(--space-meta-gap);
  margin: 40px 0 0;
  max-width: min(100%, var(--width-hero));
  font-family: var(--font-family-sans);
  font-weight: var(--font-weight-medium);
  font-size: var(--font-size-meta);
  line-height: var(--line-height-meta);
  letter-spacing: var(--tracking-meta);
  text-transform: uppercase;
  color: var(--color-text-meta);
}

/* Tracking belongs to the words; a single glyph would only be pushed
   off its own centre by it. */
.hero__meta-rule {
  letter-spacing: 0;
  color: var(--color-text-meta-rule);
}

/* The last segment is a pair: a label, then a value carrying a mark. The
   label keeps the row's grey, the value takes the slide's accent — the
   one thing in the hero that is allowed to differ per slide. */
.hero__meta-pair {
  display: inline-flex;
  align-items: center;
  gap: 8px;
}

.hero__meta-value {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  /* Blue was how the kit marked "this will be a link". With nowhere to
     link yet, the value keeps the row's grey, and only a slide that sets
     its own accent (the pink one — a colour, not a link) differs. When the
     links exist, set `--hero-accent` back to the brand blue. */
  color: var(--hero-accent, inherit);
}

.hero__meta--pink {
  --hero-accent: var(--color-accent-pink);
}

/* Either a 16px logo or an emoji, so it is sized both ways. */
.hero__meta-mark {
  flex-shrink: 0;
  width: 16px;
  height: 16px;
  font-size: 14px;
  line-height: 16px;
  letter-spacing: 0;
}

/* ——— Tag ———
   Figma component "Tag" (177:38341), three states. They share a height,
   a radius, a border and the label style; only the palette and the
   leading icon change, so a state is a colour decision, never a size. */

.tag {
  /* The ring is drawn inside, as a shadow rather than a border: Figma
     strokes inside the padding box, so a real border would make every tag
     2px wider than it is drawn. */
  --tag-ring: transparent;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  height: 24px;
  padding: 0 12px;
  border-radius: 900px;
  box-shadow: inset 0 0 0 1px var(--tag-ring);
  box-sizing: border-box;
  font-family: var(--font-family-sans);
  font-weight: var(--font-weight-medium);
  font-size: var(--font-size-label);
  line-height: var(--line-height-label);
  letter-spacing: var(--tracking-label);
  white-space: nowrap;
}

/* State 02 — the quiet one, and now the only one the cards use. The kit
   dropped its ring, so there is none here either: the plate alone is the
   whole tag. */
.tag--neutral {
  background: var(--color-background-primary);
  color: var(--color-text-secondary);
}

/* State 01 — the loud one: the product itself, and the only place the
   deep Direct blue appears. The icon sits in the padding it saves. */
.tag--brand {
  --tag-ring: rgba(0, 0, 0, 0.2);
  background: var(--color-accent-brand-deep);
  color: var(--color-surface-primary);
  padding-left: 4px;
}

/* The Yandex mark — a whole 24px tag rather than a chip with an icon in
   it, so it carries no plate and no ring of its own. It opens the row on
   every project that was built at Yandex. */
.tag--mark {
  width: 24px;
  padding: 0;
  background: none;
  box-shadow: none;
  overflow: hidden;
}

.tag--mark img {
  width: 100%;
  height: 100%;
}

/* State4 — the studio's pill. Like `.tag--mark` it says who built the
   thing, but the studio signs with a wordmark rather than a glyph, so it
   keeps the plate and the 12 of padding a chip has. */
.tag--studio {
  background: var(--color-studio-mark);
}

.tag--studio img {
  display: block;
  width: 31px;
  height: 12px;
}

/* State 03 — the blue one. The cards wore it until the kit moved them to
   State 02, so nothing uses it today; it is kept because the kit still
   draws it. Its ring went the same way as State 02's. */
.tag--case {
  background: var(--color-accent-brand-surface);
  color: var(--color-accent-brand);
  font-size: var(--font-size-project-tag);
  line-height: var(--line-height-project-tag);
  letter-spacing: var(--tracking-project-tag);
}

.tag__icon {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
}

/* H1 */
.hero__title {
  --type-scale: var(--display-scale);
  width: 100%;
  max-width: var(--width-hero-title);
  margin: 32px 0 0;
  font-family: var(--font-family-serif);
  font-weight: var(--font-weight-regular);
  font-style: normal;
  font-size: calc(var(--font-size-h1) * var(--type-scale));
  line-height: calc(var(--line-height-h1) * var(--type-scale));
  letter-spacing: var(--tracking-h1);
  color: var(--color-text-primary);
}

.hero__title em {
  font-family: var(--font-family-serif);
  font-style: italic;
  font-size: inherit;
  line-height: inherit;
  letter-spacing: inherit;
}

/* Body */
.hero__subtitle {
  max-width: var(--width-hero-subtitle);
  margin: 16px 0 0;
  font-family: var(--font-family-sans);
  font-weight: var(--font-weight-medium);
  font-size: var(--font-size-body);
  line-height: var(--line-height-body);
  letter-spacing: var(--tracking-body);
  color: var(--color-text-secondary);
}

/* ——— Selected projects ——— */

.projects-intro {
  width: min(100% - 2 * var(--page-gutter), var(--page-max));
  margin: 0 auto;
  /* A long breath after the hero instead of a rule: the texture ends,
     then air, then the projects (hero-pattern experiment) */
  padding-top: clamp(96px, 11vw, 160px);
}

.projects-intro::before {
  content: "";
  display: block;
  width: 100%;
  height: 1px;
  background: var(--color-border-default);
  margin-bottom: 2rem; /* 32 */
}

/* Section title — Display / XL, matching the hero's weight on the page. */
.projects-intro__title {
  --type-scale: var(--display-scale);
  margin: 0;
  font-family: var(--font-family-serif);
  font-weight: var(--font-weight-regular);
  font-style: normal;
  font-size: calc(var(--font-size-h1) * var(--type-scale));
  line-height: calc(var(--line-height-h1) * var(--type-scale));
  letter-spacing: var(--tracking-h1);
  color: var(--color-text-primary);
}

.projects-intro__title em {
  font-style: italic;
  font-size: inherit;
  line-height: inherit;
  letter-spacing: inherit;
}

/* ============================================================
   ProjectCard — the reusable case card.
   Figma: component "Property 1=Default" (node 16:2).
   Canonical markup: components/project-card.html.

   Anatomy, top to bottom, always in this order:
     .project-card                 the white plate
       .project-card__stage        media area, fixed 1696:939
         .slider__viewport         one .slide per frame; dots are built
                                   by slider.js when there are two or more
       .project-card__caption      tags → title → description
         .project-card__tags       .tag.tag--case, wraps
         .project-card__title      Display / L, <em> for the italic words
         .project-card__description  optional — the Figma component keeps
                                   it hidden; drop the element to omit it

   Modifiers, all on __stage:
     --light        pale backdrop, screenshot inset instead of cropped
     --overdraft    brand-blue backdrop, dots clear of a tall frame
     .slider--on-dark  light dots, for dark frames

   Cards differ by their content alone. Nothing below may be repeated
   under a per-project selector.
   ============================================================ */

.project-card {
  display: flex;
  flex-direction: column;
  gap: 1rem; /* 16 */
  width: 100%;
  margin-top: 4rem;
  padding: 1rem; /* 16 */
  border-radius: 2rem; /* 32 */
  background: var(--color-surface-primary);
  overflow: clip;
  box-sizing: border-box;
}

.project-card + .project-card {
  margin-top: 4rem; /* 64 between cards, matches Figma gap */
}

.project-card__stage {
  position: relative;
  width: 100%;
  aspect-ratio: var(--ratio-card-stage);
  border-radius: 1.5rem; /* 24 */
  background: var(--color-stage);
  overflow: hidden;
}

.project-card__ui {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
}

/* ——— Caption ———
   One column, one gap. The gap lives in the tokens, so the spacing holds
   whether or not the card carries a description. */

.project-card__caption {
  padding: 0 var(--space-card-text-inset);
}

.project-card__tags {
  display: flex;
  flex-wrap: wrap; /* tags fall to the next line rather than shrink */
  gap: var(--space-card-tag-gap);
}

/* Case title */
.project-card__title {
  --type-scale: var(--display-scale);
  margin: var(--space-card-tags-title) 0 0;
  font-family: var(--font-family-serif);
  font-weight: var(--font-weight-regular);
  font-style: normal;
  font-size: calc(var(--font-size-project-title) * var(--type-scale));
  line-height: calc(var(--line-height-project-title) * var(--type-scale));
  letter-spacing: var(--tracking-project-title);
  color: var(--color-text-primary);
}

.project-card__title em {
  font-style: italic;
  font-size: inherit;
  line-height: inherit;
  letter-spacing: inherit;
}

/* One line of description on a wide card, two once it narrows */
.project-card__description {
  margin: var(--space-card-title-description) 0 0;
  font-family: var(--font-family-sans);
  font-weight: var(--font-weight-regular);
  font-size: var(--font-size-project-description);
  line-height: var(--line-height-project-description);
  letter-spacing: var(--tracking-project-description);
  color: var(--color-text-secondary);
}

/* ——— Stage variants ——— */

.project-card__stage--light {
  background: var(--color-stage-soft);
}

.project-card__stage--app {
  background: var(--color-stage-app);
  /* So the phone can be sized against the stage, as on overdraft */
  container-type: inline-size;
}

/* ——— The app shot ———
   A photograph with the phone already in it, so there is nothing to
   build here: it is placed, not composed. Bigger than the stage on three
   sides on purpose — the kit lets the frame crop the hand and the arm,
   and only the right edge is flush. Sized in `cqw` so the crop holds as
   the card narrows, and the height comes from the file's own ratio
   rather than a second number that could drift from it. */

.app-media {
  position: absolute;
  height: auto; /* the file's own ratio; a second number here would skew it */
  max-width: none;
}

/* The photograph, bigger than the stage on three sides — the kit lets the
   frame crop the hand and the arm, and only the right edge is flush. */
.app-media--hand {
  /* The new photograph (3850 × 3607) carries the phone in its top-left
     corner: scaled so the phone keeps its 335 width, and shifted so it
     sits where the old one did — the arm runs out through the bottom
     right, the right edge flush as before. */
  left: 30.95cqw; /* 416 of 1344 */
  top: 2.83cqw; /* 38 */
  width: 69.7cqw; /* 937 */
}

/* Section 5 exports its frames already cropped by the slide, with 10 of
   padding around what is left. So each one is placed at its macket
   position less that 10, and the edge it was cut on carries none. */

/* Group 2131329566 at (83, −104): cut at the top, hence top 0 */
.app-media--screens {
  left: 5.432cqw; /* 73 — 83 less the padding */
  top: 0;
  width: 89.286cqw; /* 1200 */
}

/* Two phones, one behind the other — the kit draws both of these frames
   to the same box, 684 × 841 at (330, 76), cut at the bottom only. One
   class, so a third pair would need nothing new here. */
.app-media--pair {
  left: 23.81cqw; /* 320 */
  top: 4.911cqw; /* 66 */
  width: 52.381cqw; /* 704 */
}

.project-card__stage--overdraft {
  /* 32 above the frame, 24 from the frame down to the dots, 32 below them.
     The clamps hold those figures from ~1200px up and shrink in step with
     the stage — it keeps a fixed aspect ratio — on narrower screens. */
  --stage-inset: clamp(14px, 2.7vw, 32px);
  --stage-dots-gap: clamp(10px, 2vw, 24px);
  --stage-dots-offset: clamp(14px, 2.7vw, 32px);
  background: var(--color-stage-overdraft);
  /* So media and notes can be sized against the stage, as on loyalty */
  container-type: inline-size;
}

/* ——— Overdraft media ———
   Every frame here is centred on the stage at the size the kit draws it,
   in `cqw` so it rides the stage as the card narrows. The exports carry
   their own corners and transparency; only the video is a flat rectangle
   and needs a radius of its own. */

.project-card__stage--overdraft .overdraft-media {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  max-width: none;
}

/* The flow, 1271 × 607 at (39, 41) — centred, like everything else here */
.overdraft-media--video {
  width: 94.568cqw; /* 1271 of 1344 */
  height: 45.164cqw; /* 607 */
  border-radius: var(--radius-stage-media);
  object-fit: cover;
}

/* ——— Simple campaign ———
   A recording of the flow set into the plate the way the kit sets it
   (Figma 213:38031): 1222 × 585 at (62, 50), the bottom margin left
   deeper than the top for the dots. The file is 2040 × 976 — the same
   2.09 ratio, so nothing is cropped. Its corner is the one stage-media
   radius, the same as the overdraft's. */

.project-card__stage--simple {
  background: var(--color-stage-simple);
  container-type: inline-size;
}

.simple-media {
  position: absolute;
  max-width: none;
}

.simple-media--video {
  left: 4.613cqw; /* 62 of 1344 */
  top: 3.72cqw; /* 50 */
  width: 90.923cqw; /* 1222 */
  height: 43.527cqw; /* 585 */
  border-radius: var(--radius-stage-media);
  object-fit: cover;
}

/* Two phones, one hanging off the top edge and one off the bottom — the
   macket crops them on purpose. The export is exactly that cropped view,
   790 × 750 shot at 3x, so it has to bleed to the top and bottom edges of
   the stage. Centre it and the stage clips it the way the frame does. */
.overdraft-media--mobile {
  width: 58.78cqw; /* 790 of 1344; its 790:750 ratio then fills the stage */
  /* The file carries a height attribute; without this it wins */
  height: auto;
}

/* Three onboarding screens, whole — same 3x export, 854 wide. The macket
   hangs them from y 62 rather than centring, which leaves the heavier
   margin under them, clear of the dots. */
.project-card__stage--overdraft .overdraft-media--onboarding {
  width: 63.542cqw; /* 854 */
  /* The file carries a height attribute; without this it wins */
  height: auto;
  top: 4.427cqw; /* 59.5: macket 62 less the export's own ~2.5 of padding */
  transform: translateX(-50%);
}

/* ——— States ———
   One dialog, many states, stacked on the spot and cross-faded. Nothing
   is laid out: every state is absolutely centred, so a taller state can
   never push anything and the stage cannot jump. They differ in height
   because the states themselves do — that breathing is the content, and
   centring keeps it even on both sides. */

.overdraft-states {
  position: absolute;
  inset: 0;
  /* The stack spans the whole frame, but only the state showing is a
     target — pointing anywhere else on the beige is not pointing at the
     dialog, and must not hold the cycle or swallow a click meant for the
     edge zones. */
  pointer-events: none;
}

.overdraft-states img {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 53.571cqw; /* 720 of 1344 */
  height: auto;
  max-width: none;
  transform: translate(-50%, -50%);
  opacity: 0;
  transition: opacity 300ms ease;
  pointer-events: none;
}

/* The one on screen is the hit area the cycle pauses on. It still lets a
   click through to the frame underneath, so paging keeps working here. */
.overdraft-states img[data-current="true"] {
  opacity: 1;
  pointer-events: auto;
}

/* Asked for less motion: the cycle never starts, so the first state
   simply stays — nothing fades under anyone who did not want it to. */
@media (prefers-reduced-motion: reduce) {
  .overdraft-states img {
    transition: none;
  }
}

/* Loyalty — four cut-out frames on one tinted plate. Each export spans
   one whole side of the stage and is transparent around the content, so
   `contain` lands every one of them where its frame draws it: the two
   desktop shots fill the width, the two phone groups fill the height. */
.project-card__stage--loyalty {
  background: var(--color-stage-loyalty);
  /* The Before frame stands on a plate of its own. The colour lives here,
     on the one element that owns the rounded corner, and changes with the
     frame — a second coloured layer inside would show as a hairline of
     this one along the edge, where the two radii fail to meet. */
  transition: background-color var(--slide-duration)
    cubic-bezier(0.22, 1, 0.36, 1);
  /* So a note written over a frame can be sized against the stage. */
  container-type: inline-size;
}

.project-card__stage--loyalty[data-slide="4"] {
  background: var(--color-stage-screenshot);
}

/* ——— Stage note ———
   A handwritten aside pointing at something inside a frame (Figma
   236:43198). Every number is a share of the stage width in `cqw`, so
   the note and its arrow ride the frame instead of drifting off it as
   the card narrows. The note is anchored by its bottom: the arrow
   starts where the writing ends, whatever the text wraps to. */

.stage-note {
  position: absolute;
  width: 17.857cqw; /* 240 — the measure every note is written to */
  margin: 0;
  font-family: var(--font-family-hand);
  font-weight: var(--font-weight-regular);
  font-size: 2.083cqw; /* 28 */
  line-height: 1.786cqw; /* 24 */
  letter-spacing: -0.02em;
  color: var(--stage-note-ink, var(--color-ink-annotation));
}

/* Beside the frame, pointing at one thing inside it */
.stage-note--aside {
  left: 5.655cqw; /* 76 of 1344 */
  top: 27.753cqw; /* 373 — the baseline the arrow leaves from */
  transform: translateY(-100%);
}

/* Over the frame and centred — a label for the whole slide */
.stage-note--label {
  left: 50%;
  top: 4.911cqw; /* 66 */
  transform: translate(-50%, -100%);
  text-align: center;
}

/* The file already carries its own rounded corners and the soft edge
   around them, baked into its pixels. So no radius and no shadow here —
   a CSS corner would cut a second arc across the one in the image — and
   the height follows the file's own ratio rather than cropping it, which
   would slice those corners off. */
.project-card__shot {
  position: absolute;
  left: 50%;
  top: 7.887cqw; /* 106 */
  width: 71.875cqw; /* 966 */
  height: auto;
  transform: translateX(-50%);
}

.stage-note__arrow {
  position: absolute;
  left: 16.927cqw; /* 227.5 — the box plus the stroke it overhangs by */
  top: 29.427cqw; /* 395.5 */
  width: 8.408cqw; /* 113 */
  height: 5.841cqw; /* 78.5 */
}

/* Raw screenshot — inset it instead of cropping */
.project-card__stage--light .project-card__ui,
.project-card__stage--loyalty .project-card__ui,
.project-card__stage--overdraft .project-card__ui {
  object-fit: contain;
}

.project-card__stage--light .project-card__ui {
  padding: clamp(1rem, 2.4vw, 3rem);
}

/* Frames taller than the stage would otherwise run under the dots, so the
   bottom inset reserves the pill's whole band: its offset, its own height
   and the gap above it. The height comes from the component, so a change
   to the pill never leaves a frame sitting on top of it. */
.project-card__stage--overdraft .project-card__ui {
  padding: var(--stage-inset);
  padding-bottom: calc(
    var(--stage-dots-offset) + var(--size-control-pill-height) +
      var(--stage-dots-gap)
  );
}

.project-card__stage--overdraft .slider__dots {
  bottom: var(--stage-dots-offset);
}

/* ——— Slider ———
   Circular, click/swipe driven; dots are built by slider.js. */

.slider {
  /* slider.js reads this to keep its input lock in step with the motion;
     it lives on the slider because the slides' own `transition` is
     rewritten inline while frames wrap. */
  --slide-duration: 520ms;
  position: relative;
}

.slider__viewport {
  position: absolute;
  inset: 0;
  overflow: hidden;
  border-radius: inherit;
  cursor: pointer; /* half the stage back, half forward */
  touch-action: pan-y; /* vertical page scroll stays native */
  -webkit-user-select: none;
  user-select: none;
}

.slider[data-static] .slider__viewport {
  cursor: default;
}

/* ——— Stage cursor ———
   Over a stage with somewhere to turn, the pointer becomes the control:
   the native cursor gives way to a disc that follows it and points at the
   half it would take you to. slider.js builds it only where a fine pointer
   can reach it, and only for two frames or more — so the native cursor is
   never taken away from anyone who would be left without one. */

.slider--has-cursor .slider__viewport {
  cursor: none;
}

.slider__cursor {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 3;
  display: grid;
  place-items: center;
  width: var(--size-control-cursor);
  height: var(--size-control-cursor);
  border-radius: 900px;
  background: var(--color-control-cursor);
  color: var(--color-control-cursor-icon);
  pointer-events: none;
  /* Position and reveal are kept on separate properties: the disc tracks
     the pointer with no easing, while only its entrance is animated. */
  translate: calc(var(--cursor-x, 0px) - 50%) calc(var(--cursor-y, 0px) - 50%);
  scale: 0.6;
  opacity: 0;
  transition: opacity 160ms ease, scale 200ms cubic-bezier(0.22, 1, 0.36, 1);
}

.slider__cursor[data-visible="true"] {
  scale: 1;
  opacity: 1;
}

.slider__cursor svg {
  width: var(--size-control-cursor-arrow-w);
  height: var(--size-control-cursor-arrow-h);
  transition: rotate 260ms cubic-bezier(0.22, 1, 0.36, 1);
}

/* Crossing the middle of the stage turns the arrow round rather than
   swapping it, so the direction reads as one continuous control. */
.slider__cursor[data-direction="prev"] svg {
  rotate: 180deg;
}

/* Opaque by default so a single-frame stage — or a page without JS —
   still shows its image; slider.js fades the off-stage frames out. */
.slide {
  position: absolute;
  inset: 0;
  transition: transform var(--slide-duration) cubic-bezier(0.22, 1, 0.36, 1),
    opacity 320ms cubic-bezier(0.22, 1, 0.36, 1);
  will-change: transform, opacity;
}

.slide--placeholder {
  background: var(--gradient-stage-placeholder);
}

.slide--dark {
  background: linear-gradient(
    160deg,
    color-mix(in srgb, var(--color-surface-dark) 92%, white) 0%,
    var(--color-surface-dark) 60%,
    color-mix(in srgb, var(--color-surface-dark) 82%, black) 100%
  );
}

.slider__dots {
  position: absolute;
  left: 50%;
  bottom: var(--offset-control-pill);
  transform: translateX(-50%);
  z-index: 2;
  display: flex;
  align-items: center;
  gap: var(--gap-control-dot);
  padding: var(--padding-control-pill);
  border-radius: 900px;
  background: var(--color-control-pill-on-light);
}

.slider--on-dark .slider__dots {
  background: var(--color-control-pill-on-dark);
}

.slider__dot {
  display: block;
  width: var(--size-control-dot);
  height: var(--size-control-dot);
  padding: 0;
  border: 0;
  border-radius: 90px;
  background: var(--color-control-dot);
  cursor: pointer;
  transition: background 0.2s ease;
}

.slider__dot[aria-current="true"] {
  background: var(--color-control-dot-active);
}

@media (prefers-reduced-motion: reduce) {
  .slide,
  .slider__cursor,
  .slider__cursor svg {
    transition: none;
  }
}

/* ——— Highlight section ——— */

.highlight {
  width: min(100% - 2 * var(--page-gutter), var(--page-max));
  margin: 0 auto;
  padding: clamp(3.5rem, 6vw, 7rem) 0 0;
  text-align: center;
}

/* Section statement — Display / M */
.highlight__title {
  --type-scale: var(--display-scale);
  margin: 0;
  font-family: var(--font-family-serif);
  font-weight: var(--font-weight-regular);
  font-size: calc(var(--font-size-h2) * var(--type-scale));
  line-height: calc(var(--line-height-h2) * var(--type-scale));
  letter-spacing: var(--tracking-h2);
  color: var(--color-text-primary);
}

.highlight__title em {
  font-style: italic;
  font-size: inherit;
  line-height: inherit;
  letter-spacing: inherit;
}

.highlight .project-card {
  text-align: left;
  margin-top: 2.5rem;
}

/* ——— Other projects ———
   The two cases that were not built at Yandex, closing the page. Figma
   218:51164: a centred heading, then two cards 40 apart — this section
   sets its own rhythm, tighter than the 64 the cards carry by default. */

.other-projects {
  width: min(100% - 2 * var(--page-gutter), var(--page-max));
  margin: 0 auto;
  padding: clamp(3.5rem, 6vw, 6.75rem) 0 0; /* 108 between the sections */
  text-align: center;
}

.other-projects__title {
  --type-scale: var(--display-scale);
  margin: 0;
  font-family: var(--font-family-serif);
  font-weight: var(--font-weight-regular);
  /* The one heading the kit draws at 64 rather than the H1's 60 */
  font-size: calc(64px * var(--type-scale));
  line-height: calc(64px * var(--type-scale));
  letter-spacing: var(--tracking-h1);
  color: var(--color-text-primary);
}

.other-projects__title em {
  font-style: italic;
  font-size: inherit;
  line-height: inherit;
  letter-spacing: inherit;
}

.other-projects .project-card {
  text-align: left;
  margin-top: 2.5rem; /* 40, heading to card and card to card */
}

.project-card__stage--rental {
  background: var(--color-stage-rental);
  container-type: inline-size;
}

.project-card__stage--doubleb {
  background: var(--color-stage-doubleb);
  container-type: inline-size;
}

/* Both frames are one cut-out apiece, placed where the kit places them */
.other-media {
  position: absolute;
  height: auto; /* the file's own ratio */
  max-width: none;
}

.other-media--rental {
  left: 3.199cqw; /* 43 of 1344 */
  top: 4.315cqw; /* 58 */
  width: 93.527cqw; /* 1257 */
}

.other-media--doubleb {
  left: 3.72cqw; /* 50 */
  top: 3.72cqw; /* 50 */
  width: 92.56cqw; /* 1244 */
}

/* ——— Contact ——— */

.contact {
  display: flex;
  align-items: center;
  width: min(100% - 2 * var(--page-gutter), var(--page-max));
  margin: 0 auto;
  padding: 4.5rem 0 3rem; /* 72 from the last card, 48 to the footer rule */
}

/* The portrait, 217 × 253 at 1x, flush against the words (Figma 465:107631) */
.contact__portrait {
  flex: none;
}

.contact__portrait img {
  display: block;
  width: 217px;
  height: 253px;
}

.contact__body {
  min-width: 0;
}

/* Display / XL */
.contact__title {
  --type-scale: var(--display-scale);
  margin: 0;
  font-family: var(--font-family-serif);
  font-weight: var(--font-weight-regular);
  font-style: normal;
  font-size: calc(var(--font-size-h1) * var(--type-scale));
  line-height: calc(var(--line-height-h1) * var(--type-scale));
  letter-spacing: var(--tracking-h1);
  color: var(--color-text-primary);
}

.contact__title em {
  font-style: italic;
  font-size: inherit;
  line-height: inherit;
  letter-spacing: inherit;
}

/* Body / L */
.contact__lead {
  max-width: 62ch;
  margin: 1rem 0 0;
  font-family: var(--font-family-sans);
  font-weight: var(--font-weight-regular);
  font-size: var(--font-size-body);
  line-height: var(--line-height-body);
  letter-spacing: var(--tracking-body);
  color: var(--color-text-secondary);
}

/* Body / M */
.contact__links {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem 2rem;
  margin-top: 1.625rem; /* 26, as the kit draws it */
  font-family: var(--font-family-sans);
  font-weight: var(--font-weight-regular);
  font-size: var(--font-size-body);
  line-height: var(--line-height-body);
  color: var(--color-text-primary);
}

.contact__links a {
  border-bottom: 1px solid var(--color-border-default);
  padding-bottom: 2px;
  transition: border-color 0.2s ease, color 0.2s ease;
}

.contact__links a:hover,
.contact__links a:focus-visible {
  color: var(--color-accent-primary);
  border-bottom-color: var(--color-accent-primary);
}

/* ——— Footer ——— */

.footer {
  width: min(100% - 2 * var(--page-gutter), var(--page-max));
  margin: 0 auto;
  padding: 0 0 3rem;
  font-family: var(--font-family-sans);
  font-weight: var(--font-weight-medium);
  font-size: var(--font-size-label);
  line-height: var(--line-height-label);
  letter-spacing: var(--tracking-label);
  color: var(--color-text-muted);
}

.footer__inner {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1.5rem;
  justify-content: space-between;
  border-top: 1px solid var(--color-border-default);
  padding-top: 1.5rem;
}

/* ——— Responsive ———
   The type shrinks by itself through --display-scale; these rules only
   handle what a narrower page cannot solve by scaling: rows that have
   to wrap, and links that have to step aside. */

@media (max-width: 960px) {
  nav.links .hide-sm {
    display: none;
  }
}

@media (max-width: 720px) {
  .project-card {
    margin-top: 2rem;
    padding: 0.75rem;
    border-radius: 1.25rem;
    gap: 0.75rem;
  }

  .project-card__stage {
    border-radius: 1rem;
  }

  .hero {
    padding-top: 48px;
    padding-bottom: 72px;
  }

  /* The meta line is four segments on one row at full width; here it
     wraps, and the rows need air the single-line gap doesn't give. */
  .hero__meta {
    row-gap: 8px;
  }
}

/* Narrowest: the bar keeps only what a visitor came for — the CV and
   whether she is open to work. */
@media (max-width: 560px) {
  nav.links a:not(.link--external) {
    display: none;
  }
}

/* Below this the name, the link and the pill no longer fit on one line
   and the name would run under the link. It is the first thing to go:
   the portrait still marks home, and the bar has to carry a way out and
   a status before it carries a label. */
@media (max-width: 480px) {
  .brand__name {
    display: none;
  }
}

/* ——— Motion ———
   Calm and brief: things settle into the places they already have.
   Nothing here moves a size, a margin or a breakpoint — only opacity and
   transform, and only once. Durations and the curve live in the tokens. */

/* Entrance. The hero and the bar carry `is-entering` from the markup, so
   the sequence runs without any script: the headline first, the line
   under it a beat later, then the portrait and the meta line, then the
   bar and the dots. Every element gets its own lift through --rise. */
@keyframes rise {
  from {
    opacity: 0;
    transform: translateY(var(--rise, 20px));
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.hero.is-entering .hero__slide:first-child .hero__title {
  --rise: 20px;
  animation: rise var(--motion-hero) var(--ease-out) both;
}

.hero.is-entering .hero__slide:first-child .hero__subtitle {
  --rise: 12px;
  animation: rise var(--motion-standard) var(--ease-out) 150ms both;
}

.hero.is-entering .hero__slide:first-child .hero__portrait,
.hero.is-entering .hero__slide:first-child .hero__meta {
  --rise: 8px;
  animation: rise var(--motion-standard) var(--ease-out) 240ms both;
}

.hero.is-entering .slider__dots {
  --rise: 6px;
  animation: rise var(--motion-standard) var(--ease-out) 540ms both;
}

header.nav.is-entering .brand,
header.nav.is-entering nav.links,
header.nav.is-entering .availability {
  --rise: 6px;
  animation: rise var(--motion-standard) var(--ease-out) both;
}

header.nav.is-entering .brand {
  animation-delay: 300ms;
}

header.nav.is-entering nav.links {
  animation-delay: 390ms;
}

header.nav.is-entering .availability {
  animation-delay: 480ms;
}

/* Reveal on scroll. Hidden only once the page knows a script will show
   it: the inline check in <head> sets `reveal-pending` on <html> when
   IntersectionObserver exists and motion is wanted, and motion.js lifts
   each card as it comes into view — once, never back. A card that is
   already revealed keeps its place; the hover lift lives on the stage so
   the two never fight over one transform. */
.reveal-pending .project-card {
  opacity: 0;
  transform: translateY(var(--reveal-y, 32px)) scale(0.99);
  transition:
    opacity var(--motion-standard) var(--ease-out),
    transform var(--motion-standard) var(--ease-out);
}

.reveal-pending .project-card.is-revealed {
  opacity: 1;
  transform: none;
}

/* Asked for less motion: everything is simply there. */
@media (prefers-reduced-motion: reduce) {
  .hero.is-entering .hero__slide:first-child .hero__title,
  .hero.is-entering .hero__slide:first-child .hero__subtitle,
  .hero.is-entering .hero__slide:first-child .hero__portrait,
  .hero.is-entering .hero__slide:first-child .hero__meta,
  .hero.is-entering .slider__dots,
  header.nav.is-entering .brand,
  header.nav.is-entering nav.links,
  header.nav.is-entering .availability {
    animation: none;
  }

  .reveal-pending .project-card {
    opacity: 1;
    transform: none;
    transition: none;
  }
}
