/* ═══════════════════════════════════════════════════════════════════════
   SHARED.CSS — Codex Studio v0.5
   ──────────────────────────────────────────────────────────────────────
   Общие стили для index.html и free-assets.html.
   Содержит: globals, layout, sidebar, header, footer, theme,
   tags-dropdown, work-card (базовые), cards-toggle, contact-btn,
   game-switch, custom cursor, mobile sidebar.
   
   Portfolio-only стили (case-view, blueprints, 3D, fullscreen overlay)
   вынесены в css/portfolio.css.
   
   v0.5 [#3]: разделение — было main.css 96.6 KB, стало:
              shared.css ~55 KB + portfolio.css ~40 KB.
              Free-assets грузит только shared.css + free-assets.css.
   ═══════════════════════════════════════════════════════════════════════ */

/* ═══════════════════════════════════════════════════
   MAIN — Codex Studio
   Layout, header, toggle, tags, cards, main area
═══════════════════════════════════════════════════ */

body {
  overflow: hidden;
  position: relative;
  isolation: isolate;
}

/* ═══════════════════════════════════════════════════════════════════
   ATMOSPHERIC LAYERS — film grain + vignette
     body::before → classic film-flicker noise (translate3d / steps(2)), z:-2
     body::after  → vignette radial gradient,                              z:-1
   Адаптация техники Valentin Bossens: 11 рандомных фаз с translate3d,
   steps(2) = 2 дискретных кадра → «дрожание» плёнки.
   Смягчено на ∼15-20% от референса:
     • opacity 0.15 → 0.13
     • range ±9rem → ±7rem (-22%)
     • 1s → 1.2s (темп -17%)
   Используем свой SVG-тайл (не внешняя wikimedia картинка) — zero network.
═══════════════════════════════════════════════════════════════════ */
/* ═══════════════════════════════════════════════════════════════════
   SKIP-TO-CONTENT (v0.21.0)
   Visually hidden до :focus — keyboard users (Tab из address bar)
   получают переход на <main id="main">. Над preloader/cursor (10002).
═══════════════════════════════════════════════════════════════════ */
.skip-to-content {
  position: fixed;
  top: 0;
  left: var(--space-2);
  transform: translateY(-150%);
  z-index: 10002;
  padding: var(--space-2) var(--space-4);
  background: var(--color-primary);
  color: var(--color-text-inverse);
  font-family: var(--font-display);
  font-size: var(--text-sm);
  font-weight: 500;
  text-decoration: none;
  border-radius: var(--radius-sm);
  transition: transform 0.2s var(--ease-out);
}
.skip-to-content:focus,
.skip-to-content:focus-visible {
  transform: translateY(var(--space-2));
  outline: none;
}

body::before {
  content: '';
  position: fixed;
  /* Запас ±7rem с каждой стороны = max amplitude translate,
     чтобы при прыжках не обнажался пустой край. */
  top: -7rem;
  left: -7rem;
  width: calc(100% + 14rem);
  height: calc(100% + 14rem);
  z-index: -2;
  pointer-events: none;
  background-image: url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='320' height='320'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch' seed='3'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: 320px 320px;
  opacity: 0.13;
  animation: grain-flicker 1.2s steps(2) infinite;
}

/* ВИНЬЕТКА (над grain, под контентом) */
body::after {
  content: '';
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background: radial-gradient(
    ellipse at center,
    transparent 40%,
    rgba(0, 0, 0, 0.32) 90%,
    rgba(0, 0, 0, 0.55) 100%
  );
}

/* Light mode — ослабляем grain и vignette */
[data-theme="light"] body::before { opacity: 0.08; }
[data-theme="light"] body::after {
  background: radial-gradient(
    ellipse at center,
    transparent 55%,
    rgba(0, 0, 0, 0.06) 90%,
    rgba(0, 0, 0, 0.11) 100%
  );
}

/* Mobile — чуть меньше opacity на высоком DPR */
@media (max-width: 767px) {
  body::before { opacity: 0.10; }
}

/* ─────────────────────────────────────
   Film flicker: 11 рандомных позиций translate3d.
   steps(2) даёт 2 дискретных кадра между фазами = «прыгающее» зерно.
   Значения сжаты на 22% от референса (±9rem → ±7rem) — мягче.
───────────────────────────────────── */
@keyframes grain-flicker {
  0%   { transform: translate3d(  0,     7rem,   0); }
  10%  { transform: translate3d(-0.8rem,-3.1rem, 0); }
  20%  { transform: translate3d(-6.2rem, 1.6rem, 0); }
  30%  { transform: translate3d( 7rem,  -7rem,   0); }
  40%  { transform: translate3d(-1.6rem, 5.5rem, 0); }
  50%  { transform: translate3d(-7rem,  -3.1rem, 0); }
  60%  { transform: translate3d( 1.6rem, 4.7rem, 0); }
  70%  { transform: translate3d( 5.5rem,-6.2rem, 0); }
  80%  { transform: translate3d(-7rem,   0.8rem, 0); }
  90%  { transform: translate3d( 4.7rem,-3.9rem, 0); }
  100% { transform: translate3d(-5.5rem, 0,      0); }
}

/* Уважаем reduced-motion: полная остановка (не замедление).
   transform сбрасывается в none — не застрявает на последней фазе keyframe. */
@media (prefers-reduced-motion: reduce) {
  body::before {
    animation: none;
    transform: none;
  }
}

/* ─────────────────────────────────────────
   LAYOUT
   Desktop: sidebar 340px + main (анимируем через --sidebar-w)
   Mobile:  sidebar на всю ширину, main скрыт
───────────────────────────────────────── */
.layout {
  display: grid;
  grid-template-columns: var(--sidebar-w) 1fr;
  height: 100dvh;
  transition: grid-template-columns 320ms var(--ease-out);
}

@media (max-width: 767px) {
  .layout {
    grid-template-columns: 1fr;
    transition: none;
  }
}

/* ─────────────────────────────────────────
   SIDEBAR
───────────────────────────────────────── */
.sidebar {
  display: flex;
  flex-direction: column;
  height: 100dvh;
  overflow: hidden;
  border-right: 1px solid var(--color-divider);
  /* bg убран (был --color-bg) — noise слой body::before виден сквозь sidebar.
     Базовый фон идёт от <html> { background: var(--color-bg) }. */
}

@media (max-width: 767px) {
  .sidebar { border-right: none; }
}

/* ═══════════════════════════════════
   HEADER
═══════════════════════════════════ */
.site-header {
  flex-shrink: 0;
  padding: var(--space-4);
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.header-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
}
.header-top__controls {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  flex-shrink: 0;
}

