/* ==========================================================================
   Julia Tarasenko — portfolio
   Editorial minimalism: large name, generous right margin, one accent colour.
   Depth comes from alternating section grounds and hairlines, never shadows.
   ========================================================================== */

/* --- fonts ---------------------------------------------------------------- */

@font-face {
  font-family: 'Anton';
  src: url('fonts/anton-latin.woff2') format('woff2');
  font-weight: 400;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+2000-206F, U+2074, U+20AC, U+2122, U+2212;
}

@font-face {
  font-family: 'Inter';
  src: url('fonts/inter-latin.woff2') format('woff2');
  font-weight: 100 900;              /* variable: one file covers every weight */
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+2000-206F, U+2074, U+20AC, U+2122, U+2212;
}

/* Кириллица. Anton её не содержит вовсе — ни в нашем файле, ни в самом шрифте,
   поэтому русские заголовки набираются Roboto Condensed 900: та же плотность
   чёрного, чуть шире по пропорции. Латинское подмножество тоже подключено:
   в заголовках попадаются Bubble.io и PostgreSQL. */
@font-face {
  font-family: 'Roboto Condensed';
  src: url('fonts/robotocond-cyrillic.woff2') format('woff2');
  font-weight: 900;
  font-display: swap;
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}

/* Anton существует в одном начертании, и оно тяжёлое: все заголовки на сайте
   заданы весом 400 и выглядят плотными. У Roboto Condensed файла с весом 400
   нет, поэтому браузер подменял его синтетическим — русские заголовки выходили
   заметно легче английских. Здесь вес 400 указывает на тот же тяжёлый файл,
   что и 900: одно объявление вместо перечисления всех заголовков. */
@font-face {
  font-family: 'Roboto Condensed';
  src: url('fonts/robotocond-cyrillic.woff2') format('woff2');
  font-weight: 400;
  font-display: swap;
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}

@font-face {
  font-family: 'Roboto Condensed';
  src: url('fonts/robotocond-latin.woff2') format('woff2');
  font-weight: 400;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+2000-206F, U+2074, U+20AC, U+2122, U+2212;
}

@font-face {
  font-family: 'Roboto Condensed';
  src: url('fonts/robotocond-cyrillic-700.woff2') format('woff2');
  font-weight: 700;
  font-display: swap;
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}

@font-face {
  font-family: 'Roboto Condensed';
  src: url('fonts/robotocond-latin.woff2') format('woff2');
  font-weight: 900;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+2000-206F, U+2074, U+20AC, U+2122, U+2212;
}

@font-face {
  font-family: 'Inter';
  src: url('fonts/inter-cyrillic.woff2') format('woff2');
  font-weight: 100 900;
  font-display: swap;
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}

/* --- tokens --------------------------------------------------------------- */

:root {
  --ground: #F5F5F3;
  --ground-alt: #EAEAE7;
  --ink: #121212;
  --accent: #1A7758;                 /* AA на обоих фонах: 5.03 и 4.56 */
  --muted: #6A6A66;                  /* AA на обоих фонах: 4.98 и 4.51 */
  --hairline: #DDDDD9;
  --divider: #C9C9C3;                /* darker than a hairline: used where a
                                        line has to carry a whole column */
  --pad: #FFFFFF;                    /* light pad under screenshots */

  --font-display: 'Anton', 'Arial Narrow', Impact, sans-serif;
  --font-body: 'Inter', -apple-system, BlinkMacSystemFont, 'Helvetica Neue', Arial, sans-serif;

  /* 8px rhythm */
  --s1: 8px;  --s2: 16px; --s3: 24px; --s4: 32px;
  --s5: 40px; --s6: 48px; --s8: 64px; --s10: 80px;
  --s15: 120px;
  --shot-h: 640px;   /* высота снимка в кейсе */

  --container: 1200px;
  --margin: 80px;
  --gutter: 24px;

  --ease: 180ms ease-out;
}

/* Русская версия: заголовки набираются кириллическим шрифтом, всё остальное —
   те же токены. Anton остаётся в стеке для латинских вкраплений. */
html[lang="ru"] {
  --font-display: 'Roboto Condensed', 'Arial Narrow', Impact, sans-serif;
}

/* Имя в первом экране. Русские слова длиннее английских: при том же кегле
   «ЮЛИЯ» с разрядкой занимала 542px против 452 у «JULIA», и этой ширины
   хватало, чтобы распереть левую колонку сетки — правая сжималась с 420 до
   346, и портрет выходил мельче. Поэтому кегль на русском на десятую часть
   меньше, а разрядка и сжатие вернулись к английским пропорциям: обе строки
   встают в 464px, ровно по правому краю абзаца под ними. */
/* Фамилия набирается более лёгким начертанием: при сжатии по горизонтали
   штрихи уплотняются, и на 900 строка выглядела жирнее имени.

   Раз она тоньше, её можно почти не сжимать — вместо этого сильнее разрежено
   имя: четыре буквы против девяти. Числа посчитаны по видимым краям букв, а
   не по боксам строк: у разреженного слова бокс шире последней буквы. */
/* Anton существует в одном начертании, и .name задаёт вес 400. У Roboto
   Condensed такого файла нет, браузер подменял его синтетическим — веса надо
   назвать явно. */
html[lang="ru"] .name {
  font-weight: 900;
  font-size: clamp(58px, 9.92vw, 151px);
}

html[lang="ru"] .name__first {
  letter-spacing: 0.161em;
  margin-right: -0.161em;
}

html[lang="ru"] .name__last {
  font-weight: 700;
  transform: scaleX(0.7527);
}

