/* витрина артов — чёрный минимализм. настраивается в :root */
:root {
  --cell-ratio: 1.3;       /* ширина/высота ячейки сетки. 1.3 ≈ под превьюшки 460px; 1 = квадрат */
  --bg: #000;
  --fg: #dedede;
  --dim: #6e6e6e;
  --line: #1c1c1c;
  --line-hi: #6e6e6e;
  --gap: 10px;
  --maxw: 1280px;
  --font: ui-monospace, "Cascadia Mono", Consolas, "Liberation Mono", "Courier New", monospace;
  --size: 13px;
}

* { box-sizing: border-box; }
/* скроллбар есть всегда: иначе при первой догрузке ширина сетки меняется на 15px и всё дёргается.
   overflow-anchor: отключаем «якорение прокрутки» — фичу браузера, которая сама дёргает экран,
   когда содержимое выше вьюпорта меняет высоту (именно это и ощущается как «прыгает при скролле») */
html { scrollbar-gutter: stable; overflow-anchor: none; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font: var(--size)/1.55 var(--font);
  image-rendering: pixelated;      /* никакого сглаживания на пиксель-арте и сканах */
}

.head, main, .foot { width: 100%; max-width: var(--maxw); margin-inline: auto; padding-inline: clamp(12px, 3vw, 24px); }

/* шапка приклеена к верху: контакты всегда на виду, прокручивать вниз не надо */
.head {
  position: sticky; top: 0; z-index: 5;
  padding-top: 12px; padding-bottom: 10px;
  background: var(--bg); border-bottom: 1px solid var(--line);
}
.head-row { display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px 18px; }   /* name + links */
.name { color: var(--fg); text-decoration: none; font-size: 15px; letter-spacing: .02em; }
.name:hover { color: #fff; }
/* фон шапки непрозрачный — под картинками не должно просвечивать */

/* ---------- сетка ----------
   masonry (CONFIG.masonry, по умолчанию): карточки раскладывает gallery.js и ставит их через
   position:absolute с УЖЕ ИЗВЕСТНОЙ высотой — она считается из w/h работы из manifest.json,
   а не из того, как доедет картинка. поэтому:
     · ничего не обрезается (в отличие от object-fit: cover) и нет чёрных полей (object-fit: contain);
     · вставка новых узлов и декодирование картинок не двигают НИ ОДНУ уже стоящую карточку;
     · высота контейнера известна сразу (это не grid и не columns, которые пересобираются).
   всё сразу (CONFIG.chunk = 0) — главный источник покоя: DOM во время скролла не меняется. */
.grid {
  --cols: 2; --card-h: 260px;
  position: relative; display: block;
  margin-top: var(--gap);            /* отступ от липкой шапки: первый ряд не должен к ней липнуть */
  /* высота контейнера ставится из gallery.js (layout → totalHeight): карточки вне потока,
     без явной высоты сетка схлопывается в 0px и прокрутки нет. contain здесь не нужен и
     только мешает: малейшее расхождение в containment = «ничего не скроллится» */
}
@media (min-width: 700px)  { .grid { --cols: 3; } }
@media (min-width: 1040px) { .grid { --cols: 4; } }
@media (min-width: 1440px) { .grid { --cols: 5; } }

.card {
  position: absolute; top: 0; left: 0; margin: 0; padding: 0;
  background: #0a0a0a; border: 1px solid var(--line);
  cursor: zoom-in; overflow: hidden;
  -webkit-appearance: none; appearance: none; font: inherit; color: inherit;
}
.card img { display: block; width: 100%; height: 100%; object-fit: fill; }
/* сломанную превьюшку НЕ прячем (display:none сдвинул бы соседей): узел остаётся,
   на месте дырки — заглушка */
.card.dead { border-style: dashed; border-color: var(--line); cursor: default; }
.card:hover { border-color: var(--line-hi); }
.card:focus-visible { border-color: var(--fg); outline: none; }

/* фолбэк-режим (CONFIG.masonry = false): ровная сетка одинаковых ячеек; в ней превьюшка
   либо вписана с полями (gridFit: "fit"), либо заполняет ячейку с обрезкой ("cover") */
.grid.uniform { display: grid; grid-template-columns: repeat(var(--cols), 1fr); gap: var(--gap); }
.grid.uniform .card { position: static; width: 100%; height: var(--card-h); }
.grid.uniform .card img { object-fit: contain; }
.grid.uniform.cover .card img { object-fit: cover; }

.hint { color: var(--dim); padding: 8px 0 4px; }

/* кнопка дозагрузки сетки (CONFIG.chunk) */
.more { padding: 6px 0 18px; text-align: center; }   /* ВНЕ .grid: иначе вставка карточек таскала бы блок за собой */
.more[hidden] { display: none; }
.more-btn {
  font: inherit; color: var(--dim); background: none; cursor: pointer;
  border: 1px solid var(--line); padding: 8px 18px;
}
.more-btn:hover { color: var(--fg); border-color: var(--line-hi); }
.hint code { color: var(--fg); }

/* ---------- низ ---------- */
.about { padding: 28px 0 6px; }
.about p { margin: 0 0 12px; color: var(--dim); max-width: 60ch; }
.links { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 4px 14px; font-size: 12px; }
.links a { color: var(--dim); text-decoration: none; border-bottom: 1px solid var(--line); }
.links a:hover { color: var(--fg); border-bottom-color: var(--fg); }

.foot { padding: 22px 0 34px; color: var(--dim); font-size: 12px; }

/* ---------- лайтбокс ---------- */
.lb { position: fixed; inset: 0; background: rgba(0, 0, 0, .97); z-index: 10; }
.lb[hidden] { display: none; }
body.lb-open { overflow: hidden; }
.lb-back { position: absolute; inset: 0; }

.lb-stage { position: relative; margin: 0; height: 100%; display: flex; flex-direction: column; padding: 40px clamp(12px, 3vw, 32px) 14px; }

/* .lb-holder — flex-контейнер: у картинки появляется вычислима высота,
   поэтому max-height:100% реально кадрирует по экрану (в grid этого не было —
   огромные листы вылезали за низ и их приходилось скроллить) */
.lb-holder { flex: 1; min-height: 0; display: flex; overflow: auto; }
.lb-holder img {
  max-width: 100%; max-height: 100%; width: auto; height: auto;
  min-width: 0; min-height: 0; margin: auto; flex: 0 1 auto;
  display: block; background: #0a0a0a; cursor: zoom-in;
}
.lb-holder.actual img { max-width: none; max-height: none; cursor: zoom-out; }

/* размытое превью под полным размером: пока лист качается, экран не чёрный */
#lbBlur {
  position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
  max-width: 100%; max-height: 100%; width: auto; height: auto;
  margin: 0; min-width: 0; min-height: 0; z-index: 0; pointer-events: none;
  filter: blur(18px) saturate(1.08) brightness(.92); opacity: .9;
  image-rendering: auto;                    /* блюру пиксельные края не нужны */
}
#lbImg { position: relative; z-index: 1; }
.lb-holder img#lbImg { opacity: 0; transition: opacity .25s ease; }
.lb-holder.ready img#lbImg { opacity: 1; }
.lb-holder.ready #lbBlur { opacity: 0; transition: opacity .4s ease; }
.lb-holder.actual #lbBlur { display: none; }