/* ═══════════════════════════════════
   THEME TOGGLE — light ↔ dark (v0.13.7)
═══════════════════════════════════ */
.theme-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  padding: 0;
  background: transparent;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-tag);
  color: var(--color-text-muted);
  cursor: pointer;
  flex-shrink: 0;
  position: relative;
  transition:
    color 160ms var(--ease-out),
    border-color 160ms var(--ease-out),
    background 160ms var(--ease-out);
}
.theme-toggle:hover {
  color: var(--color-text);
  border-color: var(--color-text-muted);
}
.theme-toggle:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
}
.theme-toggle__icon {
  width: 18px;
  height: 18px;
  display: block;
  position: absolute;
  inset: 50% auto auto 50%;
  transform: translate(-50%, -50%) scale(1);
  transition: opacity 180ms var(--ease-out), transform 220ms var(--ease-out);
}
/* dark-режим (дефолт): виден MOON, SUN скрыт */
[data-theme="dark"] .theme-toggle__icon--sun,
.theme-toggle__icon--sun {
  opacity: 0;
  transform: translate(-50%, -50%) scale(0.6) rotate(-45deg);
}
[data-theme="dark"] .theme-toggle__icon--moon,
.theme-toggle__icon--moon {
  opacity: 1;
  transform: translate(-50%, -50%) scale(1) rotate(0deg);
}
/* light-режим: виден SUN, MOON скрыт */
[data-theme="light"] .theme-toggle__icon--sun {
  opacity: 1;
  transform: translate(-50%, -50%) scale(1) rotate(0deg);
}
[data-theme="light"] .theme-toggle__icon--moon {
  opacity: 0;
  transform: translate(-50%, -50%) scale(0.6) rotate(45deg);
}
@media (prefers-reduced-motion: reduce) {
  .theme-toggle__icon { transition: opacity 0ms; transform: translate(-50%, -50%) scale(1) rotate(0deg) !important; }
}

/* ═══════════════════════════════════
   LANG TOGGLE — RU ↔ EN (v0.8.x)
   Геометрия идентична .theme-toggle. Содержимое — текстовый код языка
   (показывает противоположный язык — то, на что переключит клик).
═══════════════════════════════════ */
.lang-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  padding: 0;
  background: transparent;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-tag);
  color: var(--color-text-muted);
  cursor: pointer;
  flex-shrink: 0;
  font-family: inherit;
  transition:
    color 160ms var(--ease-out),
    border-color 160ms var(--ease-out),
    background 160ms var(--ease-out);
}
.lang-toggle:hover {
  color: var(--color-text);
  border-color: var(--color-text-muted);
}
.lang-toggle:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
}
.lang-toggle__current {
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  line-height: 1;
}
@media (prefers-reduced-motion: reduce) {
  .lang-toggle { transition: none; }
}

/* ═════════════════════════════════════
   CONTACT BTN — Telegram CTA (v0.13.8)
   Является братом theme-toggle — в desktop collapsed фиксируется top-right.
   Центрирована по логотипу через align-items:center в .header-top.
═════════════════════════════════════ */
.contact-btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  height: 32px;
  padding: 0 12px;
  font-family: var(--font-body);
  font-size: var(--text-xs);
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--color-text);
  background: var(--color-surface-2);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-tag);
  text-decoration: none;
  cursor: pointer;
  flex-shrink: 0;
  line-height: 1;
  transition: background 160ms var(--ease-out), border-color 160ms var(--ease-out), color 160ms var(--ease-out);
}
.contact-btn:hover {
  background: var(--color-surface-offset);
  border-color: var(--color-accent-text);
  color: var(--color-accent-text);
}
.contact-btn:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
}
.contact-btn__icon {
  width: 14px;
  height: 14px;
  flex: 0 0 14px;
  color: currentColor;
}
.contact-btn__label { display: inline; }

/* v0.13.8.2 — Desktop: #contact-btn в sidebar header-top, icon-only 32×32
   (в одной строке с theme-toggle и HIDE PROJECTS, все по 32px).
   v0.14.0 [7] — на desktop обе sidebar/case-копии Contact скрыты в пользу .top-pill--contact. */
@media (min-width: 768px) {
  /* v0.14.0 [7]: #contact-btn (sidebar) на desktop скрыт — роль CTA взял .top-pill--contact.
     Mobile оставляем как было — #contact-btn остаётся в .header-top__controls. */
  #contact-btn { display: none; }
}

/* TOP PILL — стили item'а; контейнер .top-pills удалён, pill живёт в .site-footer обеих страниц */
.top-pill {
  pointer-events: auto;
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  /* v0.14.1 [3][4] — height 28 → 32px (визуальный паритет с .cards-toggle). */
  height: 32px;
  padding: 0 var(--space-3);
  font-family: var(--font-body);
  font-size: var(--text-xs);
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-text-muted);
  background: var(--color-surface-2);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-tag);
  white-space: nowrap;
  text-decoration: none;
  cursor: pointer;
  line-height: 1;
  transition:
    color 160ms var(--ease-out),
    border-color 160ms var(--ease-out),
    background 160ms var(--ease-out);
}
.top-pill:hover {
  color: var(--color-text);
  border-color: var(--color-text-muted);
}
.top-pill:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
}
.top-pill__icon {
  width: 14px;
  height: 14px;
  flex: 0 0 14px;
  color: currentColor;
}
/* v0.7.12 [UX] — top-pill--free accent для discoverability (Free Assets и back-to-portfolio).
   Применяется только в активном (не disabled) состоянии. v0.7.13: только border в
   --color-primary; icon наследует --color-text от parent (согласовано с другими accent-кнопками). */
.top-pill--free:not([disabled]) {
  border-color: var(--color-primary);
  color: var(--color-text);
}
.top-pill--free:not([disabled]):hover {
  color: var(--color-text);
  border-color: var(--color-primary-hover);
  background: var(--color-primary-highlight);
}
.top-pill--free[disabled] {
  cursor: not-allowed;
  opacity: 0.72;
}
.top-pill--free[disabled]:hover {
  color: var(--color-text-muted);
  border-color: var(--color-border);
  background: var(--color-surface-2);
}
.top-pill--contact { color: var(--color-text); }

/* ─── LOGO ─── */
.logo {
  display: inline-flex;
  align-items: center;
  text-decoration: none;
  color: var(--color-text);
  transition: opacity 180ms var(--ease-out);
}
.logo:hover { opacity: 0.7; }
.logo:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 4px;
  border-radius: 2px;
}
.logo__text {
  font-family: var(--font-display);
  font-size: var(--text-xl);
  font-weight: 600;
  letter-spacing: 0.14em;
  color: var(--color-text);
  line-height: 1;
}
/* ═══════════════════════════════════
   CARDS TOGGLE — кнопка Hide / Show
═══════════════════════════════════ */
.cards-toggle {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  height: 32px;
  padding: 0 var(--space-3);
  background: transparent;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-tag);
  color: var(--color-text-muted);
  font-family: var(--font-body);
  font-size: var(--text-xs);
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  cursor: pointer;
  transition:
    color 160ms var(--ease-out),
    border-color 160ms var(--ease-out),
    background 160ms var(--ease-out);
  flex-shrink: 0;
}
.cards-toggle:hover {
  color: var(--color-text);
  border-color: var(--color-text-muted);
}
.cards-toggle:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
}
/* v0.14.0 [8] — icons стали текстом (‹‹ / ››): inline-flex бокс фикс. размера
   чтобы layout не скакал при toggle. font-size 18px — визуальный паритет со старым 16px SVG.
   v0.14.1 [2] — visual bearing у шевронов «‹‹» / «››» смещён в нижнюю часть
   глифа (шрифт рисует их ближе к baseline). Поднимаем сам символ на 2px
   через внутренний translateY, чтобы он визуально сидел по центру кнопки. */
