/*
 * ira — design system v2.3
 * One sheet, five columns, one type size, black/gray on white. ASCII is the visible graphic voice:
 * the flying butterfly, prompt lines, pixel icons.
 * Docs: DESIGN.md (project root). Specimen: design-system/index.html.
 * No build step. Logical properties, custom properties, subgrid.
 */

/* ------------------------------------------------------------------ fonts */
/* IBM Plex Mono (OFL), self-hosted subsets. Ranges = the fontsource subsets the files came from. */
@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../fonts/plex-mono-cyrillic-400.woff2") format("woff2");
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("../fonts/plex-mono-cyrillic-500.woff2") format("woff2");
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../fonts/plex-mono-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: "IBM Plex Mono";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("../fonts/plex-mono-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;
}
@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../fonts/plex-mono-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: "IBM Plex Mono";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("../fonts/plex-mono-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;
}
/* Box-drawing, block and arrow glyphs: IBM's official Pi subset (OFL, see fonts/OFL.txt), same 0.6em
   advance, so frames and ASCII pieces sit on the Plex cell grid on every OS. block: a frame never flashes
   in a fallback face. Preloaded by the theme. Two rules, one per weight: a "400 500" range lands in a
   different descriptor bucket from the single-weight faces and Chrome never consults it (Menlo drew it). */
@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 400;
  font-display: block;
  src: url("../fonts/plex-mono-pi-400.woff2") format("woff2");
  unicode-range: U+2190-21FF, U+2500-259F, U+25A0-25FF;
}
@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 500;
  font-display: block;
  src: url("../fonts/plex-mono-pi-400.woff2") format("woff2");
  unicode-range: U+2190-21FF, U+2500-259F, U+25A0-25FF;
}
/* Metric-matched locals, only if the Pi file fails: Menlo/DejaVu are 0.602em wide (= Plex 0.6em);
   Consolas is 0.55em, scaled to 0.6. local() takes a full or PostScript name, never a family name. */
@font-face {
  font-family: "Plex Fallback";
  src: local("Menlo Regular"), local("Menlo-Regular"), local("DejaVu Sans Mono"), local("DejaVuSansMono");
}
@font-face {
  font-family: "Plex Fallback Win";
  src: local("Consolas");
  size-adjust: 109.1%;
}

/* ----------------------------------------------------------------- tokens */
:root {
  color-scheme: light;

  /* color: white, gray, black. Text uses --ink / --ink-2 only. */
  --paper: #fff;
  --ink: #000;          /* 21.00 : 1 */
  --ink-2: #767676;     /* 4.54 : 1, AA floor for text */
  --rule: rgba(0, 0, 0, .2); /* = #ccc, 1.61 : 1, hairlines, never text */
  /* ascii ink (v2): art is meant to be SEEN. v1's #8a8a8a (3.45:1) is retired. Never a 4th color. */
  --ascii-ink: #000;    /* 21.00 : 1. butterfly, illustrations, frames, icons */
  --ascii-quiet: var(--ink-2); /* 4.54 : 1 (4.68 on the gray ground). texture that must not win: orbit loader, nav slide */

  /* type: one size */
  --font: "IBM Plex Mono", "Plex Fallback", "Plex Fallback Win", ui-monospace, Menlo, Consolas, monospace;
  --fs: .8125rem;       /* 13px */
  --lh: 1.125rem;       /* 18px, the unit of all vertical rhythm */
  --fr-size: var(--fs); /* frame glyphs; calibration knob: 15px if strokes bead at 1x */
  --a-lh: 1.2;          /* ascii line-height: 0.6 x 1.2 = 1:2 cell */

  /* space: whole and half lines */
  --s-1: .25rem;   /*   4 */
  --s-2: .5625rem; /*   9 half line */
  --s-3: 1.125rem; /*  18 one line */
  --s-4: 2.25rem;  /*  36 */
  --s-5: 4.5rem;   /*  72 */
  --s-6: 9rem;     /* 144 */
  --row: 1.5rem;   /*  24 index row pitch (WCAG 2.5.8) */

  /* grid */
  --cols: 5;
  --m: 1.125rem;   /* page margin */
  --gap: 1.125rem; /* column gap */
  --top0: var(--wp-admin--admin-bar--height, 0px);
  --bar-h: calc(var(--m) + var(--lh));
  --media-max: calc(100svh - 4.5rem);
  --cover-max: 70svh;

  /* layers */
  --z-frame: 1;
  --z-side: 2;
  --z-fly: 3;   /* the butterfly: over every text (bar and aside included) ... */
  --z-work: 4;  /* ... and under the works */
  --z-skip: 10;

  /* motion: everything is a hard cut (hovers, image arrival) */
  --dur: 0ms;
  --delay-loader: 300ms; /* read by ui.js via data attributes; kept here as the source of truth */
}
@media (max-width: 767.98px) {
  :root { --cols: 6; --m: 1rem; --gap: .75rem; --s-6: 3.375rem; }
}
@media (pointer: coarse) {
  :root { --row: 2.5rem; }
}
/* Per-project ground (post meta ira_ground = gray) */
[data-ground="gray"] {
  --paper: #f0f0f0;
  --ink-2: #6b6b6b; /* 4.68 : 1 on #f0f0f0 */
  --ascii-quiet: var(--ink-2); /* re-declared: a var() resolves where it is declared, so :root's would stay #767676 */
}