/* Роль по-русски длиннее английской и на телефоне переносилась в две строки */
html[lang="ru"] .hero__role { letter-spacing: 0.12em; }

/* Портрет в первом экране. Колонки первого экрана — 1fr 1fr, но fr не даёт
   колонке стать уже своего содержимого: русское имя шире английского, оно
   распирает левую колонку и забирает у портрета 40–45px. Портрет мельчает,
   перестаёт доставать до нижней границы экрана, и под ним остаётся зазор.
   Поэтому по-русски правая колонка держится ровно тех же 420px, что и в
   английской версии, а потолок кегля имени опущен со 151 до 141px — ровно
   настолько, чтобы «ЮЛИЯ ТАРАСЕНКО» уложилось в оставшиеся 596px.
   Только на десктопе: ниже 768 первый экран становится одной колонкой. */
@media (min-width: 769px) {
  html[lang="ru"] .hero { grid-template-columns: minmax(0, 1fr) 420px; }
  html[lang="ru"] .name { font-size: clamp(58px, 9.92vw, 141px); }
}

/* Заголовок кейса по-русски. Переносы со дефисом нужны только в узкой колонке
   телефона: на десктопе «СИСТЕМА ФИНАНСОВОГО КОНСУЛЬТИРОВАНИЯ» рвалось как
   «ФИНАНСО-ВОГО КОНСУЛЬТИРО-ВАНИЯ», хотя самое длинное слово (939px при
   кегле 104) свободно умещается в колонку 1040. Без переносов заголовок сам
   встаёт в три строки по словам. */
@media (min-width: 769px) {
  html[lang="ru"] .case-cover__title { hyphens: none; }
}

/* Блок результата из двух чисел. По-английски там короткие «1 month» и «Solo»,
   и трёх колонок хватает. По-русски «1 разработчик» — 340px при кегле 56,
   а колонка в трёхколоннике 242, и число вставало в две строки против одной
   у соседа. Пара чисел делит ширину пополам, и обе строки снова одиночные.
   Английскую версию не трогаем: там верстка и так ровная. */
@media (min-width: 769px) {
  html[lang="ru"] .outcome--pair { grid-template-columns: repeat(2, 1fr); }
}

/* Финальная строка в рамке: «что останется» шире колонки на полтора десятка
   пикселей, и фраза разъезжалась на четыре строки вместо трёх. */
html[lang="ru"] .cta__text { font-size: 25px; }

/* --- reset ---------------------------------------------------------------- */

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

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--ground);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.6;
  font-synthesis-weight: none;
  -webkit-font-smoothing: antialiased;
}

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

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

h1, h2, h3, p, ul, figure, blockquote { margin: 0; }

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

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* --- layout --------------------------------------------------------------- */

.container {
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--margin);
}

.section {
  /* half of the original 120px: the sections were floating too far apart */
  padding-block: var(--s8);
}

.section--alt { background: var(--ground-alt); }

/* Asymmetric grid: label and heading hug columns 1–3, content runs 4–12,
   and the right margin is deliberately left empty. */
.grid {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: var(--gutter);
}

.grid__aside {
  grid-column: 1 / 4;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
}

/* pinned to the foot of the column so it lines up with the bottom of the
   project thumbnails */
.grid__aside .action { margin-top: auto; }
.grid__main  { grid-column: 4 / 13; }

/* --- type ----------------------------------------------------------------- */

.name {
  font-family: var(--font-display);
  font-size: clamp(64px, 11vw, 168px);
  line-height: 0.9;
  letter-spacing: -0.01em;
  text-transform: uppercase;
  font-weight: 400;
}

/* The two lines of the name are set to the same width, the way the reference
   does it. "Julia" is five letters against nine, so matching them by stretching
   the glyphs would double their width and destroy Anton's condensed character.
   Instead each line gets its own size: the short one larger, the long one
   smaller, both flush left and right. Sizes are expressed as a share of the
   name's own font-size, so the block stays proportional at every breakpoint. */
.name__first,
.name__last {
  /* fit-content, not full-width: the box then equals the letters themselves,
     which is what the two lines are matched on */
  display: block;
  width: fit-content;
  line-height: 0.86;
}

.name__first {
  font-size: 1em;
  /* the short line is spread out so it can reach the width of the long one
     without being blown up in size */
  letter-spacing: 0.161em;
  margin-right: -0.161em;   /* trailing tracking would push the right edge out */
}

.name__last {
  /* Only 15% shorter than the first line, per the reference. Matching widths at
     that height means condensing the glyphs horizontally — nine letters have to
     occupy the space of five. */
  font-size: 0.85em;
  transform: scaleX(0.761);
  transform-origin: left top;
  letter-spacing: 0;
  margin-top: 0.105em;      /* air between the two lines */
}

.section-heading {
  font-family: var(--font-display);
  font-size: 28px;
  line-height: 1.15;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  font-weight: 400;
}

.label {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.18em;
  font-weight: 700;
  color: var(--muted);
}

.label--accent { color: var(--accent); }

.big-number {
  font-family: var(--font-display);
  font-size: 56px;
  line-height: 1;
  font-weight: 400;
}

.body { max-width: 48ch; }

.muted { color: var(--muted); }

.rule {
  width: 64px;
  height: 2px;
  background: var(--accent);
  border: 0;
  margin: 0;
}

.hairline {
  height: 1px;
  background: var(--hairline);
  border: 0;
  margin: 0;
}

/* --- header --------------------------------------------------------------- */

.header {
  padding-block: var(--s4);
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: start;
  gap: var(--gutter);
}

.header__tagline { line-height: 1.5; }
.header__mono {
  font-family: var(--font-display);
  font-size: 20px;
  letter-spacing: 0.04em;
  text-align: center;
}