.cards-toggle__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  flex-shrink: 0;
  font-family: var(--font-body);
  font-size: 18px;
  line-height: 1;
  font-weight: 500;
  letter-spacing: -0.04em;
  /* v0.14.1 [2] — bearing шевронов вниз компенсируем подъёмом на 2px. */
  transform: translateY(-2px);
}
.cards-toggle__icon--closed { display: none; }
.cards-toggle[aria-expanded="false"] .cards-toggle__icon--open   { display: none; }
.cards-toggle[aria-expanded="false"] .cards-toggle__icon--closed { display: inline-flex; }

/* На лейбл по умолчанию — только иконка, текст скрывается на узком */
.cards-toggle__label {
  white-space: nowrap;
}

/* v0.13.8.3 — P4 fix: desktop icon-only 32×32.
   Блок размещён ПОСЛЕ базовых .cards-toggle, чтобы override padding сработал
   (иначе source-order: base `padding: 0 var(--space-3)` бил @media-override из header-top). */
@media (min-width: 768px) {
  .cards-toggle {
    width: 32px;
    padding: 0;
    justify-content: center;
  }
  .cards-toggle .cards-toggle__label { display: none; }
  .cards-toggle__icon { width: 16px; height: 16px; }
}

/* ═══════════════════════════════════
   TAGS
═══════════════════════════════════ */
/* v0.8.2: .tag / .tag--active / .tag--badge удалены — DOM использует
   .tags-dropdown__* с v0.15.5, ни одного class="tag" не осталось. */

/* ══════════════════════════════════════════
   v0.15.5 [П2] — TAGS DROPDOWN (mobile + desktop)
   Инпутподобный триггер с chips + выпадающий список с чекбоксами.
   OR-логика фильтра: chip в триггере = пульс по OR между выбранными дисциплинами.
═════════════════════════════════════════ */
.tags-dropdown {
  position: relative;
  width: 100%;
}

.tags-dropdown__trigger {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  width: 100%;
  min-height: 44px;
  padding: 6px 36px 6px 10px;
  font-family: var(--font-body);
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  background: transparent;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  cursor: pointer;
  text-align: left;
  box-sizing: border-box;
  transition:
    border-color 160ms var(--ease-out),
    background 160ms var(--ease-out);
}
.tags-dropdown__trigger:hover {
  border-color: var(--color-text-muted);
}
.tags-dropdown__trigger:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
}
.tags-dropdown[data-open="true"] .tags-dropdown__trigger {
  border-color: var(--color-primary);
}

.tags-dropdown__chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  flex: 1 1 auto;
  min-width: 0;
}
.tags-dropdown__chips:empty { display: none; }

.tags-dropdown__placeholder {
  flex: 1 1 auto;
  min-width: 0;
  font-family: var(--font-body);
  font-size: var(--text-xs);
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.tags-dropdown[data-has-chips="true"] .tags-dropdown__placeholder {
  display: none;
}

.tags-dropdown__chevron {
  position: absolute;
  top: 50%;
  right: 12px;
  transform: translateY(-50%);
  color: var(--color-text-muted);
  transition: transform 200ms var(--ease-out), color 160ms var(--ease-out);
  flex-shrink: 0;
}
.tags-dropdown[data-open="true"] .tags-dropdown__chevron {
  transform: translateY(-50%) rotate(180deg);
  color: var(--color-text);
}

/* Chip внутри триггера — визуально как активный .tag, + крестик для удаления */
.tags-dropdown__chip {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 28px;
  padding: 0 6px 0 10px;
  font-family: var(--font-body);
  font-size: var(--text-xs);
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-text);
  background: var(--color-primary-highlight);
  border: 1px solid var(--color-primary);
  border-radius: var(--radius-tag);
  white-space: nowrap;
  flex-shrink: 0;
}
.tags-dropdown__chip-remove {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  padding: 0;
  margin: 0;
  font-family: var(--font-body);
  font-size: 14px;
  line-height: 1;
  color: var(--color-text);
  background: transparent;
  border: none;
  border-radius: var(--radius-full);
  cursor: pointer;
  flex-shrink: 0;
  transition: background 160ms var(--ease-out);
}
.tags-dropdown__chip-remove:hover {
  background: color-mix(in oklab, var(--color-text) 12%, transparent);
}
.tags-dropdown__chip-remove:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 1px;
}

/* v0.2.1 [П1] — Mobile overlay: блокирует клики/скролл поверх всего сайта кроме dropdown.
   Desktop (≥768px) — display: none, десктопное поведение не трогаем. */
.tags-dropdown__overlay {
  display: none;
}
@media (max-width: 767px) {
  .tags-dropdown__overlay {
    display: none; /* по-дефолту скрыт до открытия dropdown */
    position: fixed;
    inset: 0;
    z-index: 30;          /* ниже panel (40), выше case-view/остального UI */
    background: transparent;
    touch-action: none;    /* блокирует скролл пальцем */
    pointer-events: auto;
    cursor: default;
  }
  .tags-dropdown[data-open="true"] .tags-dropdown__overlay {
    display: block;
  }
  /* Триггер остаётся кликабельным над overlay (можно закрыть повторным тапом). */
  .tags-dropdown[data-open="true"] .tags-dropdown__trigger {
    position: relative;
    z-index: 35;
  }
}

/* Panel с чекбоксами */
.tags-dropdown__panel {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  right: 0;
  z-index: 40;
  display: flex;
  flex-direction: column;
  padding: 6px;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-md);
  max-height: 320px;
  overflow-y: auto;
}
.tags-dropdown__panel[hidden] { display: none; }

.tags-dropdown__option {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: 40px;
  padding: 0 10px;
  font-family: var(--font-body);
  font-size: var(--text-xs);
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-text-muted);
  border-radius: var(--radius-sm);
  cursor: pointer;
  user-select: none;
  transition: background 160ms var(--ease-out), color 160ms var(--ease-out);
}
.tags-dropdown__option:hover {
  background: var(--color-surface-2);
  color: var(--color-text);
}
.tags-dropdown__option:has(.tags-dropdown__checkbox:checked) {
  color: var(--color-text);
}

.tags-dropdown__checkbox {
  appearance: none;
  -webkit-appearance: none;
  width: 16px;
  height: 16px;
  margin: 0;
  background: transparent;
  border: 1px solid var(--color-border);
  border-radius: 3px;
  cursor: pointer;
  flex-shrink: 0;
  position: relative;
  transition: background 160ms var(--ease-out), border-color 160ms var(--ease-out);
}
.tags-dropdown__checkbox:hover {
  border-color: var(--color-text-muted);
}
.tags-dropdown__checkbox:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
}
.tags-dropdown__checkbox:checked {
  background: var(--color-primary);
  border-color: var(--color-primary);
}
.tags-dropdown__checkbox:checked::after {
  content: "";
  position: absolute;
  left: 4px;
  top: 1px;
  width: 5px;
  height: 9px;
  border: solid var(--color-bg);
  border-width: 0 2px 2px 0;
  transform: rotate(45deg);
}

.tags-dropdown__label {
  flex: 1 1 auto;
  min-width: 0;
}

/* ─── TAGS DIVIDER — 1px horizontal rule between tags cloud and game-switch */
.tags-divider {
  height: 1px;
  background: var(--color-divider);
  margin: var(--space-2) 0 var(--space-1);
  width: 100%;
}