/* ------------------------------------------------------------ reset/base */
*, *::before, *::after { box-sizing: border-box; }
/* type is set on body, not html: on the root element rem resolves against its own font-size */
html {
  letter-spacing: 0;
  font-synthesis: none;
  font-variant-ligatures: none;
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  color: var(--ink);
  background: var(--paper);
  caret-color: var(--ink);
  scrollbar-width: thin;
  scrollbar-color: var(--ink-2) transparent;
  scroll-padding-block-start: var(--top0);
  -webkit-tap-highlight-color: transparent;
  tab-size: 4;
}
body { margin: 0; min-block-size: 100svh; font: 400 var(--fs)/var(--lh) var(--font); background: var(--paper); color: var(--ink); }
h1, h2, h3, h4, h5, h6, p, figure, blockquote, ol, ul, dl, dd, pre, table { margin: 0; padding: 0; }
h1, h2, h3, h4, h5, h6 { font-size: inherit; line-height: inherit; }
ol, ul { list-style: none; }
img, video, svg, canvas { display: block; max-inline-size: 100%; block-size: auto; }
.icon { display: inline-block; vertical-align: -2px; flex: none; } /* pixel icons (design-system/icons.js): whole-px offset keeps them crisp */
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; background: none; border: 0; border-radius: 0; padding: 0; cursor: pointer; text-align: inherit; }
strong, b, th, h1, h2, h3, h4, h5, h6 { font-weight: 500; }
em, i, cite { font-style: normal; } /* no italic ships; never faked */
table { border-collapse: collapse; }
pre, code { font: inherit; }
[hidden] { display: none !important; }
::selection { background: var(--ink); color: var(--paper); }
:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }
:focus:not(:focus-visible) { outline: none; }
main[tabindex="-1"]:focus { outline: none; } /* focused by in-place navigation for screen readers, not a control: Safari drew a frame round the page */
a:active, button:active { opacity: .6; }
a, button { transition: color var(--dur), background-color var(--dur); }

/* utilities */
.vh {
  position: absolute !important;
  inline-size: 1px; block-size: 1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}
.ink { color: var(--ink); }
.ink-2 { color: var(--ink-2); }
.skip {
  position: absolute; inset-block-start: var(--top0); inset-inline-start: 0;
  z-index: var(--z-skip);
  padding: var(--s-1) var(--m);
  background: var(--ink); color: var(--paper);
  transform: translateY(-200%);
}
.skip:focus-visible { transform: none; outline-offset: -2px; outline-color: var(--paper); }

/* ------------------------------------------------------------- primitives */
/* .grid: the five/six-column sheet. Any child may span columns; nested grids use subgrid. */
.grid {
  display: grid;
  grid-template-columns: repeat(var(--cols), minmax(0, 1fr));
  column-gap: var(--gap);
}
.sub { display: grid; grid-template-columns: subgrid; grid-column: 1 / -1; }

/* .site: page shell. DOM order: .skip, .bar, .stage (main), .side (aside), .foot. Works come first. */
.site {
  display: grid;
  grid-template-columns: repeat(var(--cols), minmax(0, 1fr));
  column-gap: var(--gap);
  align-content: start;
  padding-inline: var(--m);
  min-block-size: 100svh;
  position: relative; /* .role */
}
.site > * { grid-column: 1 / -1; }

/* "графічна дизайнерка": the name's mirror on the bar line, the same margin from the right edge, in the
   name's own style (ink + 500 on the front page, gray elsewhere). Gone on phones (client, 2026-10-14). */
.role { display: none; }
@media (min-width: 768px) {
  .role { display: block; position: absolute; inset-block-start: var(--m); inset-inline-end: 0; margin: 0; color: var(--ink-2); } /* end of the grid area = the page margin */
  .site:has(.bar__home[aria-current]) .role { color: var(--ink); font-weight: 500; }
}

/* ------------------------------------------------------------------- bar */
.bar {
  display: grid;
  grid-template-columns: subgrid;
  padding-block: var(--m);
  color: var(--ink-2);
  background: var(--paper);
}
.bar a { color: var(--ink-2); }
.bar a:hover, .bar a:focus-visible, .bar [aria-current="page"] { color: var(--ink); }
.bar [aria-current="page"] { font-weight: 500; }
.bar__home { grid-column: 1 / 4; }
.bar__info { grid-column: 5 / 7; justify-self: end; }
@media (min-width: 768px) {
  .bar__home { grid-column: 1 / 3; }
  .bar__info { grid-column: 5; justify-self: start; }
}

/* ----------------------------------------------------------------- stage */
.stage {
  display: grid;
  grid-template-columns: subgrid;
  align-content: start;
  padding-block-end: var(--s-6);
}
.stage > * { grid-column: 1 / -1; }