/* On a case page the monogram is the way home: "Back" returns to wherever you
   came from, which on a phone is rarely the front page. Padding gives the
   letters a thumb-sized target without moving the header line. */
.header__mono a {
  display: inline-block;
  padding: 10px 12px;
  margin: -10px -12px;
  transition: color var(--ease);
}
.header__meta { text-align: right; line-height: 1.5; }

/* --- language switch ------------------------------------------------------ */

/* Sits in the same slot as the portfolio label, set in the same utility type:
   two codes split by a slash, the active one in ink, the other muted. */
.header__right {
  display: flex;
  align-items: flex-start;
  justify-content: flex-end;
  gap: var(--s4);
}

.lang {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.18em;
  font-weight: 700;
  white-space: nowrap;
}

/* The letters are 11px, so the tap target is padding, not text. Negative
   margins keep the header line exactly where it was. */
.lang__item {
  color: var(--muted);
  transition: color var(--ease);
  padding: 11px 8px;
  margin: -11px -8px;
}

/* aria-current marks the page's own language, so the state is announced as
   well as shown */
.lang__item[aria-current="true"] { color: var(--accent); }

.lang__sep { color: var(--hairline); }

@media (max-width: 768px) {
  /* the portfolio label is the first thing to go on narrow screens, but the
     language switch has to stay reachable */
  .header__right { gap: var(--s2); }
}

/* --- hero ----------------------------------------------------------------- */

.hero {
  position: relative;
  display: grid;
  grid-template-columns: 1fr 1fr;
  /* start, not center: the name and the portrait must share a top line,
     the way they do in the reference */
  align-items: start;
  gap: var(--gutter);
  /* the figure is allowed to cross the bottom edge of the section */
  overflow: visible;
}

.hero__copy {
  padding-block: var(--s5) 0;
  display: flex;
  flex-direction: column;
  /* минимум между именем, ролью, абзацем и линией: авто-отступы делят остаток
     высоты, но на длинном тексте делить нечего и всё слипается */
  gap: var(--s4);
  align-items: flex-start;
  /* the grid aligns children to the top, but this column has to span the whole
     section so the rule can be pinned to its foot */
  align-self: stretch;
}

.hero__role {
  /* auto on all three of role / paragraph / rule splits the leftover height
     of the column equally, so the gaps between them come out the same */
  margin-top: auto;
  margin-left: 3px;
  font-size: 16px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  font-weight: 700;
  color: var(--accent);
}

.hero__text {
  margin-top: auto;
  max-width: 46ch;
}

/* the rule closes the column 45px above the section edge, which the portrait
   now defines */
.hero__rule { margin-top: auto; margin-bottom: 45px; }

.hero__figure {
  position: relative;
  align-self: start;
  justify-self: center;
  /* The crown of the head sits just below the top of the "J", as in the
     reference. The text column carries a 40px top padding that the figure does
     not, so it has to be repeated here; the extra 4px drop the head slightly
     below the letter rather than level with it. */
  margin-top: calc(var(--s5) + 4px);
  margin-bottom: 0;
  width: min(420px, 100%);
}

/* The accent circle is a separate element, not baked into the image —
   that way it scales independently and the cut-out can overlap the edge. */
.hero__circle {
  position: absolute;
  left: 50%;
  /* the head rises out of the circle: its top edge sits around the temples,
     as in the reference, not above the crown */
  top: 41%;
  transform: translate(-50%, -50%);
  width: 380px;
  aspect-ratio: 1;
  border-radius: 50%;
  background: var(--accent);
  z-index: 0;
}

.hero__portrait { position: relative; z-index: 1; }

/* --- project cards -------------------------------------------------------- */

/* The cards share one set of rows, so number, title, work type, hairline and
   screenshot each line up across all three — even when a title wraps onto a
   second line. Without this the hairline and the image followed the height of
   their own title and stepped down out of line. */
.cards {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  /* three rows: number, the title+type block, the screenshot */
  grid-template-rows: auto auto 1fr;
  /* the vertical gap used to inherit the 24px gutter meant for columns, which
     spread the parts of a card far apart; rows need much less */
  column-gap: var(--gutter);
  row-gap: 10px;
}

.card {
  display: grid;
  grid-row: 1 / -1;
  grid-template-rows: subgrid;
}

.card__pad-wrap { align-self: start; }

.card__num {
  font-family: var(--font-display);
  font-size: 20px;
  color: var(--accent);
  transition: color var(--ease);
}

.card__meta { display: block; }

/* An arrow after the title says the card is a link. It used to be implied by a
   colour change on hover alone, which a touch screen never shows. */
.card__title::after,
.work-list__name::after {
  content: '\2192';
  color: var(--accent);
  display: inline-block;
  margin-left: 0.4em;
  transition: transform var(--ease);
}

.card__title {
  font-size: 16px;
  font-weight: 700;
  letter-spacing: 0.03em;
  line-height: 1.25;
  text-transform: uppercase;
  transition: color var(--ease);
}

.card__type {
  margin-top: 3px;   /* tight inside the block, so it reads as one unit */
  font-size: 14px;
  color: var(--muted);
}

/* The card images are prepared at 4:3 already — short sides are extended with
   the screenshot's own edge colour rather than cropped — so the picture fills
   the pad completely and nothing is cut off. No pad colour shows at rest. */
.card__pad {
  background: transparent;
  overflow: hidden;
  aspect-ratio: 4 / 3;
  transition: background var(--ease);
}

.card__img {
  width: 100%;
  height: 100%;
  object-fit: cover;   /* safe now: the file is already 4:3, nothing to crop */
  transition: transform var(--ease);
}