/* ─── GAME SWITCH — native checkbox with custom track/thumb */
.game-switch {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-1) 0;
  cursor: pointer;
  user-select: none;
  -webkit-user-select: none;
}
.game-switch__input {
  position: absolute;
  opacity: 0;
  width: 0; height: 0;
  pointer-events: none;
}
.game-switch__track {
  position: relative;
  display: inline-block;
  width: 34px;
  height: 20px;
  background: var(--color-surface-2);
  border: 1px solid var(--color-border);
  border-radius: 999px;
  transition: background 200ms var(--ease-out), border-color 200ms var(--ease-out);
  flex-shrink: 0;
}
.game-switch__thumb {
  position: absolute;
  top: 2px; left: 2px;
  width: 14px; height: 14px;
  background: var(--color-text-muted);
  border-radius: 50%;
  transition: transform 200ms var(--ease-out), background 200ms var(--ease-out);
}
.game-switch__input:checked + .game-switch__track {
  background: var(--color-primary-highlight);
  border-color: var(--color-primary);
}
.game-switch__input:checked + .game-switch__track .game-switch__thumb {
  transform: translateX(14px);
  background: var(--color-primary);
}
.game-switch__input:focus-visible + .game-switch__track {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
}
.game-switch__label {
  font-family: var(--font-body);
  font-size: var(--text-xs);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-text-muted);
  transition: color 160ms var(--ease-out);
}
.game-switch:hover .game-switch__label { color: var(--color-text); }
.game-switch__input:checked ~ .game-switch__label { color: var(--color-primary); }

/* ═══════════════════════════════════
   CARDS SCROLL
═══════════════════════════════════ */
.cards-scroll {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;
  padding: var(--space-3) var(--space-4) var(--space-8);

  scrollbar-gutter: stable;
  scrollbar-width: thin;
  scrollbar-color: var(--color-border) transparent;
}
.cards-scroll::-webkit-scrollbar        { width: 6px; }
.cards-scroll::-webkit-scrollbar-track  { background: transparent; }
.cards-scroll::-webkit-scrollbar-thumb  {
  background: var(--color-border);
  border-radius: 2px;
}

/* Скрытое состояние (когда toggle выключен) */
.cards-scroll[hidden] { display: none; }

/* v0.14.0 [14] — sidebar__row: row с game-switch слева и cards-count справа.
   Заменяет прежний .sidebar__divider между header'ом и .cards-scroll. */
.sidebar__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  width: 100%;
}

.cards-count {
  font-size: var(--text-xs);
  color: var(--color-text-faint);
  letter-spacing: 0.06em;
  /* v0.14.0 [14] — без border-bottom/margin-bottom (раньше были декором
     старого положения в .cards-scroll). Теперь внутри row'а в header. */
  white-space: nowrap;
}

.cards-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

/* ═══════════════════════════════════
   WORK CARD
   Структура для tilt-эффекта:
   .work-card (корень, GSAP-цель) → __thumb + __info
═══════════════════════════════════ */
.work-card {
  position: relative;
  background:
    linear-gradient(180deg, var(--color-work-card-bg-hover), var(--color-work-card-bg) 46%),
    var(--color-work-card-bg);
  border: 1px solid var(--color-work-card-border);
  border-radius: var(--radius-card);
  overflow: hidden;
  cursor: pointer;

  /* Нужен для tilt: 3D-контекст */
  transform-style: preserve-3d;
  perspective: 800px;
  will-change: transform;

  transition:
    border-color 200ms var(--ease-out),
    background   200ms var(--ease-out),
    box-shadow   240ms var(--ease-out);
}
.work-card::before {
  content: "";
  position: absolute;
  inset: 0 0 auto;
  height: 2px;
  background: var(--color-primary);
  opacity: 0;
  pointer-events: none;
  transition: opacity 200ms var(--ease-out);
  z-index: 4;
}
.work-card:hover {
  border-color: var(--color-work-card-border-hov);
  background: var(--color-work-card-bg-hover);
  box-shadow: var(--shadow-md);
}
.work-card:hover::before,
.work-card--active::before {
  opacity: 1;
}
.work-card:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
}
.work-card[hidden] { display: none; }

/* Активная карточка — открытый кейс выделен основным цветом.
   Рамка цвелой периметр (border-left запрещён в build_rules). */
.work-card--active {
  border-color: var(--color-primary);
  background: var(--color-work-card-bg-hover);
  box-shadow: 0 0 0 1px var(--color-primary) inset,
              var(--shadow-sm);
}
.work-card--active .work-card__cat {
  /* активная карточка уже подсвечена акцент-рамкой — text на высококонтрастный (15:1) */
  color: var(--color-text);
}
.work-card--active:hover {
  border-color: var(--color-primary);
  background: var(--color-work-card-bg-hover);
}

/* Превью-контейнер — содержит градиент (placeholder) + <img> сверху */
.work-card__thumb {
  width: 100%;
  aspect-ratio: 4 / 3;
  overflow: hidden;
  background: var(--color-work-card-bg-hover);
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
}
.work-card__thumb::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 3;
  pointer-events: none;
  background:
    linear-gradient(180deg, transparent 42%, oklch(0 0 0 / 0.48) 100%),
    linear-gradient(135deg, var(--color-primary-highlight), transparent 58%),
    repeating-linear-gradient(
      135deg,
      transparent 0,
      transparent 31px,
      color-mix(in srgb, var(--color-primary) 14%, transparent) 32px,
      transparent 33px
    );
  opacity: 0.72;
}
/* Лейбл плейсхолдера — показывается всегда.
   Когда <img> реально загрузился, он перекрывает текст (z-index и непрозрачный img). */
.work-card__thumb::after {
  content: attr(data-label);
  font-family: var(--font-display);
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--color-text-faint);
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1;
  pointer-events: none;
  opacity: 0.78;
  text-shadow: 0 1px 0 color-mix(in srgb, var(--color-bg) 72%, transparent);
  transform: translateY(0.18rem);
}
.work-card__thumb img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  z-index: 2;
  transition: transform 400ms var(--ease-out);
}
.work-card:hover .work-card__thumb img {
  transform: scale(1.03);
}

/* v0.13.4 — clip-path reveal для миниатюр карточек.
   Закрытое состояние ставится ТОЛЬКО при no-preference — при
   prefers-reduced-motion картинка видна сразу (нет флеша из невидимого).
   Без will-change — GSAP выставит его сам на время tween (Safari-safe).
   clear-props после tween убирает inline — не мешает hover-scale. */
@media (prefers-reduced-motion: no-preference) {
  .work-card__thumb img.is-clip-reveal {
    clip-path: inset(0 100% 0 0);
  }
}

/* Инфо */
.work-card__info {
  display: grid;
  gap: var(--space-1);
  padding: var(--space-3);
}
.work-card__meta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  margin-bottom: var(--space-1);
}
.work-card__cat {
  font-size: var(--text-xs);
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  /* текстовый акцент через выделенный WCAG-токен (было --color-primary — 3.10:1 dark и 4.07:1 light) */
  color: var(--color-accent-text);
}
.work-card__year {
  font-size: var(--text-xs);
  /* v0.5: --color-text-faint (#8a8884 = 3.67:1 на surface-2) → --color-text-muted
     (#a8a6a2 = 5.27:1) для WCAG AA. Аналогично AX1 fix в free-assets.css.
     Это улучшает contrast на work-card в index.html и tag-card на FA. */
  color: var(--color-text-muted);
}
.work-card__meta-tail {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-width: 0;
}
.work-card__hint {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.35rem;
  height: 1.35rem;
  border: 1px solid var(--color-work-card-border);
  border-radius: var(--radius-tag);
  color: var(--color-accent-text);
  line-height: 1;
  transform: translateY(-0.02rem);
  transition:
    border-color 160ms var(--ease-out),
    color 160ms var(--ease-out),
    background 160ms var(--ease-out);
}
.work-card:hover .work-card__hint,
.work-card--active .work-card__hint {
  background: var(--color-primary-highlight);
  border-color: var(--color-primary);
  color: var(--color-text);
}
.work-card__title {
  font-family: var(--font-display);
  font-size: var(--text-base);
  font-weight: 600;
  color: var(--color-text);
  line-height: 1.25;
  margin: 0;
}
.work-card__desc {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  line-height: 1.55;
  margin: 0;
}