/* project meta: the stage's first line(s), shares line 1 with the bar on desktop */
.meta {
  display: grid;
  grid-template-columns: subgrid;
  align-content: start;
  min-block-size: calc(3 * var(--lh));
  margin-block-end: var(--s-5);
}
.meta > * { min-inline-size: 0; }
.meta__n { grid-column: 1 / -1; } /* the slow-navigation loader slot; empty at rest */
.meta__n:empty { display: none; }
.meta__a { grid-column: 1 / -1; }
.meta__b { grid-column: 1 / -1; }
.meta__b dl { margin: 0; }
.meta__b div { display: flex; column-gap: 1ch; }
.meta__b dt { color: var(--ink-2); font-weight: 400; }
.meta__b dd { margin: 0; }
.meta__status { grid-column: 1 / -1; color: var(--ink-2); } /* home: ira_status line */
.meta__title { font-weight: 500; }
.meta .ink-2, .meta__sub { color: var(--ink-2); }
@media (min-width: 768px) {
  .meta__n { grid-column: 5; white-space: nowrap; }
  .meta__a { grid-column: 1 / 3; }
  .meta__b { grid-column: 1 / 5; }
}

/* ------------------------------------------------------------------ side */
.side {
  display: grid;
  grid-template-columns: subgrid;
  grid-template-rows: auto 1fr auto;
  margin-block-start: var(--s-5);
}
.side__top { display: grid; grid-template-columns: subgrid; grid-column: 1 / -1; row-gap: var(--s-4); }
.side__low { display: grid; grid-template-columns: subgrid; grid-column: 1 / -1; grid-row: 3; margin-block-start: var(--s-4); }
.viewer, .viewer__say { grid-column: 1 / -1; }
.bio { grid-column: 1 / -1; }
@media (min-width: 768px) {
  .side :is(.viewer, .viewer__say) { grid-column: 1 / 4; }
  .bio { grid-column: 4 / 6; }
}

/* typography: short words are bound to the next word in PHP (ira_typo); the browser avoids widows */
:is(p, li, dd, blockquote) { text-wrap: pretty; }
:is(h1, h2, h3) { text-wrap: balance; }

/* bio + contacts; the photo is a small square on the line grid (8 lines), no frame, no radius */
.bio__text { max-inline-size: 60ch; }
.bio__pic { display: block; margin-block-end: var(--lh); }
.bio__photo { display: block; inline-size: calc(8 * var(--lh)); block-size: calc(8 * var(--lh)); object-fit: cover; }
.bio__pic--wide .bio__photo { inline-size: auto; max-inline-size: 100%; object-fit: contain; } /* photo + note: same height, its own width */
.contacts { margin-block-start: var(--lh); color: var(--ink-2); }
.contacts a { color: var(--ink-2); overflow-wrap: anywhere; }
.contacts a:hover, .contacts a:focus-visible { color: var(--ink); }

/* colophon line: © (the motion toggle is parked: client, 2026-10-14) */
.colophon { display: grid; grid-template-columns: subgrid; grid-column: 1 / -1; align-items: baseline; margin-block-start: var(--s-4); color: var(--ink-2); }
.colophon > :first-child { grid-column: 1 / 4; }
.colophon > * + * { grid-column: 4 / -1; justify-self: start; }
@media (min-width: 768px) {
  .colophon > :first-child { grid-column: 1 / 3; }
  .colophon > * + * { grid-column: 3 / -1; }
}

/* ------------------------------------------------------------------ foot */
/* footer: "↑ up" only (credits removed, cat parked — client, 2026-10-14); every page */
.foot {
  display: grid;
  grid-template-columns: subgrid;
  row-gap: var(--s-4);
  align-items: end;
  padding-block: var(--s-6) var(--m);
}
.foot__c { grid-column: 1 / -1; color: var(--ink-2); }
.foot__c a { color: var(--ink-2); }
.foot__c a:hover, .foot__c a:focus-visible { color: var(--ink); }
.foot__c > * + * { margin-block-start: var(--lh); }
/* .grass: ASCII daisies along the very bottom of every page, edge to edge, outside .site (grass.js grows it to
   the width; client 2026-10-15, variant 5). Grass gray, flowers ink. 6em = its 5 rows, reserved before JS. */
.grass { display: block; margin: var(--s-4) 0 0; inline-size: 100%; min-block-size: 6em; font: 400 var(--fs)/1.2 var(--font); white-space: pre; overflow: hidden; color: var(--ink-2); }
.grass b { color: var(--ink); font-weight: 400; }
/* .foot__cat: PARKED with the cat (footer.php) */
.foot__cat { grid-column: 1 / -1; container-type: inline-size; min-inline-size: 0; }
@media (min-width: 1024px) {
  .foot__c { grid-column: 1; }
  .foot__cat { grid-column: 2 / -1; grid-row: 1; }
  .foot__cat .cat { margin-inline-start: auto; }
}

/* ------------------------------------------- phone + tablet: home typewriter first */
/* Stacked home only (.site:has(.covers)), client 2026-10-14: bar → greeting (two lines) → [photo + bio, tablet
   and phone] → роботи/ + index → covers (4 on a phone) → colophon. display: contents lifts the aside's
   parts into the .site grid; order does the rest (DOM order is works-first for screen readers). */
