/* Kirill Konstantinov — MVP
 *
 * One page. Sticky info block, scrolling index of 13 entries, plate then
 * caption. No build step, no dependencies.
 *
 * Plates are centred on the page at varying widths, with a numbered caption
 * directly beneath: 01 · Title · Object · Year. One type size throughout.
 */

:root {
  --fg: #111;
  --bg: #fff;
  --size: 12px;
  --lead: 1.28;
  --track: 0.022em;         /* tracking — nudge to taste */
  --pad: 14px;
  --pad-top: 12px;         /* header sits slightly tighter to the top edge */
  --measure: 44em;          /* caption column */
  --bio-w: 62%;             /* info block runs wide across the page */
  /* Ground behind plates. Off by default; switched on per entry in the
   * markup, so only the entries whose plates are white get a mount. */
  --ground: transparent;
  --mount: 0;
  --plate-max-h: 82vh;      /* no plate taller than the screen */
  --gap: 7vh;               /* between entries — viewport-relative, so the
                               rhythm holds across screen sizes */
}

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

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

/* Hide the page scrollbar. The header already carries a scroll percentage, so
 * position in the document is still readable without it. Scrolling itself is
 * untouched — this hides the indicator, not the behaviour. */
html, body { scrollbar-width: none; }
html::-webkit-scrollbar,
body::-webkit-scrollbar { display: none; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font-family: var(--zerko);
  font-size: var(--size);
  font-weight: 400;
  line-height: var(--lead);
  letter-spacing: var(--track);
  font-kerning: normal;
  font-feature-settings: "kern" 1;
  text-rendering: optimizeLegibility;
}

p { margin: 0; }
b { font-weight: 500; }
i { font-style: normal; }

a { color: inherit; text-decoration: underline; text-underline-offset: 0.18em; }


/* ── Header ──────────────────────────────────────────────────────────────
 * Stays put while the index scrolls under it. No background — the type sits
 * directly over whatever passes beneath. White fill plus difference blending
 * so it inverts over a plate rather than disappearing into it.
 */

.head {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 10;
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  padding: var(--pad-top) var(--pad) var(--pad);
  mix-blend-mode: difference;
  color: #fff;
  pointer-events: none;
}

.head a { pointer-events: auto; }

.id { font-weight: 500; }
.bio { font-weight: 500; }
.dim { opacity: 0.55; }
.rule { opacity: 0.55; }

/* The info block runs wide rather than sitting in a narrow column — it is the
 * first thing read and carries the positioning, so it gets the width. */
.head-l { width: var(--bio-w); }
.bio { max-width: none; }


/* ── Index ───────────────────────────────────────────────────────────────
 * Everything centred on the page axis. Plate, then its numbered caption.
 *
 * --w on each entry sets its plate width; entries without one are caption
 * only. No fixed ratio: width/height on the <img> gives the browser the
 * aspect ratio before the file lands, so nothing shifts while scrolling.
 */

.index {
  padding: 0 var(--pad) 40vh;
  margin-top: 13em;         /* clears the fixed header */
  text-align: center;
}

.entry { margin-bottom: var(--gap); }

.plate {
  background: var(--ground, transparent);
  margin: 0 auto 0.5em;
  width: var(--w, 46%);
}

.plate img {
  display: block;
  width: 100%;
  height: auto;
}


/* ── Carousel ────────────────────────────────────────────────────────────
 * A native overflow container with scroll-snap. Trackpad swipe, touch swipe
 * and momentum all come from the browser — better than a library does it, and
 * free. Clicking the plate advances one, wrapping to the first after the last.
 *
 * All plates in a set share a ratio, so the height never jumps.
 */

.carousel {
  width: var(--w, 46%);
  margin: 0 auto 0.5em;
}