/* ═══════════════════════════════════
   MAIN AREA — контейнер для case-view
═══════════════════════════════════ */
.main-area {
  display: flex;
  align-items: stretch;
  justify-content: stretch;
  height: 100dvh;
  overflow: hidden;
  /* bg убран (был --color-bg) — noise виден сквозь main-area.
     Иллюстрации в .case-item__media имеют свой bg (--color-surface),
     3D canvas и blueprints canvas — тоже непрозрачны. */
}

@media (max-width: 767px) {
  .main-area { display: none; }
}

/* ════════════════════════════════════
   COLLAPSED (desktop) — rail 56px
   Остаётся только кнопка Show projects вертикально
════════════════════════════════════ */
@media (min-width: 768px) {
  body.cards-collapsed .site-header {
    padding: var(--space-3) var(--space-2);
    align-items: center;
  }
  body.cards-collapsed .header-top {
    flex-direction: column;
    gap: var(--space-3);
    width: 100%;
  }
  body.cards-collapsed .logo,
  body.cards-collapsed .tags-dropdown,
  body.cards-collapsed .tags-divider,
  body.cards-collapsed .sidebar__row,
  body.cards-collapsed .theme-toggle,
  body.cards-collapsed .contact-btn,
  body.cards-collapsed .lang-toggle { display: none; }

  body.cards-collapsed .cards-toggle {
    width: 32px;
    height: 32px;
    padding: 0;
    justify-content: center;
  }
  body.cards-collapsed .cards-toggle__label { display: none; }
  body.cards-collapsed .cards-toggle__icon  { width: 16px; height: 16px; }
}

/* ════════════════════════════════════
   MOBILE — «гамбургер» сверху, sidebar-оверлей
   Кнопка ОСТАЁТСЯ наверху в обоих состояниях (fixed).
   При collapsed — sidebar уезжает за край, показывается main-area.
════════════════════════════════════ */
@media (max-width: 767px) {
  /* Sidebar как выезжающий оверлей */
  .sidebar {
    position: fixed;
    inset: 0;
    z-index: 50;
    transform: translateX(0);
    transition: transform 320ms var(--ease-out);
    will-change: transform;
  }
  /* v0.15.3 [П4] — при открытом кейсе sidebar НЕ уезжает целиком.
     Внутренние блоки (header, cards-scroll) скрываем, footer остаётся
     как absolute-полоса внизу экрана поверх main-area. */
  body.cards-collapsed .sidebar {
    transform: none;
    pointer-events: none;        /* клики проходят в case-view */
    background: transparent;
  }
  body.cards-collapsed .sidebar > *:not(.site-footer) {
    display: none;
  }
  body.cards-collapsed .site-footer {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 60;                 /* поверх main-area */
    pointer-events: auto;
    background: var(--color-bg);
  }

  /* Main-area показывается ТОЛЬКО в collapsed-состоянии */
  .main-area { display: none; }
  body.cards-collapsed .main-area { display: flex; }
  /* v0.15.3 [П4] — резервируем пространство под подвал-полосу
     (чтобы контент case-view не залезал под footer).
     v0.22 — 140→124 (синхронизация с .site-footer min-height после shrink). */
  body.cards-collapsed .main-area {
    padding-bottom: 124px;
    box-sizing: border-box;
  }

  /* v0.13.8.2 — cards-toggle в потоке вместе с contact/theme (одинаковый ряд в header-top__controls).
     В collapsed (sidebar уехал) — fixed top-right, чтобы можно было развернуть sidebar.
     Прозрачный фон — шапка сливается с основным фоном (как в dark). */
  .cards-toggle {
    width: 44px;
    height: 44px;
    padding: 0;
    justify-content: center;
    background: transparent;
    border-color: var(--color-border);
    color: var(--color-text);
  }
  .cards-toggle__label { display: none; }
  .cards-toggle__icon  { width: 20px; height: 20px; }

  /* В collapsed (main-area виден — sidebar уехал) toggle становится fixed top-right,
     чтобы вернуть sidebar. */
  body.cards-collapsed .cards-toggle {
    position: fixed;
    top: var(--space-3);
    right: var(--space-3);
    z-index: 200;
    bottom: auto;
    background: var(--color-surface-2);
    box-shadow: var(--shadow-sm);
  }

  /* v0.13.8.2 — 3 кнопки header-top унифицированы по 44×44 (как .cards-toggle).
     Gap 4px задаётся внизу через .header-top__controls.
     Фон transparent — шапка сливается с основным, виден только border. */
  .theme-toggle {
    width: 44px;
    height: 44px;
    background: transparent;
    border-color: var(--color-border);
    color: var(--color-text);
  }
  .theme-toggle__icon { width: 18px; height: 18px; }

  /* Phase 5 — на mobile занимает место бывшего contact-icon в шапке.
     Контактная функция остаётся доступной через #contact-pill в footer
     (там она была и раньше), а в плотном header sidebar — только
     язык / тема / cards-toggle. */
  .lang-toggle {
    width: 44px;
    height: 44px;
    background: transparent;
    border-color: var(--color-border);
    color: var(--color-text);
  }
  /* Phase 5 — #contact-btn (icon-square в .header-top__controls) скрыт на
     mobile. Раньше тут была 44×44 икона рядом с lang/theme/cards, но header
     стал тесным; Contact-CTA остаётся доступным через #contact-pill в footer.
     Старые размер-правила сохранены как inline-комментарий для истории. */
  .contact-btn { display: none; }
  /* legacy (kept commented for context):
       .contact-btn { width:44px; height:44px; min-height:44px; padding:0;
         justify-content:center; background:transparent;
         border-color:var(--color-border); color:var(--color-text); }
       .contact-btn__icon { width:18px; height:18px; flex-basis:18px; }
       .contact-btn__label { display:none; } */

  /* v0.8.2: .tag правило удалено (класс мёртв с v0.15.5). */
  .case-tab {
    min-height: 44px;
    height: auto;
    padding-block: var(--space-2);
  }
  .case-nav__btn {
    min-width: 44px;
    min-height: 44px;
  }

  /* v0.13.8.2 — header-top на мобильном: 3 кнопки в одной группе, gap 4px.
     Collapsed-состояние (sidebar уехал) — cards-toggle fixed, запасного padding не нужно (sidebar всё равно не виден).
     Divider снизу отделяет шапку от ленты карточек (bg у шапки = bg страницы). */
  .header-top {
    padding-right: 0;
    padding-bottom: var(--space-3);
    border-bottom: 1px solid var(--color-divider);
  }
  .header-top__controls { gap: 4px; }
}