/* --- action link ---------------------------------------------------------- */

.action {
  display: inline-flex;
  align-items: center;
  gap: var(--s1);
  font-size: 12px;
  color: var(--accent);
  text-transform: uppercase;
  letter-spacing: 0.18em;
  font-weight: 700;
  position: relative;
  padding-bottom: 2px;
}

.action__arrow { transition: transform var(--ease); }

/* scaleX, not width: the same line, drawn on the compositor instead of
   re-running layout on every frame */
.action::after {
  content: '';
  position: absolute;
  left: 0; bottom: 0;
  width: 100%; height: 1px;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--ease);
}

.action:active { transform: translateY(1px); }

/* --- skills, toolkit & impact --------------------------------------------- */

/* The bars were read as a proficiency score. They are years, and the caption
   says so before the first row rather than only at the end of each. */
.skills__caption { margin-top: var(--s3); color: var(--muted); }

/* Two columns split by a hairline, as in the reference: what she can do on the
   left, what it added up to on the right. */
.skills-impact {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--s8);
  align-items: stretch;
}

/* the quote is pinned to the foot of the column so it ends level with the
   toolkit chips on the left */
.skills-impact__right { display: flex; flex-direction: column; }

.skills-impact__left { border-right: 1px solid var(--divider); padding-right: var(--s8); }

.skills__list { margin-top: var(--s4); }

.skill { display: grid; grid-template-columns: 1fr 160px auto; align-items: center; gap: var(--s3); }
.skill + .skill { margin-top: var(--s3); }

.skill__name {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  font-weight: 700;
}

.skill__track {
  height: 4px;
  background: var(--hairline);
  position: relative;
}

.skill__fill {
  position: absolute;
  inset: 0 auto 0 0;
  background: var(--accent);
}

.skill__value { font-size: 12px; color: var(--muted); letter-spacing: 0.06em; }

/* Разработка с AI стоит в том же списке, но без шкалы: подпись над списком
   обещает годы практики, а их тут называть нечего. Значение переносим в третью
   колонку, иначе без шкалы оно встаёт в середину строки, а не к «5 yrs». */
.skill--note .skill__value { grid-column: 3; white-space: nowrap; }

.chips { display: flex; flex-wrap: wrap; gap: var(--s1); }

.chip {
  border: 1px solid var(--hairline);
  padding: 6px 10px;
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--muted);
}

/* --- numbers & experience ------------------------------------------------- */

.numbers {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--gutter);
  margin-top: var(--s6);
}

.numbers__label { margin-top: var(--s1); }

.role { display: grid; grid-template-columns: 160px 1fr; gap: var(--gutter); padding-block: var(--s3); }
.role + .role { border-top: 1px solid var(--hairline); }
.role__years { font-size: 12px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--muted); }
.role__title { font-weight: 700; }
.role__note { color: var(--muted); margin-top: 2px; }

/* --- about ---------------------------------------------------------------- */

/* Heading and copy stack in the left column; the portrait spans both rows of
   the right one, so the crown of the head lines up with the top of the word
   ABOUT rather than with the text below it. */
.about {
  display: grid;
  grid-template-columns: 1fr 1fr;
  /* the two rows have to be declared: "grid-row: 1 / -1" on the portrait counts
     lines of the explicit grid only, and against implicit rows it would span
     just the first one */
  grid-template-rows: auto auto;
  column-gap: var(--s8);
  row-gap: var(--s5);
  align-items: stretch;
}
.about__title { grid-column: 1; }
.about__copy { grid-column: 1; }
.about__copy { max-width: 60ch; }
.about__copy p + p { margin-top: var(--s3); }

/* The portrait runs from the first line of text down to the section boundary:
   the negative margin eats the section's bottom padding, so the shoulders end
   exactly where the section does. The image is positioned rather than in flow —
   otherwise its own height would feed back into the row height it depends on. */
.about__figure {
  position: relative;
  grid-column: 2;
  grid-row: 1 / -1;
  /* The glyph of a 28px Anton heading starts 3.8px below the top of its line
     box (measured against the font's own metrics), so the figure drops by the
     same amount and the head starts level with the letter A, not with the
     invisible box around it. */
  margin-top: 4px;
  margin-bottom: calc(var(--s8) * -1);
  min-height: 320px;
}

/* <picture> is inline by default: in the stacked layout, where the image sits
   in normal flow, that left a 16px baseline gap under it and the portrait
   overshot the section edge by exactly that much */
.about__figure picture { display: block; }
.about__figure img {
  position: absolute;
  right: 0;
  bottom: 0;
  height: 100%;
  width: auto;
  max-width: 100%;
  object-fit: contain;
  object-position: bottom right;
}

.pull {
  font-family: var(--font-display);
  font-size: 28px;
  line-height: 1.2;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin-block: var(--s5);
}

/* --- testimonials --------------------------------------------------------- */

/* Three plain columns: an accent quote mark, the words, and who said them.
   No frames, no rules, no numbering — the white space does the separating. */
.quotes {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  /* две строки на всю группу: слова отзыва и подпись под ними. Карточки делят
     их через subgrid, поэтому имена начинаются на одной линии независимо от
     того, на сколько строк разошлась цитата или название проекта. */
  grid-template-rows: auto auto;
  gap: var(--s6);
  margin-top: var(--s5);
  align-items: start;
}

.quote {
  display: grid;
  grid-row: 1 / -1;
  grid-template-rows: subgrid;
  gap: var(--s2);
  height: 100%;
}

.quote__mark {
  font-family: var(--font-display);
  font-size: 20px;
  color: var(--accent);
  margin-right: 5px;
  vertical-align: -2px;
}