@media (max-width: 1023.98px) {
  .site:has(.covers) > .bar { order: -2; }
  .site:has(.covers) > .side, .site:has(.covers) :is(.side__top, .side__low) { display: contents; }
  .site:has(.covers) .side__low > * { grid-column: 1 / -1; }
  .site:has(.covers) .viewer { order: -1; margin-block: var(--s-3) var(--s-4); }
  .site:has(.covers) .meta { min-block-size: 0; margin-block-end: var(--s-3); }
  .site:has(.covers) .viewer__say { order: -1; margin-block: var(--s-3) var(--s-4); }
  .site:has(.covers) .bio { order: -1; margin-block-end: var(--s-4); }
  .site:has(.covers) .side__low > :not(.colophon) { order: -1; }
  .site:has(.covers) .index { margin-block-end: var(--s-5); }
  .site:has(.covers) .colophon, .site:has(.covers) > .foot { order: 1; }
}
@media (max-width: 767.98px) {
  .site:has(.covers) .cover:nth-child(n + 5) { display: none; } /* four previews on a phone */
}

/* --------------------------------------------------------- split >= 1024 */
@media (min-width: 1024px) {
  .site { grid-template-rows: auto auto; }
  .bar {
    grid-column: 1 / 3; grid-row: 1 / -1; align-self: start;
    position: sticky; inset-block-start: var(--top0); z-index: var(--z-side);
    padding-block-end: 0;
  }
  .bar__home { grid-column: 1; }
  .bar__info { grid-column: 2; }

  .stage { grid-column: 3 / -1; grid-row: 1; }
  .meta { padding-block-start: var(--m); min-block-size: calc(var(--m) + 4 * var(--lh)); margin-block-end: calc(4 * var(--lh)); } /* 4 header lines (клієнт, послуги, рік, Yankov Club); first work still at y = 162 */
  .meta__n { grid-column: 3; grid-row: 1; }
  .meta__a { grid-column: 1; grid-row: 1; }
  .meta__b { grid-column: 1 / 3; grid-row: 1; } /* where the title was: the title is a visually hidden h1 */
  .meta__status { grid-column: 1 / 3; grid-row: 1; }

  .side {
    grid-column: 1 / 3; grid-row: 1 / -1; align-self: start;
    position: sticky; inset-block-start: calc(var(--top0) + var(--bar-h)); z-index: var(--z-side);
    margin-block-start: var(--bar-h);
    /* the scroll box clips; widen it into the page margin so a frame's left stroke (9px + .3em out)
       survives. Subgrid: -m margin + m padding nets 0 on the edge tracks, nothing moves */
    margin-inline: calc(-1 * var(--m)); padding-inline: var(--m);
    block-size: calc(100svh - var(--top0) - var(--bar-h));
    padding-block: calc(7 * var(--lh)) var(--m); /* top row at y = 162, level with the first work */
    overflow-y: auto;
    overscroll-behavior: contain;
    scrollbar-width: thin;
  }
  .side :is(.viewer, .viewer__say) { grid-column: 1 / 3; }
  /* bio text in col 2, contacts beside it in col 1 (the sky took the height they had) */
  .bio { grid-column: 1 / 3; display: grid; grid-template-columns: subgrid; }
  .bio__text { grid-column: 2; }
  .contacts { grid-column: 1; grid-row: 1; margin-block-start: 0; }
  .bio__pic { grid-column: 1; grid-row: 1; margin-block-end: 0; } /* beside the text, contacts under it */
  .bio__pic ~ .contacts { grid-row: 2; margin-block-start: var(--lh); }
  /* the wide desktop version keeps the 8-line height, spans both columns; text, if any, goes under it */
  .bio__pic--wide { grid-column: 1 / -1; }
  .bio__pic--wide ~ .bio__text { grid-row: 2; margin-block-start: var(--lh); }
  .colophon > :first-child { grid-column: 1; }
  .colophon > * + * { grid-column: 2; }

  .foot { grid-column: 3 / -1; grid-row: 2; }
}

/* ----------------------------------------------------------------- index */
.index, .index li, .index__row { display: grid; grid-template-columns: subgrid; grid-column: 1 / -1; }
.index__row {
  padding-block: calc((var(--row) - var(--lh)) / 2);
  box-shadow: inset 0 -1px var(--rule);
  color: var(--ink-2);
  white-space: nowrap;
}
.index li:first-child .index__row { box-shadow: inset 0 1px var(--rule), inset 0 -1px var(--rule); }
.index__t { grid-column: 1 / 5; display: flex; min-inline-size: 0; }
.index__title { min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; }
.index__type { display: none; min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; }
.index__year { grid-column: 5 / -1; grid-row: 1; justify-self: end; }
@media (min-width: 768px) {
  .index__t { grid-column: 1 / 3; }
  .index__type { display: block; grid-column: 3 / 5; grid-row: 1; }
  .index__year { grid-column: 5; justify-self: start; }
}
@media (min-width: 1024px) {
  .index__t { grid-column: 1; }
  .index__type { grid-column: 2; padding-inline-end: 6ch; }
  .index__year { grid-column: 2; justify-self: end; }
}
/* states: hover/focus, near (scroll-spy), hot (frame cursor), current */
.index__row:hover, .index__row:focus-visible, .index__row.is-hot { color: var(--ink); }
.index__row:focus-visible { outline-offset: -2px; }
.index__row[aria-current="page"], .index__row[data-current] { color: var(--ink); font-weight: 500; }
/* optimistic current: the clicked row inks at once; the old current lets go */
.index:has([data-current]) .index__row[aria-current="page"]:not([data-current]) { color: var(--ink-2); font-weight: 400; }
.index__empty { grid-column: 1 / -1; color: var(--ink-2); }