/* ═══════════════════════════════════
   reduced motion — отключаем transform transitions
═══════════════════════════════════ */
@media (prefers-reduced-motion: reduce) {
  .work-card,
  .work-card__thumb img,
  .layout {
    transition: none !important;
    transform: none !important;
  }
}

/* ═══════════════════════════════════
   MOBILE CASE BAR — лого + кнопка «назад»
   Видно только на мобильном: <=767px.
═══════════════════════════════════ */
.case-mobile-bar { display: none; }

@media (max-width: 767px) {
  .case-mobile-bar {
    display: block;
    position: sticky;
    top: 0;
    z-index: 5;
    background: var(--color-case-bar-bg);
    /* v0.14.0 [9] — border убран: bar и body теперь в одном фоне, стыка нет. */
    /* v0.14.0 [10] — вертикальный padding выровнен с десктопным site-header (var(--space-4)). */
    padding: var(--space-4);
    margin: 0 calc(var(--space-4) * -1) var(--space-2);
  }
  /* v0.15.1 [1.1] — в light фон прозрачный (#dedede00), нужен разделитель снизу.
     В dark делитель не нужен: фон #212121 полностью сливается с body. */
  :root[data-theme="light"] .case-mobile-bar,
  body[data-theme="light"] .case-mobile-bar {
    border-bottom: 1px solid var(--color-divider);
  }
  /* v0.15.1 [1.2] / v0.7.4 [P2] — на mobile .cards-toggle полностью скрыт: дублирует клик по карточке,
     а в collapsed-стейте возврат к sidebar даёт .case-back в case-mobile-bar. Desktop не трогаем.
     v0.7.4 [P2]: убран !important — он был избыточен. Каскад работает через source-order
     (это правило идёт после .cards-toggle на 1114) + строка 1256 покрывает collapsed-кейс
     через более высокую специфичность (body.cards-collapsed .cards-toggle, 0,2,1). */
  .cards-toggle { display: none; }
  .case-mobile-bar__row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    width: 100%;
  }
  .case-mobile-bar__logo {
    text-decoration: none;
    display: inline-flex;
    align-items: center;
  }
  /* Лого в case-mobile-bar — совпадает с .logo__text в sidebar (text-xl, 0.14em) */
  .case-mobile-bar__logo .logo__text {
    font-family: var(--font-display);
    font-size: var(--text-xl);
    font-weight: 600;
    letter-spacing: 0.14em;
    color: var(--color-text);
    line-height: 1;
  }

  /* В состоянии case-view (body.cards-collapsed на мобильном)
     hamburger не нужен — его роль выполняет кнопка Back. */
  body.cards-collapsed .cards-toggle { display: none; }
  .case-back {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    min-height: 44px;
    padding: 0 var(--space-3);
    /* v0.13.8.3 — offset (dark #3a3a3a / light #d4d4d4), чтобы кнопка была
       видна на фоне case-mobile-bar (в light новый bar = #ebebeb). */
    background: var(--color-surface-offset);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-tag);
    color: var(--color-text);
    font-family: var(--font-body);
    font-size: var(--text-xs);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    cursor: pointer;
    box-shadow: var(--shadow-sm);
    transition: color 160ms var(--ease-out), border-color 160ms var(--ease-out);
  }
  .case-back:hover { color: var(--color-text); border-color: var(--color-text-muted); }
  .case-back:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; }
  .case-back svg { flex-shrink: 0; }

  /* v0.6 [Z8] — mobile: desktop-кнопка share в header скрыта,
     mobile-кнопка в case-mobile-bar показана рядом с .case-back.
     Стиль: height 44px для touch, паритет с .case-back.
     ВАЖНО: double-class selector .case-share.case-share--{mobile,desktop} —
     specificity 2 классов > portfolio.css `.case-share--{mobile,desktop}` (1 класс).
     В v0.5 правила .case-share--desktop в portfolio.css (вне @media) побеждали mobile
     overrides здесь по cascade order → на mobile была видна desktop-кнопка. */
  .case-share.case-share--desktop { display: none; }
  .case-share.case-share--mobile {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-1);
    height: auto;
    min-height: 44px;
    min-width: 44px;
    /* v0.3 [П1] — icon-only по умолчанию: симметричный padding var(--space-2)
       даёт квадратную touch-target ~44×44px, label визуально скрыт.
       При .case-share--copied label включается на 2 секунды для feedback,
       в этот момент padding расширяется до var(--space-3). */
    padding: 0 var(--space-2);
    background: var(--color-surface-offset);
    /* v0.7.13 [UX] — accent border --color-primary паритет с .case-share base / desktop. */
    border: 1px solid var(--color-primary);
    border-radius: var(--radius-tag);
    color: var(--color-text);
    font-family: var(--font-body);
    font-size: var(--text-xs);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    cursor: pointer;
    box-shadow: var(--shadow-sm);
    transition: color 160ms var(--ease-out), border-color 160ms var(--ease-out), padding 160ms var(--ease-out);
  }
  .case-share.case-share--mobile:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; }
  .case-share.case-share--mobile svg { flex-shrink: 0; }
  /* v0.3 [П1] — mobile: текст label визуально скрыт,
     остаётся в DOM для aria и JS-swap на COPIED ✓. */
  .case-share--mobile .case-share__label {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
  }
  /* v0.3 [П1] — feedback-состояние: показываем COPIED ✓ на 2 секунды. */
  .case-share--mobile.case-share--copied {
    padding: 0 var(--space-3);
  }
  .case-share--mobile.case-share--copied .case-share__label {
    position: static;
    width: auto;
    height: auto;
    margin: 0;
    overflow: visible;
    clip: auto;
    white-space: normal;
  }

  /* v0.3 [П1] — единый блок действий (back + share) справа в mobile-bar.
     gap=space-2 — паритет с .case-view__tabs. */
  .case-mobile-bar__actions {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    margin-left: auto;
  }
}

/* ═══════════════════════════════════
   WORK CARD BADGE — индикатор Game Asset на карточке
═══════════════════════════════════ */
.work-card__badge {
  position: absolute;
  top: var(--space-2);
  left: var(--space-2);
  z-index: 3;
  display: inline-flex;
  align-items: center;
  height: 18px;
  padding: 0 6px;
  background: var(--color-card-badge-bg);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  border: 1px solid var(--color-primary);
  border-radius: 2px;
  color: var(--color-primary);
  font-family: var(--font-body);
  font-size: 0.6rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  line-height: 1;
  pointer-events: none;
}

/* Когда фильтр Game Assets активен — усиливаем badge.
   v0.8.2: убран мёртвый селектор .tag--badge.tag--active ~ * — остался
   только body.filter-game (выставляется JS в main.js на click game-switch). */
body.filter-game .work-card__badge {
  background: var(--color-primary);
  color: var(--color-bg);
}

/* ═══════════════════════════════════
   reduced-motion — отключаем lift-анимации
═══════════════════════════════════ */
@media (prefers-reduced-motion: reduce) {
  .case-view,
  .case-scroll,
  .case-progress__bar,
  .case-item { transition: none !important; }
  .case-scroll { scroll-behavior: auto; }
}