.track {
  background: var(--ground, transparent);
  display: flex;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  overscroll-behavior-x: contain;   /* stop a swipe triggering back-navigation */
  scrollbar-width: none;
  -ms-overflow-style: none;
  cursor: e-resize;                 /* reads as "forward", not "open" */

  /* The track is clickable and focusable, so a click would otherwise select
   * the figure inside it — which Safari paints as a blue wash over the whole
   * plate. Suppress selection and the tap highlight; keep a focus ring for
   * keyboard users only. */
  user-select: none;
  -webkit-user-select: none;
  -webkit-tap-highlight-color: transparent;
  outline: none;
}

.track:focus-visible { outline: 1px solid currentColor; outline-offset: 2px; }

.slide img { -webkit-user-drag: none; }

.track::-webkit-scrollbar { display: none; }

.slide {
  flex: 0 0 100%;
  margin: 0;
  /* Inset so the ground reads as a mount around the plate rather than as
   * letterbox bars above and below it. */
  padding: var(--mount, 0);
  scroll-snap-align: center;
  scroll-snap-stop: always;

  /* Slides stretch to the tallest in the set so the carousel height is fixed —
   * nothing below it moves while you swipe. The cost is dead space when ratios
   * within a set differ wildly, which is a reason to group plates of similar
   * shape rather than to animate the height. */
  display: flex;
  align-items: center;
  justify-content: center;
}

.slide video {
  display: block;
  width: 100%;
  height: auto;
  max-height: var(--plate-max-h);
  object-fit: contain;
  pointer-events: none;             /* clicks land on the track, not the video */
  background: #f2f2f2;              /* holds the frame before the poster paints */
}

.slide img {
  display: block;
  /* Sized to the picture, not to the slide. With width:100% + object-fit the
   * element box stayed full-width and the plate letterboxed inside it, so a
   * ground or a rule drew around the box rather than around the plate. Letting
   * the box shrink to the image means any edge treatment hugs the plate. */
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: var(--plate-max-h);
  background: var(--plate-bg, transparent);
  outline: var(--plate-rule, none);
  outline-offset: 0;
  pointer-events: none;             /* clicks land on the track, not the image */
}

/* Counter is hidden visually but stays in the accessibility tree, so a screen
 * reader still announces which plate of how many. Delete this rule to bring
 * it back on the page. */