/* ---------------------------------------------------------- ascii pieces */
.ascii {
  display: block;
  margin: 0;
  font: 400 var(--fs)/var(--a-lh) var(--font);
  letter-spacing: 0;
  font-kerning: none;
  font-variant-ligatures: none;
  white-space: pre;
  overflow: hidden;
  user-select: none;
  -webkit-user-select: none;
  pointer-events: none;
  color: var(--ascii-ink);
}
/* the viewer (star sky) is hidden under 8px (column < 307px). PARKED with the sky: see ira_typewriter() */
@container (max-width: 307px) { .viewer > * { display: none; } }
/* calendar: 32 x 13 cells; its blank outer ring carries the frame */
.cal { position: relative; display: inline-block; vertical-align: top; justify-self: start; } /* justify-self: a grid item (info section) would stretch it */
.cal .ascii { inline-size: 19.2em; block-size: 15.6em; } /* reserved: 32 x .6em, 13 x 1.2em */
.cal .fr { font-size: var(--fs); }
/* cat: 56 x 15 cells at the text cell (13px = 437 x 234), black. PARKED (client, 2026-10-14): footer.php no
   longer prints it; these rules stay so bringing it back is markup only. */
.cat { font-size: min(var(--fs), 100cqi / 33.6); inline-size: 33.6em; block-size: 18em; }
/* spinners */
.spin { display: inline-block; vertical-align: top; color: var(--ascii-quiet); }
.spin--text { font: inherit; white-space: nowrap; }
.spin--center { position: absolute; inset: 50% auto auto 50%; translate: -50% -50%; }
[data-nav-loader][data-loading] > :not(.spin, .vh) { display: none; } /* nav slot only; media keeps its box */

/* ----------------------------------------------------------------- frame */
/* Glyph frame "┌ title ───┐". Corners + title are glyphs; the edges are long glyph runs clipped by
   CSS, so a frame closes on any pixel size with no JS layout. Markup: DESIGN.md / ui.js frameEl(). */
.fr {
  position: absolute;
  inset: 0;
  z-index: var(--z-frame);
  pointer-events: none;
  user-select: none;
  -webkit-user-select: none;
  color: var(--fr-ink, var(--ascii-ink));
  font: 400 var(--fr-size)/var(--a-lh) var(--font);
  letter-spacing: 0;
  font-variant-ligatures: none;
  white-space: pre;
}
.fr__t, .fr__b { position: absolute; inset-inline: 0; display: flex; block-size: 1.2em; overflow: hidden; }
.fr__t { inset-block-start: 0; }
.fr__b { inset-block-end: 0; }
.fr__title { flex: 0 1 auto; min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; color: var(--fr-title-ink, var(--ascii-ink)); }
.fr__h { flex: 1 0 1ch; overflow: hidden; }
.fr__h::before { content: "────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────"; }
/* verticals: a 1px rule on the glyph stroke axis (cell centre .3em; row centre .6em), so the
   corners' stems join it and no font can bead it at 1x */
.fr__v { position: absolute; inset-block: .6em; inset-inline-start: round(.3em - .5px, 1px); inline-size: 1px; background: currentColor; }
.fr__v--r { inset-inline: auto round(.3em - .5px, 1px); }
/* outside variant: strokes sit in the gutter, the marked box never moves */
/* strokes a quarter gutter out, so two framed neighbours keep half a gutter between them */
.fr--out { inset: calc(-1 * (var(--gap) / 4 + .6em)) calc(-1 * (var(--gap) / 4 + .3em)); }
/* inside variant host: content starts on cell 2, level with the title */
.framed { position: relative; padding: calc(1.2 * var(--fr-size) + var(--s-2)) calc(2 * .6 * var(--fr-size)); }
.is-style-frame { position: relative; }
.is-hot > .fr { --fr-ink: var(--ink); --fr-title-ink: var(--ink); }

/* ---------------------------------------------------------------- figure */
/* --ar = width / height, printed by the theme (ui.js fills it from the width/height attributes if missing).
   Width = min(column, max-height x ratio), so a work never exceeds one viewport and the box is exact
   before the file arrives. Without --ar the work fills its column. */