/* ══════════════════════════════════════════════════════════════
   CUSTOM CURSOR v0.13.0 — кольцо + dot, только pointer: fine.
     • без mix-blend-mode — читаемо на любом grain/фоне.
     • только токены (--color-primary, --color-text) — light/dark нативно.
     • pointer-events: none — не блокирует клики под собой.
     • позиционирование движется transform'ом из JS (quickSetter),
       начально — за экраном, чтобы не мелькал в 0,0 до первого mousemove.
══════════════════════════════════════════════════════════════ */
/* v0.15.2 [B4] — z-index поднят до 10001, чтобы курсор оставался
   видным поверх .media-fs (z-index: 9999). */
.cursor {
  position: fixed;
  top: 0;
  left: 0;
  width: 40px;
  height: 40px;
  margin: -20px 0 0 -20px;      /* центрирование относительно translate(x,y) */
  border: 1px solid var(--color-primary);
  border-radius: 50%;
  pointer-events: none;
  z-index: 10001;
  transform: translate3d(-100px, -100px, 0);  /* старт за viewport */
  opacity: 0;
  transition: opacity 180ms var(--ease-out);
  will-change: transform;
}
.cursor.is-active { opacity: 1; }
.cursor.is-hover {
  /* при наведении на магнитный элемент расширяем кольцо
     (scale из JS не трогаем — он на внутреннем .cursor-dot __wrap'е, см. JS) */
  border-color: var(--color-text);
}

.cursor-dot {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 6px;
  height: 6px;
  margin: -3px 0 0 -3px;
  background: var(--color-text);
  border-radius: 50%;
  transition: transform 200ms var(--ease-out);
}
.cursor.is-hover .cursor-dot {
  transform: scale(1.6);
}

/* На touch-устройствах (вкл. гибридные: нет точного поинтера) — полностью выкл.
   any-pointer: fine гарантирует: без мыши/трекпада — курсор не скрывается и сам не рисуется. */
@media (hover: none), (any-pointer: coarse) {
  .cursor,
  .cursor-dot { display: none; }
}

/* При реальной мыши/трекпаде прячем native cursor всюду.
   v0.13.1 — класс `cursor-fine` ставится из JS только после прохода
   всех проверок (pointer:fine + hover + !reduced-motion). Специфичность
   `html.cursor-fine *` = (0,1,1) — выше одиночного класса вроде `.tag`,
   поэтому перекрывает все встроенные `cursor: pointer/grab/not-allowed`
   без нужды в `!important`. */
html.cursor-fine,
html.cursor-fine * { cursor: none; }
/* v0.15.2 [B4] — в fullscreen overlay возвращаем системный курсор (наряду с кастомным). */
html.cursor-fine .media-fs,
html.cursor-fine .media-fs * { cursor: auto; }
html.cursor-fine .media-fs__close,
html.cursor-fine .media-fs__close * { cursor: pointer; }

/* При prefers-reduced-motion: курсор скрываем полностью (JS не запустится,
   .cursor-fine не ставится, native cursors работают как обычно). */
@media (prefers-reduced-motion: reduce) {
  .cursor,
  .cursor-dot { display: none; }
}

/* ═══════════════════════════════════════════════════════════════════════
   CURSOR EXTENDED STATES (v0.19.0)
   ─────────────────────────────────────────────────────────────────────
   Базовый ring 40×40 + dot 6×6 (v0.13.0/v0.14.0) НЕ трогается. Новые
   состояния добавляют child-overlay (.cursor__shell) и модифицируют
   существующий .cursor-dot. Делегация — data-cursor="link|work|drag"
   на интерактивных элементах, обрабатывается mouseover в main.js.
═══════════════════════════════════════════════════════════════════════ */

/* Shell — общий контейнер для расширенных состояний (work, drag) */
.cursor__shell {
  position: absolute;
  top: 50%;
  left: 50%;
  width: var(--cursor-shell-size, 56px);
  height: var(--cursor-shell-size, 56px);
  border: 1px solid var(--color-text);
  border-radius: 50%;
  opacity: 0;
  transform: translate(-50%, -50%) scale(0.6);
  pointer-events: none;
  display: flex;
  align-items: center;
  justify-content: center;
  transition:
    opacity 0.25s var(--ease-out),
    transform 0.25s var(--ease-out),
    border-color 0.2s ease;
  will-change: transform, opacity;
}

.cursor__shell-label {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 0.6rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-text);
  opacity: 0;
  transition: opacity 0.2s var(--ease-out) 0.08s;
  white-space: nowrap;
}

.cursor__crosshair {
  position: absolute;
  inset: 0;
  opacity: 0;
  transition: opacity 0.25s var(--ease-out);
  pointer-events: none;
}
.cursor__crosshair i {
  position: absolute;
  background: var(--color-text);
}
.cursor__crosshair i:nth-child(1) { top: 0;    left: 50%; width: 1px; height: 6px; transform: translateX(-50%); }
.cursor__crosshair i:nth-child(2) { bottom: 0; left: 50%; width: 1px; height: 6px; transform: translateX(-50%); }
.cursor__crosshair i:nth-child(3) { left: 0;   top: 50%;  width: 6px; height: 1px; transform: translateY(-50%); }
.cursor__crosshair i:nth-child(4) { right: 0;  top: 50%;  width: 6px; height: 1px; transform: translateY(-50%); }

/* Smooth background transition on the dot — opt-in via new state classes */
.cursor-dot {
  transition: transform 200ms var(--ease-out), background-color 0.25s var(--ease-out), opacity 0.2s var(--ease-out);
}

/* LINK state — повторяем паттерн magnetic .is-hover: ring инвертируется
   на --color-text (white в dark / dark в light, авто через токен), dot
   scale 1.6. Раньше был border:transparent + translucent primary fill —
   на reverse'ах (.case-nav__btn, .case-share, .case-3d__fs-btn) почти
   не читался. Теперь link и magnetic-hover дают одинаковый visible visual. */
.cursor.is-link {
  border-color: var(--color-text);
}
.cursor.is-link .cursor-dot {
  transform: scale(1.6);
}

/* WORK state — 64×64 shell with text label, base ring/dot hidden */
.cursor.is-work {
  border-color: transparent;
}
.cursor.is-work .cursor-dot { opacity: 0; }
.cursor.is-work .cursor__shell {
  opacity: 1;
  transform: translate(-50%, -50%) scale(1);
}
.cursor.is-work .cursor__shell-label {
  opacity: 1;
}
.cursor.is-work .cursor__shell-label::after { content: 'VIEW →'; }

/* DRAG state — 32×32 borderless shell with crosshair ticks */
.cursor.is-drag {
  --cursor-shell-size: 32px;
  border-color: transparent;
}
.cursor.is-drag .cursor-dot { opacity: 0; }
.cursor.is-drag .cursor__shell {
  opacity: 1;
  transform: translate(-50%, -50%) scale(1);
  border-color: transparent;
}
.cursor.is-drag .cursor__crosshair { opacity: 1; }

/* FS-GALLERY zone states (v0.20.0) — borderless 64×64 shell с текстом-направлением.
   Активируются из media-fs zone tracker (main.js setupCursorZones). */
.cursor.is-fs-prev,
.cursor.is-fs-next {
  border-color: transparent;
}
.cursor.is-fs-prev .cursor-dot,
.cursor.is-fs-next .cursor-dot { opacity: 0; }
.cursor.is-fs-prev .cursor__shell,
.cursor.is-fs-next .cursor__shell {
  opacity: 1;
  transform: translate(-50%, -50%) scale(1);
  border-color: transparent;
}
.cursor.is-fs-prev .cursor__shell-label,
.cursor.is-fs-next .cursor__shell-label { opacity: 1; }
.cursor.is-fs-prev .cursor__shell-label::after { content: '← PREV'; }
.cursor.is-fs-next .cursor__shell-label::after { content: 'NEXT →'; }

