/* Anton Klimov — personal site */

/* — Type ————————————————————————————————————————————— */
/* Geist covers the Latin only: `unicode-range` keeps Cyrillic on Helvetica
   Neue, so a mixed line («в Skillbox») stays on one set of metrics.
   Google ships a Cyrillic subset too — deliberately not downloaded.

   Файлы урезаны под набор символов сайта, а вариативный Geist разложен
   на два статических начертания (400 и 500 — других в вёрстке нет).
   Причина не в весе страницы: канал до домена душат после ~10 КБ, и
   всё, что крупнее, не доезжает. Потолок для каждого файла — 10 КБ;
   добавляя глиф или начертание, проверяйте размер. Пересобрать:
   pyftsubset + fontTools.varLib.instancer. */

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

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

/* Faculty Glyphic — только для бегущей строки. Файлы лежат рядом с Geist:
   страница не должна ходить на чужие домены ради шрифта. Кириллицы в нём
   нет, но в строке она и не нужна — надпись латиницей. */

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

@font-face {
  font-family: "Faculty Glyphic";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("fonts/faculty-glyphic-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7,
                 U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF,
                 U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB,
                 U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: "Geist";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("fonts/geist-latin-ext-400.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7,
                 U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF,
                 U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB,
                 U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: "Geist";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("fonts/geist-latin-ext-500.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7,
                 U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF,
                 U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB,
                 U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

:root {
  --bg:      #fbfbfa;
  --text:    #0000ff;   /* синий экран смерти — им набрано всё */
  --muted:   #5555f2;   /* тот же синий, отпущенный на полтона */
  --rule:    #c0c0ff;
  --hover:   #000;      /* ховеры — чёрные */

  /* Синий градиент заливает имя. Стопы держатся тёмной половины спектра,
     иначе на светлом фоне буквы теряют контраст. */
  --iris: linear-gradient(105deg,
            #0000ff, #2d5cff, #0a1b8c, #4f86ff, #0000ff);


  --bar:          2.25rem;   /* высота бегущей строки */
  --pad:          1.75rem;
  --gap-section:  4rem;
  --gap-entry:    2.25rem;
  --gap-h2:       1.75rem;
}

/* Тёмной темы нет намеренно: чёрный ховер читается как «нажато» только
   на светлом фоне, а #0000ff на тёмном — уже не тот синий. Светлая схема
   объявлена в <meta name="color-scheme">, чтобы браузер не инвертировал. */

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

[hidden] { display: none; }

/* `.content` ниже объявлен гридом, поэтому скрытый язык гасится правилом
   с той же специфичностью плюс атрибут — без !important. */
.content[hidden] { display: none; }

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

body {
  margin: 0;
  display: flex;
  flex-direction: column;
  background: var(--bg);
  color: var(--text);
  font-family: "Geist", "Helvetica Neue", ui-sans-serif, -apple-system, "Segoe UI", Arial, sans-serif;
  font-size: 1rem;
  line-height: 1.55;
  font-feature-settings: "kern" 1;
  -webkit-font-smoothing: antialiased;
  overflow-wrap: break-word;
  text-transform: lowercase;
}

main {
  width: 100%;
  max-width: 38rem;
  margin: 0 auto;
  padding: 5rem var(--pad) 5rem;
}

/* — Marquee ——————————————————————————————————————————— */
/* Синяя плашка тем же #0000ff, что и текст страницы, вывернутая наизнанку:
   фон синий, буквы цвета бумаги. Лента внутри — две одинаковые половины,
   уезжает ровно на половину ширины, поэтому склейка не видна. Длину и
   скорость считает app.js, здесь только само движение. */

.marquee {
  flex: none;
  height: var(--bar);
  display: flex;
  align-items: center;
  overflow: hidden;
  background: var(--text);
  color: var(--bg);
  font-family: "Faculty Glyphic", "Geist", "Helvetica Neue", sans-serif;
  font-size: .9375rem;
  letter-spacing: .05em;
  white-space: nowrap;
}

.marquee-track {
  display: flex;
  flex: none;
  animation-name: marquee-run;
  animation-timing-function: linear;
  animation-iteration-count: infinite;
}

@keyframes marquee-run {
  to { transform: translateX(-50%); }
}

.marquee .sep {
  padding: 0 1.1rem;
  opacity: .5;
}

/* Бежать без спроса — плохая идея: при запрете анимаций лента замирает. */
@media (prefers-reduced-motion: reduce) {
  .marquee-track { animation: none; }
}

/* Видно только читалкам: одна копия надписи вместо бегущей ленты. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.col-a > * + * { margin-top: 1.75rem; }

/* — Language switch ————————————————————————————————— */

.lang {
  position: absolute;
  /* Плашка стоит выше и в потоке, поэтому отсчёт от её нижнего края. */
  top: calc(var(--bar) + var(--pad));
  right: var(--pad);
  z-index: 1;
  display: flex;
  align-items: baseline;
  gap: .4rem;
  font-size: .9375rem;
  letter-spacing: .04em;
  color: var(--muted);
}

.lang button {
  appearance: none;
  border: 0;
  padding: .5rem .25rem;
  margin: -.5rem -.25rem;
  background: none;
  font: inherit;
  letter-spacing: inherit;
  color: var(--muted);
  cursor: pointer;
  transition: color .15s ease;
}

.lang button[aria-pressed="true"] { color: var(--text); }
.lang button:hover { color: var(--hover); }

.lang button:focus-visible,
a:focus-visible {
  outline: 1px solid var(--text);
  outline-offset: 3px;
  border-radius: 1px;
}

/* — Header, intro, links ————————————————————————————— */

header > * { margin: 0; }

h1 {
  text-transform: none;
  font-size: 2.25rem;
  font-weight: 500;
  letter-spacing: -.02em;
  line-height: 1.2;
}

/* Имя набрано тем же синим, что и всё остальное. Ховер пока просто
   чёрный, как у ссылок, — до отдельного придуманного. Span оставлен:
   в него этот ховер и приедет. */
h1 .name { transition: color .15s ease; }

@media (hover: hover) {
  h1 .name:hover { color: var(--hover); }
}

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

.role  { color: var(--muted); font-size: 1.125rem; }

.intro { max-width: 32rem; font-size: 1.125rem; text-wrap: pretty; }

.nb { white-space: nowrap; }

.links {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: .65rem;
}

.links span { color: var(--rule); }

/* Links carry no colour at rest — only a hairline underline. Colour is the hover. */
a {
  color: inherit;
  text-decoration: underline;
  text-decoration-color: var(--rule);
  text-decoration-thickness: 1px;
  text-underline-offset: .22em;
  transition: color .15s ease, text-decoration-color .15s ease;
}

a:hover {
  color: var(--hover);
  text-decoration-color: currentColor;
}

/* Company links stay invisible until hovered, so the list reads as plain text. */
h3 a {
  text-decoration-color: transparent;
  text-underline-offset: .18em;
}

/* — Sections ————————————————————————————————————————— */

section + section { margin-top: var(--gap-section); }

h2 {
  margin: 0 0 var(--gap-h2);
  padding-bottom: .75rem;
  border-bottom: 1px solid var(--rule);
  font-size: .875rem;
  font-weight: 500;
  letter-spacing: .05em;
  color: var(--muted);
}

/* Заголовок-переключатель: живёт в том же h2, поэтому линия снизу
   и отступы достаются ему даром. */
h2.tabs {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: .45rem;
}

h2.tabs button {
  appearance: none;
  border: 0;
  background: none;
  /* Отрицательный margin гасит padding: цель для пальца больше, линия на месте. */
  padding: .35rem .1rem;
  margin: -.35rem -.1rem;
  font: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  color: var(--muted);
  cursor: pointer;
  transition: color .15s ease;
}

h2.tabs button[aria-pressed="true"] { color: var(--text); }
h2.tabs button:hover { color: var(--hover); }

h2.tabs button:focus-visible {
  outline: 1px solid var(--text);
  outline-offset: 3px;
  border-radius: 1px;
}

h2.tabs span { color: var(--rule); }

/* На узком экране страница и так скроллится — прячем неактивную панель
   совсем, чтобы под короткой вкладкой не оставалось пустоты. */
.tabpanels > .panel.is-off { display: none; }

/* Секция, живущая внутри вкладки, отбивается как обычная соседняя. */
.panel > section { margin-top: var(--gap-section); }

.entries { margin: 0; padding: 0; list-style: none; }

.entries li {
  display: grid;
  grid-template-columns: 7.5rem 1fr;
  gap: 0 1.5rem;
}

.entries li + li { margin-top: var(--gap-entry); }

.year {
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  font-size: .9375rem;
  padding-top: .1rem;
  white-space: nowrap;
}

h3 {
  margin: 0;
  font-size: 1.1875rem;
  font-weight: 500;
}

h3 .title {
  font-weight: 400;
  color: var(--muted);
}

h3 .title::before { content: "— "; }

/* Форматы работы идут без года, поэтому и без левой колонки. */
.entries.plain li { grid-template-columns: 1fr; }

.entries.plain li + li { margin-top: calc(var(--gap-entry) * 1.35); }

.entries p {
  margin: .35rem 0 0;
  color: var(--muted);
  text-wrap: pretty;
}

.entries p + p { margin-top: .65rem; }

/* Подпись слева, содержание справа — та же колонка в 7.5rem, что у года. */
.rows {
  display: grid;
  grid-template-columns: 7.5rem 1fr;
  gap: .5rem 1.5rem;
  margin: .5rem 0 0;
  color: var(--muted);
}

/* Колонки расставляет автоплейсмент — явный grid-column ломал бы
   схлопывание в одну колонку на мобильном. */
.rows dd {
  margin: 0;
  text-wrap: pretty;
}

/* Secondary link row inside an entry — quieter than the header contacts. */
.entries .meta {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: .6rem;
  margin-top: .4rem;
}

.entries .meta span { color: var(--rule); }

/* — Photo ———————————————————————————————————————————— */
/* Место под фотографию: живёт только в двухколоночном режиме на один
   экран, внизу левой колонки, под текстом. В одной колонке (мобилка,
   узкое или низкое окно) его нет вовсе — иначе оно отжимает первый
   экран под себя.

   Картинка выводится как есть, без обработки: приходит готовой из
   content.js. Пока пути там нет, на месте остаётся синий прямоугольник
   с подписью — он и показывает, куда встанет кадр. */

.photo {
  display: none;
  margin: 0;
  background-image: var(--iris);
  overflow: hidden;
}

.photo img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Подпись пустого места — поверх градиента, поэтому светлая. */
.photo .placeholder {
  display: flex;
  height: 100%;
  align-items: flex-end;
  padding: .9rem 1rem;
  font-size: .9375rem;
  line-height: 1.35;
  color: #fff;
  opacity: .85;
}

/* — Two columns, one screen —————————————————————————— */
/* Only where there is genuinely room: below this, the page scrolls normally. */

@media (min-width: 68rem) and (min-height: 45rem) {
  /* Кегль привязан к окну: в большом окне текст крупнее, в маленьком сам
     садится, чтобы страница осталась в один экран. Пределы, за которыми
     самое высокое состояние (русский, вкладка «Проекты») перестаёт влезать,
     измерены — это 1.7vh и 1.24vw; здесь взято заметно ниже, чтобы текст
     не подпирал края и остался спокойным. Сверху 20px: дальше расти незачем.
     Порог ширины — 68rem: в окне уже колонки выходят такими узкими, что
     текст перебирает строк и не влезает, сколько ни уменьшай кегль. Ниже
     45rem по высоте страница честно скроллится одной колонкой. Предел по
     vw держит узкое высокое окно: там кегль по высоте разрешил бы больше,
     чем вытягивают колонки в ширину. */
  html { font-size: clamp(12.5px, min(1.55vh, 1.13vw), 20px); }

  :root {
    --pad:         2.5rem;
    --gap-section: 2.25rem;
    --gap-entry:   1.35rem;
    --gap-h2:      1rem;
  }

  body { height: 100dvh; overflow: hidden; }

  /* Оба языка лежат в одной ячейке грида: высота строки равна самому
     высокому из них и при переключении не меняется — страница не прыгает.
     `safe` — чтобы у высокого языка не срезался верх при центрировании. */
  main {
    /* Остаток высоты под плашкой — иначе страница вылезет ровно на неё. */
    flex: 1;
    min-height: 0;
    max-width: 104rem;
    padding: var(--pad) clamp(2.5rem, 5vw, 6rem);
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    align-content: safe center;
    overflow: auto;          /* safety valve on unusual viewports */
  }

  main > .content { grid-area: 1 / 1; }

  /* Скрытый язык остаётся в потоке и держит высоту — прячем его видимостью,
     а не display: из дерева доступности visibility: hidden его убирает. */
  main > .content[hidden] {
    display: grid;
    visibility: hidden;
  }

  .content {
    width: 100%;
    display: grid;
    grid-template-columns: minmax(0, 4fr) minmax(0, 7fr);
    gap: 0 clamp(4rem, 9vw, 11rem);
    /* stretch, а не start: левая колонка дотягивается до высоты правой,
       и внутри неё есть куда опустить фото. */
    align-items: stretch;
  }

  /* Текст сверху, фото внизу колонки — его прижимает margin-top: auto. */
  .col-a {
    display: flex;
    flex-direction: column;
  }

  .col-a > * + * { margin-top: 1.35rem; }

  .intro { max-width: 27rem; }

  .photo {
    display: block;
    width: 13rem;
    aspect-ratio: 4 / 5;
    /* Потолок в vh: фото не должно вытянуть левую колонку выше правой,
       иначе страница перестанет быть одним экраном. */
    max-height: 34vh;
    /* Прижимает к низу и одновременно гасит отбивку от .col-a > * + *. */
    margin-top: auto;
  }

  /* Экран центрирован по вертикали, поэтому смена высоты секции сдвигает
     всю страницу. Списки вкладок кладём в одну ячейку грида: высота равна
     самой высокой вкладке и при переключении не меняется. */
  .tabpanels { display: grid; }

  .tabpanels > .panel { grid-area: 1 / 1; }

  .tabpanels > .panel.is-off {
    display: block;
    visibility: hidden;
  }
}

/* — Small screens ————————————————————————————————————— */

@media (max-width: 34rem) {
  /* Кегль и воздух ужаты: на телефоне страница иначе уходит на три
     экрана. Базовый размер ведёт за собой всё остальное — вёрстка
     свёрстана в rem.

     12px — это предел. Ниже строки в описаниях слипаются, а на
     iOS мельче 11px текст уже плохо читается с руки. В один экран
     страница всё равно не влезает: список опыта — 18 записей,
     три четверти высоты. Уменьшать дальше смысла нет, надо резать
     список. Замеры при 12px: 1.22 экрана на 390x844, 1.72 на 360x640. */
  html { font-size: 12px; }

  :root {
    --pad:         1.125rem;
    --gap-section: 1.75rem;
    --gap-entry:   1rem;
  }

  main {
    padding: 2.5rem var(--pad) calc(2rem + env(safe-area-inset-bottom));
  }

  .col-a > * + * { margin-top: .875rem; }

  h1 { font-size: 1.75rem; }
  .role { font-size: 1rem; }
  .intro { font-size: 1rem; }

  h2 { padding-bottom: .5rem; }
  h3 { font-size: 1.0625rem; }

  .entries p { margin-top: .25rem; }
  .entries.plain li + li { margin-top: calc(var(--gap-entry) * 1.2); }

  .marquee { font-size: .8125rem; }

  /* Year becomes a label above the entry — same order as the desktop columns. */
  .entries li {
    grid-template-columns: 1fr;
    gap: .15rem;
  }

  .year {
    padding-top: 0;
    font-size: .9375rem;
    letter-spacing: .02em;
  }

  h3 .title { display: block; }
  h3 .title::before { content: none; }

  .rows { grid-template-columns: 1fr; gap: 0; }
  .rows dt { font-size: .9375rem; letter-spacing: .02em; }
  .rows dd + dt { margin-top: .7rem; }

  /* Underlines are the only affordance, so show them on touch too. */
  h3 a { text-decoration-color: var(--rule); }
}

@media print {
  .lang { display: none; }
  .tabpanels > .panel.is-off { display: none; }
  body  { background: #fff; color: #000; height: auto; overflow: visible; }
}