.quote__text { font-size: 16px; line-height: 1.6; flex: 1; }

.quote__by {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  font-weight: 700;
  /* no extra margin: the column's own gap is the whole distance */
}

.quote__role { color: var(--muted); font-weight: 400; display: block; margin-top: 2px; letter-spacing: 0.1em; }
/* The project name under a testimonial links to that case. The arrow makes the
   link visible without hover, which a phone never provides. */
.quote__project {
  color: var(--accent);
  display: inline-block;
  margin-top: 4px;
  letter-spacing: 0.14em;
}

.quote__project-arrow {
  display: inline-block;
  margin-left: 0.35em;
  transition: transform var(--ease);
}

.quote--inline {
  display: flex;
  flex-direction: column;
  gap: var(--s3);
  border: 1px solid var(--hairline);
  margin-top: auto;
  padding: var(--s4);
  /* the shared .quote rule sets height:100% so cards in a row match each
     other; this one stands alone and must keep the height of its content */
  height: auto;
  flex: none;
}

.quote--inline .quote__text {
  font-size: 16px;
  /* the shared rule stretches quote text to fill its column; here the card
     must stay the height of its own content and simply sit at the bottom */
  flex: none;
}

/* --- contact -------------------------------------------------------------- */

/* Three blocks in a row, as in the reference: the invitation on the left, the
   contacts in the middle behind a vertical hairline, the call to action on the
   right. The CTA is stretched to the height of the contact list so the two
   right-hand blocks share a baseline. */
.contact {
  display: grid;
  grid-template-columns: 1fr 1.1fr 0.9fr;
  gap: var(--s8);
  align-items: stretch;
}

.contact__list {
  /* same weight as the divider in Skills & Toolkit: a line that separates two
     halves of a section has to read at a glance, a hairline does not */
  border-left: 1px solid var(--divider);
  padding-left: var(--s8);
  /* the three items span the same height as the CTA beside them: first one
     level with its top edge, last one with its bottom. The gap is the minimum
     spacing, space-between distributes whatever height is left over. */
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: var(--s4);
}
.contact__item { display: grid; grid-template-columns: 28px 1fr; gap: var(--s2); align-items: start; }
/* no rules between the three items — the label above each value already
   separates them, and the spacing does the rest */
.contact__icon { color: var(--accent); margin-top: 2px; }
.contact__value { margin-top: 2px; display: inline-block; }

.cta {
  /* the grey frame was barely visible against the alternate ground; green
     states outright that the block is the thing to click */
  border: 1px solid var(--accent);
  padding: var(--s5);
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  transition: box-shadow var(--ease);
}