/* Form inputs — restore native text cursor over editable fields.
   Custom cursor остаётся видимым поверх (fixed overlay) — это компромисс,
   реальных text-inputs на сайте сейчас нет, правило про запас. */
html.cursor-fine input[type="text"],
html.cursor-fine input[type="email"],
html.cursor-fine input[type="search"],
html.cursor-fine input[type="number"],
html.cursor-fine input[type="tel"],
html.cursor-fine input[type="url"],
html.cursor-fine input[type="password"],
html.cursor-fine textarea,
html.cursor-fine [contenteditable="true"] {
  cursor: text;
}

/* ═══════════════════════════════════════════════════════════════════════
   v0.15.1 [3] — SITE FOOTER (sidebar bottom)
   v0.15.2 [A1] — mobile reveal-on-scroll убран. Подвал виден всегда.
   v0.15.2 [A2] — 3 placeholder-кнопки заменены на .site-footer__stats.
   v0.15.2 [A3] — .site-footer__row--pill вмещает Contact + Free Assets,
                    обе flex:1 (50/50), gap 16px, padding 16px по краям.
═══════════════════════════════════════════════════════════════════════ */
.site-footer {
  flex-shrink: 0;
  /* v0.22 — −16px по вертикали: min-height 140→124, padding-bottom 16→0.
     Стороны и верх не трогаем (см. ТЗ). */
  min-height: 124px;
  box-sizing: border-box;
  padding: var(--space-4) var(--space-4) 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: var(--space-3);
  border-top: 1px solid var(--color-divider);
}
.site-footer__row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}
.site-footer__row--actions {
  justify-content: flex-start;
}
/* v0.15.2 [A3] — нижний ряд растягивается на всю ширину, pill-ы flex:1 (50/50).
   v0.15.3 [П1] — Contact прижат к левому краю, Free Assets к правому
   (padding-left/right = 0), gap 8px. */
.site-footer__row--pill {
  justify-content: stretch;
  gap: 8px;
  padding-left: 0;
  padding-right: 0;
}
.site-footer__row--pill > .top-pill {
  flex: 1 1 0;
  min-width: 0;
  justify-content: center;
}
.site-footer__divider {
  height: 1px;
  background: var(--color-divider);
  width: 100%;
}
/* v0.15.2 [A2] — текстовая строка статистики вместо 3 кнопок.
   Стиль микро-caption: uppercase, text-xs, letter-spacing как в .top-pill. */
.site-footer__stats {
  margin: 0;
  font-family: var(--font-body);
  font-size: var(--text-xs);
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-text-muted);
  line-height: 1.2;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Free Assets pill в футере — не fixed, не pointer-events:none от .top-pills.
   Сбрасываем потенциальные inherit-эффекты, сохраняем внешний вид .top-pill. */
.site-footer__free {
  pointer-events: auto;
}
/* v0.15.2 [A3] — Contact pill в футере: сбрасываем наследуемые эффекты. */
.site-footer__contact {
  pointer-events: auto;
  color: var(--color-text);
}

@media (max-width: 767px) {
  .cards-scroll {
    scroll-padding-bottom: calc(124px + var(--space-6));
    padding-bottom: calc(124px + var(--space-6));
  }
}

/* Collapsed (desktop rail) — скрываем весь футер, как и всё содержимое sidebar
   кроме cards-toggle.
   v0.15.3 [П4] — на mobile скрытие НЕ применяется: sidebar уезжает целиком
   через translateX, а когда возвращается (case-view открыт) — подвал виден. */
@media (min-width: 768px) {
  body.cards-collapsed .site-footer { display: none; }
}

/* ═══════════════════════════════════════════════════════════════════════
   PRELOADER (v0.17.0) — real progress on top-12 work-card SVG decode()
   ─────────────────────────────────────────────────────────────────────
   Anti-flicker: html.is-loading hides interactive layout/cursor until
   the IIFE in main.js removes the overlay and unsets the class.
   z-index 10000 sits above .media-fs (9999) but BELOW .cursor (10001),
   which we hide via visibility while loading anyway.
═══════════════════════════════════════════════════════════════════════ */
html.is-loading body > .layout,
html.is-loading body > .cursor {
  visibility: hidden;
}

.preloader {
  position: fixed;
  inset: 0;
  z-index: 10000;
  background: var(--color-bg);
  color: var(--color-text);
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  /* Explicit inset() start so GSAP can interpolate to inset(0 0 100% 0) on exit. */
  clip-path: inset(0 0 0% 0);
  will-change: clip-path, opacity;
}

.preloader__readout {
  position: absolute;
  top: var(--space-4);
  right: var(--space-4);
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-text-muted);
  white-space: nowrap;
}

.preloader__readout-sep {
  color: var(--color-text-faint);
  opacity: 0.6;
}

.preloader__center {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-6);
}

.preloader__counter {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--text-hero);
  line-height: 1;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
  color: var(--color-text);
}

.preloader__bar {
  position: relative;
  width: min(80vw, 480px);
  height: 1px;
  background: var(--color-divider);
  overflow: hidden;
}

.preloader__bar-fill {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 0;
  background: var(--color-primary);
}

.preloader__label {
  position: absolute;
  bottom: var(--space-12);
  left: 50%;
  transform: translateX(-50%);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--color-text-faint);
  white-space: nowrap;
}

.preloader__dots {
  display: inline-block;
  width: 1.4em;
  text-align: left;
  margin-left: 0.2em;
}

.preloader__dots span {
  display: inline-block;
  opacity: 0;
  animation: preloader-dot 1.4s ease-in-out infinite;
}

.preloader__dots span:nth-child(2) { animation-delay: 0.2s; }
.preloader__dots span:nth-child(3) { animation-delay: 0.4s; }

@keyframes preloader-dot {
  0%, 80%, 100% { opacity: 0; }
  40%           { opacity: 1; }
}

.preloader__skip {
  position: absolute;
  bottom: var(--space-24);
  left: 50%;
  background: transparent;
  border: 1px solid var(--color-border);
  color: var(--color-text);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  padding: var(--space-2) var(--space-6);
  cursor: pointer;
  opacity: 0;
  transform: translate(-50%, 8px);
  transition:
    opacity 0.3s var(--ease-out),
    transform 0.3s var(--ease-out),
    border-color 0.2s ease,
    color 0.2s ease;
}

.preloader__skip[hidden] { display: none; }

.preloader__skip.is-visible {
  opacity: 1;
  transform: translate(-50%, 0);
}

.preloader__skip:hover,
.preloader__skip:focus-visible {
  border-color: var(--color-primary);
  color: var(--color-primary);
  outline: none;
}

@media (max-width: 768px) {
  .preloader__counter { font-size: var(--text-2xl); }
  .preloader__bar    { width: min(70vw, 320px); }
}

@media (max-width: 375px) {
  .preloader__readout { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .preloader__dots span { animation: none; opacity: 1; }
}

/* v0.8.9 L5 — screen-reader-only utility (standard a11y pattern).
   Используется для hint'ов через aria-describedby. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}