.count {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* ── Footer ──────────────────────────────────────────────────────────────
 * Sits at the end of the scroll, aligned to the same margin as everything
 * else. The index already has a deep bottom pad, so this needs no extra air.
 */

.foot {
  padding: 0 var(--pad) var(--pad);
  display: flex;
  flex-wrap: wrap;
  gap: 0.4em 2em;
}

.foot p { margin: 0; }


/* ── Mobile ──────────────────────────────────────────────────────────────
 * Plates fill the column rather than bleeding to the edge, so they keep the
 * page margin and line up with the caption and the header. The bio is dropped
 * — it would take most of a small viewport — leaving the identifier and the
 * percentage.
 */

@media (max-width: 700px) {
  :root { --pad: 10px; --gap: 11vh; }

  .head-l { width: auto; }

  .head .rule { display: none; }

  .index { margin-top: 4em; }

  .foot {
    flex-direction: column;
    gap: 0.2em;
  }

  /* Full width of the padded column — --pad each side. */
  .plate,
  .carousel {
    width: 100%;
    margin-left: 0;
    margin-right: 0;
    margin-bottom: 0.4em;
  }
}


/* ── Info overlay ────────────────────────────────────────────────────────
 * A plain white sheet at full viewport, sitting UNDER the header. The header
 * is z-index 10 with mix-blend-mode: difference — with the sheet beneath it,
 * the blend resolves against white and the identifier reads black, exactly as
 * it does over the page. Nothing about the header has to change.
 */

.info {
  position: fixed;
  inset: 0;
  z-index: 5;                    /* under .head (10), over the index */
  width: 100vw;
  height: 100vh;
  height: 100dvh;                /* mobile browser chrome */
  background: #fff;
  overflow-y: auto;
  overscroll-behavior: contain;
  display: flex;
  flex-direction: column;

  opacity: 0;
  transition: opacity .3s ease;
}

.info.is-open { opacity: 1; }
.info[hidden] { display: none; }

/* The header is pointer-events: none so it never blocks the scroll beneath it.
 * The identifier is the one thing that has to receive clicks. */
.id-toggle {
  pointer-events: auto;
  font: inherit;
  color: inherit;
  letter-spacing: inherit;
  background: none;
  border: 0;
  padding: 0;
  margin: 0;
  text-align: left;
  cursor: pointer;
  user-select: text;             /* still selectable, despite being a button */
  -webkit-user-select: text;
}

.id-toggle:focus-visible { outline: 1px solid currentColor; outline-offset: 2px; }

/* Same affordance as the Selected Works rows: a tint to 0.55 on hover, so the
 * one clickable thing in the header reads as clickable. The tint is on .id
 * alone — the clock beside it is already .dim, and tinting the whole button
 * would compound the two to 0.30. */
.id { transition: opacity 0.15s ease; }
.id-toggle:hover .id { opacity: 0.55; }

/* Stop the page behind scrolling while the sheet is open. */
body.info-open { overflow: hidden; }

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


/* ── Intro (the bio layer) ───────────────────────────────────────────────
 * Same fixed position, same difference blend, same measure as when it lived
 * inside .head — so desktop is unchanged. It is a separate layer only so that
 * on mobile the index can pass over it while the identifier stays on top.
 *
 * top = header padding + one identifier line (--lead), so it lands exactly
 * where the flow put it before.
 */
.intro {
  position: fixed;
  top: calc(var(--pad-top) + 1.28em);
  left: var(--pad);
  width: var(--bio-w);
  z-index: 10;
  mix-blend-mode: difference;
  color: #fff;
  pointer-events: none;
}

.intro .contact { pointer-events: auto; }
.intro p { margin: 0; }

/* The index paints its own ground and rides above the intro, so scrolling
 * covers the bio rather than moving it. Desktop keeps the intro on top
 * (z-index 10 vs 2), so nothing changes there. */
.index { background: var(--bg); }

@media (max-width: 700px) {
  .intro {
    display: block;            /* the old rule hid .bio at this width */
    z-index: 1;                /* now BELOW the index, so content covers it */
    width: auto;
    right: var(--pad);

    /* Safari promotes a position:fixed element carrying mix-blend-mode onto its
     * own compositing layer, where it paints above scrolled content regardless
     * of z-index — the index then slides under the bio instead of over it.
     * The blend earns nothing here: on mobile the bio only ever sits on the
     * page ground, because it is covered rather than scrolled across. */
    mix-blend-mode: normal;
    color: var(--fg);
  }

  /* Start the index below the bio so it is readable on load, then let the
   * scroll bring the index up over it. */
  .index { margin-top: 15em; }
}


/* ── Scroll container ────────────────────────────────────────────────────
 * The body does not scroll; the index scrolls inside its own container.
 *
 * Why: on iOS Safari, `position: fixed` elements travel with the elastic
 * rubber-band at the ends of a scroll — the header and bio visibly slide, and
 * the index's white ground rides over them. A fixed element only stays put if
 * there is no body scroll to bounce. Moving the scroll into a container also
 * makes `overscroll-behavior` effective, which stops the bounce entirely.
 */
html, body { height: 100%; overflow: hidden; }

.scroller {
  /* The z-index lives HERE, not on .index. Safari treats a touch-scrolling
   * overflow container as a stacking context, which traps any z-index set on a
   * child — .index at 2 was being compared inside .scroller rather than against
   * .intro, and .scroller itself defaulted to auto, so the bio painted on top.
   * -webkit-overflow-scrolling is dropped: it has been a no-op since iOS 13 and
   * it was what forced the extra layer. */
  position: relative;
  z-index: 2;
  height: 100vh;
  height: 100dvh;
  overflow-y: auto;
  overscroll-behavior-y: contain;   /* no bounce past either end */
  scrollbar-width: none;
}
.scroller::-webkit-scrollbar { display: none; }

/* The overlay covers the scroller, so locking the body is no longer how the
 * page behind is held still — the sheet simply sits above it. */
body.info-open .scroller { overflow: hidden; }

/* The sheet is z-index 5. On mobile .intro drops to 1 so the index can cover
 * it, which also put it under the sheet — lift it back over while open. */
body.info-open .intro { z-index: 6; }


/* ── Info overlay content ────────────────────────────────────────────────
 * The bio and email already sit above the sheet, so the overlay does not
 * repeat them — it continues underneath, in the same column, at the same
 * measure. Opening it reads as the info block extending rather than as a
 * panel arriving. --intro-h is set by script, since the bio's height depends
 * on how the copy wraps at the current width.
 */
.info-inner {
  padding: calc(var(--pad-top) + 1.28em + var(--intro-h, 6em) + 2em) var(--pad) 6em;
  /* Not --bio-w. That measure exists to stop the bio running as one long line
   * of prose; these are four short columns and the constraint only squeezed
   * them. The bio and intro keep --bio-w. */
  width: 100%;
  /* Grid, not multi-column: `columns` flows the content, so a short block in
   * one column pushed the next one down and the headings stopped lining up. */
  display: grid;
  /* Five units, not four: Selected Works carries the full caption names as
   * they read on the page and takes two of them. The other three sections are
   * short lists and hold at one unit each. */
  grid-template-columns: repeat(5, 1fr);
  align-items: start;
  gap: 2em;
}

.info-works { grid-column: span 2; }
/* Keeps a title and its year on one line rather than orphaning the year. */
.info-works .yr { white-space: nowrap; }

/* Each work opens its own description in place. The marker is suppressed and
 * the affordance is a tint to the same 0.55 the .dim class uses, so hovering a
 * title matches the register of the years and the secondary copy. */
.info-works summary {
  list-style: none;
  cursor: pointer;
  transition: opacity 0.15s ease;
}
.info-works summary::-webkit-details-marker { display: none; }
.info-works summary:hover { opacity: 0.55; }
.info-works summary:focus-visible { outline: 1px solid currentColor; outline-offset: 2px; }

/* Indents only the open description, so the closed list stays a flush column. */
.info-works details p {
  margin: 0.35em 0 0.9em 1.1em;
  max-width: 30em;
  opacity: 0.55;
}
/* An open row needs no marker of its own — the description below it is the
 * signal, and a permanent tint would read as disabled. */

.info-inner section { margin: 0; }
/* Bold marks the category, regular marks the thing — the jotundes device noted
 * in WEB.md section 4. Bold is a drawn weight, declared in zerko.css, not
 * synthesised from Medium. */
.info-inner h2 { font-size: inherit; font-weight: 700; margin: 0; }
.info-inner p  { font-weight: 400; }
.info-inner p  { margin: 0; }
/* Anchors the foot of the sheet rather than sitting in the column flow, where
 * it read as a fifth block level with Typefaces. */
.info-colophon { margin: auto 0 0; }   /* .foot already carries the padding */

@media (max-width: 700px) {
  .info-inner {
    width: auto;
    /* Two columns, filled downward then across, so Practice sits beside
     * Typefaces and Clients beside Contact, each pair sharing a baseline. */
    grid-template-columns: repeat(2, 1fr);
    grid-template-rows: repeat(2, min-content);
    grid-auto-flow: column;
    gap: 2em 1.5em;
  }
  /* The two-unit span is a desktop measure. Here it would break the 2x2
   * column flow, so Selected Works returns to a single cell. */
  .info-works { grid-column: auto; }
}