.cta__text {
  font-family: var(--font-display);
  font-size: 28px;
  line-height: 1.25;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.cta__arrow { align-self: flex-end; color: var(--accent); transition: transform var(--ease); }
/* hover thickens the frame by a pixel. An inset shadow rather than a wider
   border: a border change would reflow the text inside by that pixel. */

.footer {
  border-top: 1px solid var(--hairline);
  margin-top: var(--s10);
  padding-block: var(--s4);
  display: flex;
  justify-content: space-between;
  gap: var(--s3);
  flex-wrap: wrap;
}

/* same trick in the footer: the links were 18px tall, below the 24px minimum */
.footer__links { display: flex; gap: var(--s2); }
.footer__links a { padding: 11px 8px; margin: -11px -8px; }

/* Skip link: parked off-screen until focused, then a plain block in the top
   left. Not display:none — that would drop it out of the tab order, which is
   the one thing it exists for. */
.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 10;
  background: var(--ink);
  color: var(--ground);
  padding: var(--s2) var(--s3);
  font-size: 12px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.skip-link:focus { left: 0; }
/* the skip target takes focus programmatically and needs no ring of its own */
#content:focus { outline: none; }

/* Hover states live behind a real pointer. On a touch screen the state sticks
   after a tap and the card stays lit until something else is tapped. */
@media (hover: hover) and (pointer: fine) {
  .lang__item:hover { color: var(--ink); }
  .header__mono a:hover { color: var(--accent); }

  .card:hover .card__num,
  .card:hover .card__title { color: var(--accent); }
  .card:hover .card__title::after,
  .work-list__item:hover .work-list__name::after { transform: translateX(4px); }
  .card:hover .card__pad { background: var(--ground); }
  .card:hover .card__img { transform: scale(1.02); }

  .contact__value:hover { color: var(--accent); }

  .cta:hover { box-shadow: inset 0 0 0 1px var(--accent); }
  .cta:hover .cta__arrow { transform: translate(6px, -6px); }

  .next-project:hover .next-project__name { color: var(--accent); }
  .next-project:hover .next-project__arrow { transform: translateX(6px); }

  .work-list__item:hover .work-list__name { color: var(--accent); }

  .quote__project:hover .quote__project-arrow { transform: translateX(4px); }
  .work-list__item:hover .work-list__arrow { transform: translateX(6px); }

  .action:hover::after { transform: scaleX(1); }
  .action:hover .action__arrow { transform: translateX(6px); }
}

/* Press feedback on the big click targets: a tap on a phone had no reply at
   all, since hover never fires there. */
.card:active,
.work-list__item:active,
.cta:active,
.next-project:active { transform: scale(0.995); }

.card, .work-list__item, .cta, .next-project { transition: transform 140ms ease-out; }

/* --- motion --------------------------------------------------------------- */

/* Everything here hangs off .js, a class the document sets for itself before
   the stylesheet loads. Without scripts the class never appears, no rule hides
   anything, and the page reads in full — previously a blocked or failed
   main.js left fifteen of fifteen blocks at opacity 0 forever. */
@media (prefers-reduced-motion: no-preference) {
  .js .reveal { opacity: 0; transform: translateY(16px); }
  .js .reveal.is-visible {
    opacity: 1;
    transform: none;
    transition: opacity 500ms ease-out, transform 500ms ease-out;
  }
  .js .hero__line { opacity: 0; animation: line-in 500ms ease-out forwards; }
  .js .hero__line:nth-child(2) { animation-delay: 120ms; }
}

/* Uses `translate`, not `transform`: the surname carries a scaleX of its own,
   and an animation on `transform` would override it once it finished. */
@keyframes line-in {
  from { opacity: 0; translate: 0 16px; }
  to   { opacity: 1; translate: 0 0; }
}

/* --- responsive ----------------------------------------------------------- */

@media (max-width: 1024px) {
  :root { --margin: 48px; --gutter: 20px; }
  .hero__figure { width: min(340px, 100%); }
  .hero__circle { width: 300px; }
  .skill { grid-template-columns: 1fr 110px auto; }
}

@media (max-width: 768px) {
  :root { --margin: 32px; --s15: 88px; }

  .hero {
    grid-template-columns: 1fr;
    min-height: auto;
    padding-top: var(--s6);
  }
  .hero__copy { padding-block: 0 var(--s8); }
  .hero__figure { justify-self: start; margin-bottom: 0; }

  .grid__aside, .grid__main { grid-column: 1 / -1; }
  .grid__main { margin-top: var(--s5); }

  /* A tablet is not a phone. Numbers, testimonials and the contact block hold
     two columns comfortably at 768 — stacking everything there doubled the page
     to 7370px while half the width sat empty. They collapse below 640 instead. */
  /* Two cards per row. The shared subgrid has to be released first: with it in
     place every card spans all three rows, and the third card would open an
     implicit third column instead of starting a second row. */
  .cards { grid-template-columns: 1fr 1fr; grid-template-rows: none; row-gap: var(--s6); }
  .card { grid-row: auto; grid-template-rows: none; }
  .numbers, .quotes { grid-template-columns: 1fr 1fr; gap: var(--s5); }
  .contact { grid-template-columns: 1fr 1fr; gap: var(--s6); }
  /* the third testimonial and the third number take the full width of the row */
  .quotes > :last-child:nth-child(odd),
  .numbers > :last-child:nth-child(odd) { grid-column: 1 / -1; }
  /* общие строки отзывов живут только в раскладке из трёх колонок: здесь
     третья карточка занимает свою строку целиком, и субгрид, оставшись,
     схлопывал её до сорока пикселей */
  .quotes { grid-template-rows: none; }
  .quote { grid-row: auto; grid-template-rows: none; }
  .cta { grid-column: 1 / -1; }

  .skills-impact, .about { grid-template-columns: 1fr; gap: var(--s6); }
  /* stacked, the portrait has no text column to match in height: back to flow,
     below the copy, but it still runs down to the section boundary. The column
     and row spans have to be released too — with one column declared, "column
     2" would open an implicit one beside the text. */
  .about { grid-template-rows: none; }
  .about__title, .about__copy, .about__figure { grid-column: 1; grid-row: auto; }
  /* Stacked, the portrait ends the section, so the section simply stops where
     the image does. Pulling it down with a negative margin, as the two-column
     layout does, misses by the amount the row loses to that same margin. */
  .section--about { padding-bottom: 0; }
  .about__figure { min-height: 0; margin-top: 0; margin-bottom: 0; }
  .about__figure img { position: static; height: auto; width: 100%; max-width: 340px; }
  /* stacked, the columns follow one another — the divider has nothing left to
     divide, and the spacing separates them */
  .contact__list { border-left: 0; padding-left: 0; }
  /* the vertical divider turns horizontal when the columns stack */
  .skills-impact__left { border-right: 0; border-bottom: 1px solid var(--divider); padding-right: 0; padding-bottom: var(--s6); }

  .role { grid-template-columns: 1fr; gap: var(--s1); }
  .header { grid-template-columns: 1fr auto; }
  .header__meta { display: none; }
}

/* Phone width: everything that was still paired now stacks. */
@media (max-width: 640px) {
  .cards, .numbers, .quotes, .contact { grid-template-columns: 1fr; }
  /* в столбик общие строки не нужны: каждая карточка сама по себе. Оставленный
     субгрид загонял бы все три в две объявленные строки и открывал неявные
     колонки за краем экрана */
  .quotes { grid-template-rows: none; }
  .quote { grid-row: auto; grid-template-rows: none; }
  .cards { grid-template-rows: none; }
  .card { grid-row: auto; grid-template-rows: none; }
  .numbers { gap: var(--s5); }
  .contact__list { border-left: 0; padding-left: 0; }
}

@media (max-width: 480px) {
  :root { --margin: 20px; --s15: 72px; }
  .name { font-size: clamp(44px, 13vw, 64px); }
  .section-heading, .pull, .cta__text { font-size: 24px; }
  .big-number { font-size: 44px; }
  /* tap targets stay at least 44px tall */
  .action, .contact__value { min-height: 44px; display: inline-flex; align-items: center; }
}

/* ==========================================================================
   Case study pages
   ========================================================================== */

/* barely anything under the cover image: the next block brings its own 40px,
   and two full paddings in a row pushed Context most of a screen away. 24px is
   just enough that the rule does not touch the screenshot. */
.case-cover { padding-block: var(--s8) var(--s3); }

/* Русские названия длиннее английских, и одно слово может оказаться шире
   экрана — «консультирования» на 390px занимает 380 при 350 доступных.
   Переносы делят его по слогам вместо того, чтобы вытолкнуть строку за край. */
.case-cover__title {
  font-family: var(--font-display);
  hyphens: auto;
  /* не меньше четырёх букв до переноса и трёх после: без этого браузер рвёт
     слово по первой возможной точке и получается «ФИ-НАНСОВОГО» */
  hyphenate-limit-chars: 8 4 3;
  overflow-wrap: break-word;
  font-size: clamp(48px, 8vw, 104px);
  line-height: 0.95;
  text-transform: uppercase;
  letter-spacing: -0.01em;
  font-weight: 400;
}

.case-cover__what { margin-top: var(--s3); }

.case-cover__meta {
  margin-top: var(--s4);
  display: flex;
  flex-wrap: wrap;
  gap: var(--s3);
}

/* No pad behind the cover: the screenshots are trimmed to their own edges in
   prep_images.py, and the plate only added grey around them. */
.case-cover__image {
  margin-top: var(--s5);
  overflow: hidden;
}

/* label left, content right — the same asymmetry as the home page */
/* Halved from 80px: every block on the page keeps the same distance from the
   rule above it, and the heading no longer floats away from its own text. */
.case-block { padding-block: var(--s5); }
.case-block + .case-block { border-top: 1px solid var(--hairline); }

.case-block__body p + p { margin-top: var(--s3); }

.scope { display: grid; gap: 0; }
.scope__row { display: grid; grid-template-columns: 200px 1fr; gap: var(--gutter); padding-block: var(--s3); }
.scope__row + .scope__row { border-top: 1px solid var(--hairline); }

/* Process: text and images share one right edge — the text column used to stop
   at 48ch, well short of where the screenshots below it ended, and the two read
   as belonging to different layouts. */
/* The gap below a screenshot equals the gap above it: at 80px the next step's
   heading drifted away from the images it followed. */
.process__step + .process__step { margin-top: var(--s5); }
.process__text p + p { margin-top: var(--s3); }

/* No plate behind the screenshots: the phone mock-ups are cut out of their grey
   backdrop in prep_images.py, so the device itself stands on the page. */
.process__figure { margin: var(--s5) 0 0; }

/* One height for every screenshot in a case, so the steps read as a series
   rather than a pile of different pictures. A cap rather than a fixed height:
   wide desktop shots hit the column width first and simply come out shorter,
   without being stretched or padded to reach the number. */
.process__figure img {
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: var(--shot-h);
  margin-inline: auto;
}

.process__caption {
  margin-top: var(--s2);
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--muted);
}