/* счётчик и статус — в углах, НЕ поверх середины картинки */
.lb-count { color: var(--dim); font-size: 12px; white-space: nowrap; }
.lb-status {
  position: absolute; top: 10px; left: 50%; transform: translateX(-50%); margin: 0;
  color: var(--dim); font-size: 12px; pointer-events: none;
  background: var(--bg); border: 1px solid var(--line); border-radius: 2px; padding: 2px 10px;
  max-width: 80vw; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.lb-status:empty { display: none; }
.lb-status.err { color: var(--fg); }

.lb-meta {
  display: flex; gap: 20px; align-items: baseline; padding-top: 12px;
  color: var(--dim); font-size: 12px;
}
#lbCap { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#lbCap b { color: var(--fg); font-weight: 400; }
.lb-meta a { color: var(--dim); text-decoration: none; border-bottom: 1px solid var(--line); white-space: nowrap; }
.lb-meta a:hover { color: var(--fg); border-bottom-color: var(--fg); }

.lb-x, .lb-nav {
  position: absolute; z-index: 2; background: none; border: 0; padding: 6px 10px;
  color: var(--dim); font: 15px/1 var(--font); cursor: pointer;
}
.lb-x { top: 8px; right: 10px; }
.lb-nav { top: 50%; transform: translateY(-50%); font-size: 20px; }
.lb-prev { left: 2px; } .lb-next { right: 2px; }
.lb-x:hover, .lb-nav:hover { color: var(--fg); }

/* ---------- слайдшоу в лайтбоксе ----------
   кнопка и регулятор живут в подписи (не поверх картинки), на весь экран не разъезжаются */
.lb-sl { display: flex; align-items: center; gap: 8px 12px; flex-wrap: wrap; margin-left: auto; }
#slBtn {
  font: inherit; color: var(--dim); background: none; cursor: pointer;
  border: 1px solid var(--line); padding: 2px 8px; border-radius: 2px; white-space: nowrap;
}
#slBtn:hover { color: var(--fg); border-color: var(--line-hi); }
#slBtn[aria-pressed="true"] { color: var(--bg); background: var(--fg); border-color: var(--fg); }
#slBtn[aria-pressed="true"]::before { content: "❚❚ "; }
#slBtn[aria-pressed="false"]::before { content: "▶ "; }
.lb-sl-rate { display: flex; align-items: center; gap: 8px; color: var(--dim); font-size: 12px; }
.lb-sl-rate input[type="range"] { width: 110px; accent-color: var(--fg); cursor: ew-resize; }
#slDelayVal { color: var(--fg); min-width: 44px; text-align: right; font-variant-numeric: tabular-nums; }

      <a id="lbDirect" href="#" target="_blank" rel="noopener">original</a>
      <span class="lb-sl" id="lbSl">
        <button id="slBtn" type="button" aria-pressed="false">slideshow</button>
        <label class="lb-sl-rate"><span id="slDelayVal">1.2 s</span>
          <input id="slDelay" type="range" min="0.3" max="10" step="0.1" value="1.2" aria-label="seconds per artwork">
        </label>
      </span>
    </figcaption>