.fig { position: relative; justify-self: start; inline-size: min(100%, var(--media-max) * var(--ar, 999)); }
.fig + .fig, .fig + .prose, .prose + .fig { margin-block-start: var(--s-6); }
/* box exists before the file: width/height attributes, else --ar */
.fig :is(img, video), .cover :is(img, video) { inline-size: 100%; block-size: auto; aspect-ratio: auto var(--ar); }
.fig--portrait { grid-column: 1 / -1; }
@media (min-width: 768px) { .fig--portrait { grid-column: 1 / 4; } }
@media (min-width: 1024px) { .fig--portrait { grid-column: 1 / 3; } }
/* states (ui.js sets data-state only for media still missing after 300ms) */
.fig > .fr, .cover > .fr { visibility: hidden; }
:is(.fig, .cover)[data-state="loading"] > .fr,
:is(.fig, .cover)[data-state="error"] > .fr { visibility: visible; }
:is(.fig, .cover)[data-state="loading"] :is(img, video) { opacity: 0; transition: none; min-block-size: var(--s-6); } /* no ratio known: still a box */
:is(.fig, .cover)[data-state="error"] :is(img, video) { visibility: hidden; min-block-size: var(--s-6); } /* keeps its box (ratio set on the media by ui.js); caption stays below */
.fig__alt { position: absolute; inset: var(--s-2) 0 auto 0; color: var(--ink-2); }
.fig__play { margin-block-start: var(--s-2); }