/* a pair of narrow phone screens sits side by side rather than stacked huge */
.process__pair { display: grid; grid-template-columns: 1fr 1fr; gap: var(--gutter); margin-top: var(--s5); }
.process__pair .process__figure { margin-top: 0; }

/* Числа результата бывают разной длины: по-русски «В одиночку» встаёт в две
   строки, а соседнее «1 месяц» — в одну, и подписи под ними расходятся.
   Подсетка держит подписи на одной линии независимо от высоты числа.
   В колонку (768 и уже) подсетку снимаем: там у каждой карточки своя строка,
   а grid-row: 1 / -1 свёл бы их все в одни и те же две. */
.outcome {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: auto auto;
  gap: var(--gutter);
}

.outcome > * {
  display: grid;
  grid-row: 1 / -1;
  grid-template-rows: subgrid;
}

/* The closing testimonial of a case is its own block, not a column in a row of
   three: it runs the full width of the column, with the name under the words
   and nothing behind it. The shared .quote rule could not be reused — its
   height:100% is there to level three cards against each other, and here it
   made the quote overflow its section and sit on top of the next-project
   link. */
.case-quote { margin-top: var(--s5); }

.case-quote__mark {
  font-family: var(--font-display);
  font-size: 20px;
  line-height: 1;
  color: var(--accent);
}

.case-quote__text { margin-top: var(--s2); line-height: 1.6; }
.case-quote__text p + p { margin-top: var(--s3); }

.case-quote__by {
  margin-top: var(--s4);
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  font-weight: 700;
}

.case-quote__role {
  color: var(--muted);
  font-weight: 400;
  display: block;
  margin-top: 2px;
  letter-spacing: 0.1em;
}

.outcome__note { margin-top: var(--s1); }

/* The sentence that closes the numbers runs the full width of the column, like
   the testimonial under it — at 48ch it broke after five words and looked
   clipped next to the block below. */
.outcome__lead { margin-top: var(--s5); }

.next-project {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: var(--s4);
  padding-block: var(--s8);
  border-top: 1px solid var(--hairline);
}

.next-project__num { font-family: var(--font-display); font-size: 20px; color: var(--accent); }
.next-project__name {
  font-family: var(--font-display);
  font-size: clamp(28px, 5vw, 56px);
  text-transform: uppercase;
  line-height: 1;
  transition: color var(--ease);
}
.next-project__arrow { color: var(--accent); transition: transform var(--ease); }

/* work index */
.work-list__item {
  display: grid;
  grid-template-columns: 88px 1fr 190px;
  gap: var(--s4);
  /* the number used to float to the middle of the row, level with the
     description instead of the name it belongs to */
  align-items: start;
  padding-block: var(--s3);
  border-top: 1px solid var(--hairline);
}
/* no closing rule under the last item: the footer's own line follows a few
   dozen pixels below, and two rules read as a mistake */
.work-list__num {
  font-family: var(--font-display);
  font-size: clamp(24px, 2.4vw, 34px);
  color: var(--accent);
}
/* Колонки сетки по умолчанию не сжимаются уже своего содержимого, и одно
   длинное слово — «консультирования» — распирало строку за край экрана.
   min-width: 0 разрешает колонке сжаться, переносы делят слово по правилам
   языка, а не по буквам. */
.work-list__item > * { min-width: 0; }

.work-list__name {
  font-family: var(--font-display);
  hyphens: auto;
  overflow-wrap: break-word;
  font-size: clamp(34px, 3.4vw, 48px);
  text-transform: uppercase;
  line-height: 1.05;
  transition: color var(--ease);
}

.work-list__thumb { background: var(--pad); aspect-ratio: 4 / 3; overflow: hidden; }
.work-list__thumb img { width: 100%; height: 100%; object-fit: cover; }

@media (max-width: 768px) {
  .scope__row { grid-template-columns: 1fr; gap: var(--s1); }
  .process__pair { grid-template-columns: 1fr; }
  .outcome { grid-template-columns: 1fr; grid-template-rows: none; gap: var(--s5); }
  .outcome > * { display: block; grid-row: auto; grid-template-rows: none; }
  /* The thumbnails used to be hidden here, leaving a visual portfolio with no
     visuals on a phone. They move under the text instead. */
  .work-list__item { grid-template-columns: 56px 1fr; row-gap: var(--s3); }
  .work-list__thumb { grid-column: 2; }
  .next-project { grid-template-columns: auto 1fr auto; gap: var(--s3); padding-block: var(--s6); }
}

/* --- телефон: первый экран ------------------------------------------------ */

@media (max-width: 640px) {
  /* Шапка перестраивается: монограмма уходит в левый край, переключатель языка
     в правый, а строка о том, чем занимается Юля, спускается под них. В три
     колонки, как на десктопе, эти три вещи на 390px не помещаются, и раньше
     переключатель отрывался вниз к левому краю. */
  .header {
    grid-template-columns: auto 1fr;
    grid-template-areas:
      "mono lang"
      "tag  tag";
    row-gap: var(--s2);
    column-gap: var(--s3);
  }
  .header__mono { grid-area: mono; text-align: left; }
  .header__right { grid-area: lang; justify-self: end; }
  .header__tagline { grid-area: tag; }

  /* Имя по центру: слева оно занимало половину ширины, и экран читался пустым.
     Колонка выравнивает детей по содержимому, поэтому заголовку нужна полная
     ширина — иначе центрировать строки не в чем. Точка отсчёта сжатия фамилии
     переезжает в центр: иначе буквы уехали бы влево внутри своей коробки. */
  .hero__copy { align-items: stretch; }
  .name { width: 100%; }
  .name__first,
  .name__last { margin-inline: auto; }
  .name__last { transform-origin: center top; }

  /* По-русски у первой строки отрицательное правое поле — оно съедает
     трейлинг-разрядку, чтобы на десктопе буквы упирались в край колонки.
     Здесь строки центрируются полями auto, и одно фиксированное поле
     прижимало «ЮЛИЯ» к правому краю. Возвращаем auto, а разрядку
     уравновешиваем таким же отступом слева. */
  html[lang="ru"] .name__first {
    margin-right: auto;
    padding-left: 0.161em;
  }

  /* Расстояния между именем, ролью, абзацем и линией держит gap колонки;
     собственные отступы здесь только удваивали бы его. */
  .hero__role, .hero__text { margin-top: 0; }
  .hero__rule { margin-top: 0; margin-bottom: 0; }

  /* Портрет по центру и ближе к тексту: между линией и снимком было больше
     ста пикселей пустоты — колонка ещё несла нижнее поле десктопной раскладки. */
  .hero { gap: var(--s3); }
  .hero__copy { padding-block: 0; }
  .hero__figure { justify-self: center; }

  /* Отзыв под цифрами прижимается к низу колонки автоотступом; в столбик
     делить нечего, и он вставал вплотную к «Loans migrated…». */
  .quote--inline { margin-top: var(--s5); }

  /* Название кейса стояло в 64px под шапкой — на маленьком экране это
     четверть первого экрана впустую. */
  .case-cover { padding-block: var(--s4) var(--s3); }

  /* Два телефонных экрана рядом, как в остальных шагах. В столбик они
     занимали два экрана прокрутки на один шаг рассказа. */
  .process__pair { grid-template-columns: 1fr 1fr; gap: var(--s2); }

  /* Заголовок раздела и его текст разделяли 60px — на десктопе они стоят в
     соседних колонках и такой отступ разводит их по горизонтали, а в столбик
     он просто рвёт связь между названием и тем, что под ним. Одно значение на
     все разделы: Context, Role & scope, Process, Outcome. */
  .grid__main { margin-top: var(--s2); }

  /* Между заголовком раздела и его содержимым набегало 52px: 20 от row-gap
     сетки, 16 от собственного отступа колонки и ещё 16 внутри блока. На
     телефоне это читается как разрыв, а не как воздух — оставляем половину. */
  .case-block { row-gap: var(--s1); }
  .case-block .grid__main { margin-top: 0; }

  /* И сами разделы ближе друг к другу */
  .case-block { padding-block: var(--s4); }
  .scope__row { padding-block: var(--s2); }
}

/* Самое длинное название в списке проектов. «КОНСУЛЬТИРОВАНИЯ» — 312px при
   кегле 34, а колонка рядом с номером всего 262, и браузер рвал слово
   дефисом посередине. На телефоне это название набирается на кегль мельче
   и переносится по словам: «Система / финансового / консультирования»,
   стрелка уходит на четвёртую строку. */
@media (max-width: 640px) {
  html[lang="ru"] .work-list__name--long {
    font-size: 28px;
    hyphens: none;
  }
}