/* ---------------------------------------------------------- home covers */
.covers { display: grid; grid-template-columns: subgrid; row-gap: var(--s-4); align-items: start; }
.cover { position: relative; display: block; grid-column: 1 / -1; justify-self: start; inline-size: min(100%, var(--cover-max) * var(--ar, 999)); }
.cover__cap { display: block; margin-block-start: var(--s-2); color: var(--ink-2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } /* the client's name */
.cover:hover .cover__cap, .cover:focus-visible .cover__cap, .cover.is-hot .cover__cap { color: var(--ink); }
/* No frame round a cover on hover, focus or scroll (client, 2026-10-15): the loader's frame stays for loading
   and errors only; keyboard focus gets the plain outline. */
.cover:nth-child(n + 19) { display: none; } /* the mosaic shows 18; the shuffle (front-page.php) removes the rest, this covers no JS */
/* touch (= not ui.js `fine`): the cover crossing the middle of the viewport inks its row and caption.
   With a mouse nothing is "near": an inked row nobody points at reads as a bug */
@media not all and (hover: hover) and (pointer: fine) {
  .index__row.is-near, .cover.is-near .cover__cap { color: var(--ink); }
}
/* The mosaic (helioteles home, client 2026-10-14): tiles from different cases in a 6-tile rhythm of sizes and
   sides, rows left open where a tile does not fill them. Auto-placement keeps the reading order. */
.cover:nth-child(6n + 1) { grid-column: 1 / 5; }
.cover:nth-child(6n + 2) { grid-column: 4 / -1; }
.cover:nth-child(6n + 3) { grid-column: 1 / -1; }
.cover:nth-child(6n + 4) { grid-column: 3 / -1; }
.cover:nth-child(6n + 5) { grid-column: 1 / 4; }
.cover:nth-child(6n + 6) { grid-column: 2 / -1; justify-self: end; }
@media (min-width: 768px) {
  .covers { row-gap: var(--s-5); }
  .cover:nth-child(6n + 1) { grid-column: 1 / 4; }
  .cover:nth-child(6n + 2) { grid-column: 4 / 6; }
  .cover:nth-child(6n + 3) { grid-column: 1 / 5; }
  .cover:nth-child(6n + 4) { grid-column: 3 / 6; justify-self: end; }
  .cover:nth-child(6n + 5) { grid-column: 1 / 3; }
  .cover:nth-child(6n + 6) { grid-column: 3 / 6; }
}
@media (min-width: 1024px) {
  .cover:nth-child(6n + 1) { grid-column: 1 / 3; inline-size: min(75%, var(--cover-max) * var(--ar, 999)); }
  .cover:nth-child(6n + 2) { grid-column: 3; }
  .cover:nth-child(6n + 3) { grid-column: 1 / 3; }
  .cover:nth-child(6n + 4) { grid-column: 2 / 4; inline-size: min(66%, var(--cover-max) * var(--ar, 999)); justify-self: end; }
  .cover:nth-child(6n + 5) { grid-column: 1; }
  .cover:nth-child(6n + 6) { grid-column: 2 / 4; }
}

/* ------------------------------------------- about: description, credits */
.about { display: grid; grid-template-columns: subgrid; margin-block-start: var(--s-6); row-gap: var(--s-4); }
.side .about { grid-column: 1 / -1; margin-block-start: 0; }
.side :is(.about__text, .credits) { grid-column: 1 / -1; }
.about__text { grid-column: 1 / -1; max-inline-size: 70ch; }
.about__text > * + * { margin-block-start: var(--lh); }
.about__result { display: grid; grid-template-columns: auto minmax(0, 1fr); column-gap: 1ch; }
.about__result > :first-child, .about__author { color: var(--ink-2); }
.about__text > .about__quote { margin-block-start: var(--s-4); }
.credits { grid-column: 1 / -1; }
.credits div + div { margin-block-start: var(--lh); }
.credits > .prompt { margin-block-end: var(--lh); }
.credits dt { color: var(--ink-2); font-weight: 400; }
@media (min-width: 1024px) {
  .about__text { grid-column: 1 / 3; }
  .credits { grid-column: 3; }
}

/* ----------------------------------------------------------------- prose */
.prose { max-inline-size: 65ch; overflow-wrap: anywhere; }
.prose > * + * { margin-block-start: var(--lh); }
.prose a, a.link {
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: .2em;
  text-decoration-color: var(--ink-2);
}
.prose a:hover, .prose a:focus-visible, a.link:hover, a.link:focus-visible { text-decoration-color: var(--ink); }
.prose ul > li::before { content: "- "; color: var(--ink-2); }
.prose h2, .prose h3, .prose h4 { color: var(--ink-2); font-weight: 400; }

/* ---------------------------------------------------------- info page */
.info__sec { display: grid; grid-template-columns: subgrid; row-gap: var(--lh); }
.info__sec + .info__sec { margin-block-start: var(--s-5); }
.info__sec > h2 { grid-column: 1 / -1; color: var(--ink-2); font-weight: 400; }
.info__sec > :not(h2) { grid-column: 1 / -1; }
@media (min-width: 768px) {
  .info__sec > h2 { grid-column: 1; }
  .info__sec > :not(h2) { grid-column: 2 / -1; }
}
/* now: the calendar in its titled frame */
.now { display: grid; justify-items: start; padding-block: var(--s-3); }
.ledger td, .ledger th { padding: 0 2ch 0 0; text-align: start; vertical-align: top; font-weight: 400; }
.ledger td:first-child { color: var(--ink-2); white-space: nowrap; }

/* -------------------------------------------------------------- buttons */
/* text button "[ label ]": brackets are part of the text */
.btn {
  display: inline-block;
  padding-block: 3px;
  color: var(--ink);
  background: transparent;
  white-space: nowrap;
}
.btn:hover, .btn:focus-visible, .btn[aria-pressed="true"]:hover { background: var(--ink); color: var(--paper); }

/* ------------------------------------------------------------------ 404 */
/* one random case photo, centred, linking to its case; the caption under it; "на головну" below (client, 2026-10-14) */
.e404 { display: flex; flex-direction: column; align-items: center; text-align: center; row-gap: var(--s-3); }
/* the link takes the image's width (capped), the image is centred in it: a portrait capped by height stays centred */
.e404__work { display: block; inline-size: fit-content; max-inline-size: min(100%, 36rem); }
.e404__work img { display: block; inline-size: auto; block-size: auto; max-inline-size: 100%; max-block-size: 60svh; margin-inline: auto; }
.e404__cap { margin: 0; max-inline-size: 40ch; }
.e404__home { margin-block-start: var(--s-3); }

/* ============================================================ v2: ASCII as a visible language */

/* ------------------------------------------------------------ viewer */
/* PARKED (client, 2026-10-14): the aside's star sky (ascii.rest saptarishi, 64 x 18 cells,
   499 x 281 at 1440), black, unframed. Restore: ira_typewriter() in inc/template-tags.php. */
.viewer { position: relative; container-type: inline-size; min-inline-size: 0; }
.viewer > pre { font-size: min(var(--fs), 100cqi / 38.4); inline-size: 38.4em; block-size: 21.6em; } /* 18 rows */
/* the greeting, home only, on the y = 162 line with the first work:
   "Привіт! Мене звати Іра, я графічна дизайнерка." / "Я створюю айдентику▌" (no "~", no arrow: client, 2026-10-14) */
.viewer__say { min-inline-size: 0; overflow: hidden; }
/* line 2, "Я створюю <typed>", sits under the greeting (≤ 24 cells, never wraps), so nothing below moves
   while it types. */
.prompt.viewer__say { white-space: normal; }
.viewer__line { display: block; white-space: nowrap; } /* flush with line 1: the greeting has no arrow */
/* exactly two lines (client): on a phone the 46-cell greeting shrinks just enough to fit the column */
.prompt.viewer__say { font-size: min(var(--fs), (100vw - 2 * var(--m)) / 27.6); } /* 46 cells */

/* ------------------------------------------------------------ prompt line */
/* A header for something you READ: "→ роботи/". The "→ verb" is gray and aria-hidden,
   the object is ink and is the heading's name. Never on the same module as a titled frame. */
.prompt { font-weight: 400; color: var(--ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.prompt__p { color: var(--ink-2); }
.prompt .ascii { display: inline; font: inherit; overflow: visible; }
.side__low > .prompt { grid-column: 1 / -1; margin-block-end: var(--s-2); }

/* ------------------------------------------------------------ art */
/* Hand-drawn ASCII from assets/ascii-art/*.txt, printed by ira_ascii_art(): black, plain ASCII
   (latin subset only), shrunk to fit its column. --art-w = widest line in cells (PHP prints it). */
.art-box { container-type: inline-size; min-inline-size: 0; }
.art {
  display: block; margin: 0;
  font: 400 min(var(--fs), 100cqi / (var(--art-w, 64) * .6))/var(--a-lh) var(--font);
  letter-spacing: 0; font-kerning: none; font-variant-ligatures: none;
  white-space: pre; color: var(--ascii-ink);
  user-select: none; -webkit-user-select: none;
}
.art-cap { margin-block-start: var(--s-3); color: var(--ink-2); }
/* a frame that holds art hugs it: art starts on cell 3 under the title (1 row + 18px) */
.art-frame { position: relative; justify-self: start; inline-size: fit-content; max-inline-size: 100%;
  padding: calc(1.2 * var(--fr-size) + var(--s-3)) calc(3 * .6 * var(--fr-size)); }
.art-frame > .art-box { inline-size: calc(var(--art-w, 64) * .6 * var(--fs)); max-inline-size: 100%; }

/* ------------------------------------------------------------ pager (end of a project) */
.pager { display: grid; grid-template-columns: subgrid; margin-block-start: var(--s-6); }
.pager a { color: var(--ink-2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; padding-block: 3px; }
.pager a:hover, .pager a:focus-visible { color: var(--ink); }
.pager__prev { grid-column: 1 / 4; }
.pager__next { grid-column: 4 / -1; justify-self: end; }
@media (min-width: 768px) { .pager__prev { grid-column: 1 / 3; } .pager__next { grid-column: 3 / -1; } }
/* 0ms swap: the arrow becomes a pointing hand while pointed */
.pager a:not(:hover, :focus-visible) .i-hot, .pager a:is(:hover, :focus-visible) .i-rest { display: none; }

/* ------------------------------------------------------------ pixel icons + cursor */
/* icons sit one space from their label, in the line's own color (currentColor) */
.contacts .icon, .services .icon, .foot__c .icon, .pager .icon, .bar .icon, .meta__status .icon { margin-inline-end: 1ch; }
/* contact and service icons are drawn in ink even where the label is gray: they are what the eye finds */
.contacts .icon, .services .icon, .meta__status .icon { color: var(--ink); }
/* a 2x icon in a text line: centred on the x-height, and its margin box kept to the 18px line */
.icon[width="24"] { vertical-align: -7px; margin-block: -3px; }
.pager__next .icon { margin-inline: 1ch 0; }
.icon--l { margin-inline-end: 0; }
/* PARKED (client, 2026-10-14): the pixel pointing-hand cursor. To bring it back, uncomment this block.
@media (hover: hover) and (pointer: fine) {
  :root { --hand: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2228%22%20height%3D%2228%22%20viewBox%3D%220%200%2014%2014%22%20shape-rendering%3D%22crispEdges%22%3E%3Cpath%20fill%3D%22%23fff%22%20d%3D%22M4%201h3v1h-3zM3%202h9v1h-9zM2%203h11v1h-11zM1%204h13v1h-13zM1%205h12v1h-12zM1%206h11v1h-11zM1%207h10v1h-10zM1%208h11v1h-11zM1%209h10v2h-10zM2%2011h8v1h-8zM3%2012h6v1h-6z%22%2F%3E%3Cpath%20transform%3D%22translate(1%201)%22%20d%3D%22M3%201h3v1h-3zM2%202h1v1h-1zM6%202h5v1h-5zM1%203h1v7h-1zM11%203h1v1h-1zM6%204h5v1h-5zM9%205h1v1h-1zM5%206h4v1h-4zM9%207h1v1h-1zM5%208h4v1h-4zM8%209h1v1h-1zM2%2010h6v1h-6z%22%2F%3E%3C%2Fsvg%3E") 25 8, pointer; }
  a[href], button, .cover { cursor: var(--hand); }
}
*/
/* motion toggle: PHP prints both icons, CSS shows the one that matches <html data-motion> */
html[data-motion="off"] .i-on, html[data-motion="on"] .i-off { display: none; }

/* ---------------------------------------------------------- view + print */
@media print {
  .fr, .ascii, .spin, .viewer, .viewer__say, .skip, [data-motion-toggle], .fig__play, .icon { display: none !important; }
  :root { --ink-2: #000; }
  .side, .bar { position: static !important; block-size: auto !important; overflow: visible !important; }
}

/* ------------------------------------------------------------ butterfly (v2.3) */
/* ascii.rest butterfly (64 x 24 cells) at a small cell, fixed to the viewport; ui.js flutter() moves it.
   Over every text, under the works (images and videos sit on --z-work). Never catches the pointer. */
.flutter {
  position: fixed; inset: 0 auto auto 0; z-index: var(--z-fly);
  margin: 0; font-size: clamp(3.33px, .28vw, 4.67px); /* 1.5x smaller than v2.3 (client, 2026-10-14) */ inline-size: 38.4em; block-size: 28.8em;
  color: var(--ascii-ink); pointer-events: none; will-change: transform;
}
:is(.cover, .fig) > :is(img, video) { position: relative; z-index: var(--z-work); }
@media print { .flutter { display: none; } }
html[data-motion="off"] .flutter { display: none; } /* resting over text it would hide it; reduced motion = no butterfly */
