/* Look: torypolska.com (Share Tech, 16:9 gallery grid, coral accent). Header proportions, text size and the
   black body follow lesia-lu.com.
   Responsive behaviour follows both Squarespace sites from 768 to 1920px wide; phones have their own sizes:
     < 600px   upright phone: one image per row
     < 768px   phone:  menu button, 48px logo in a 72px header, 16px text
     768-799   tablet: menu button, full-size logo (106px header row)
     >= 768    gallery: at most 2 images per row
     >= 800    nav shown in the header
     >= 999    21px text, at most 3 images per row
     >= 1400   at most 4 images per row (full-screen windows)
     <= 500px tall (phones turned sideways): menu button and a 56px header, whatever the width
   Page margins are small so the images take the room, as on victoriaying.com: 20px on phones, 30px from 768px, and
   the page has no maximum width (the About page keeps lesia-lu.com's 6vw). */
@font-face {
  font-family: "Share Tech";
  src: url("fonts/share-tech.woff2") format("woff2");
  font-display: swap;
}
:root {
  /* Header is white with dark ink; page body is black with white ink (as on lesia-lu.com) */
  --header-bg: #fff;
  --header-ink: hsl(180 5.88% 20%);
  --header-muted: hsl(180 4% 45%);
  --bg: #000;
  --ink: #fff;
  --accent: hsl(9.23 80.79% 65.29%);
  --muted: hsl(0 0% 65%);
  --pad: 20px;
  --max: none;
  --gap: 12px; /* between images; 16px from 768px */
  --logo-h: 48px; /* diameter of the round logo */
  --header-content: 48px;
  --header-pad-y: 12px;
  --header-h: calc(var(--header-content) + 2 * var(--header-pad-y));
  --page-top: max(34px, 3.9vw);
}
@media (min-width: 768px) { :root { --pad: 30px; --gap: 16px; } }
@media (min-width: 768px) and (min-height: 501px) { :root { --logo-h: 85px; --header-content: 106px; --header-pad-y: max(12px, 1.5vw); } }

* { box-sizing: border-box; }
/* The scrollbar's room is always kept, so the page doesn't shift sideways when the lightbox or menu locks scrolling */
html { font-size: 16px; -webkit-text-size-adjust: 100%; scrollbar-gutter: stable; }
@media (min-width: 999px) { html { font-size: 21px; } }
body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: "Share Tech", system-ui, sans-serif;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}
body.is-locked { overflow: hidden; }
a { color: inherit; text-decoration: none; }
img { display: block; max-width: 100%; }
button { -webkit-tap-highlight-color: transparent; }

/* ---------- Header ---------- */
.top-header {
  position: fixed; inset: 0 0 auto 0; z-index: 20;
  background: var(--header-bg); color: var(--header-ink);
}
.top-header__inner {
  height: var(--header-h);
  padding: var(--header-pad-y) var(--pad);
  display: flex; align-items: center;
}
.logo { flex: none; }
/* The logo is always a circle. Its sizes match the round picture inside lesia-lu.com's logo image, which had
   transparent space around it: 85px in the 106px header row. Phones get 48px, as big as the menu button. */
.logo img { width: var(--logo-h); height: var(--logo-h); border-radius: 50%; object-fit: cover; }

/* Shrinking header, like tympanus.net/Blueprints/AnimatedHeader: once the page is scrolled 300px, app.js adds
   .is-shrunk and the header eases to a smaller size in 0.3s. Only the header changes; the page keeps its top
   padding, so nothing below it jumps. The menu overlay's top bar follows, so its close button sits where the menu
   button was. Phones go from 72px to 60px; sideways phones keep their 56px header. */
.top-header__inner, .logo img { transition: height .3s, width .3s, padding .3s; }
.top-header.is-shrunk, .top-header.is-shrunk + .mobile-menu {
  --logo-h: 40px; --header-content: 44px; --header-pad-y: 8px; --header-h: calc(var(--header-content) + 2 * var(--header-pad-y));
}
@media (min-width: 768px) and (min-height: 501px) { .top-header.is-shrunk, .top-header.is-shrunk + .mobile-menu { --logo-h: 45px; --header-content: 56px; --header-pad-y: 12px; } }
@media (max-height: 500px) {
  :root, .top-header.is-shrunk, .top-header.is-shrunk + .mobile-menu { --logo-h: 36px; --header-content: 40px; --header-pad-y: 8px; }
}
@media (prefers-reduced-motion: reduce) { .top-header__inner, .logo img { transition: none; } }

/* When the nav doesn't fit on one line it wraps onto a second line, like lesia-lu.com; the logo never shrinks. */
.nav { display: flex; flex-wrap: wrap; column-gap: 0.9em; row-gap: 0.15em; }
/* The current page stands out: its link is full ink with a 2px coral line right under the letters, the others are
   dimmed. The line grows in from the left when the page changes. */
.nav a { position: relative; color: var(--header-muted); transition: color .15s; white-space: nowrap; }
.nav a::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0.12em; height: 2px; background: var(--accent);
  transform: scaleX(0); transform-origin: left;
}
.nav a.is-active { color: var(--header-ink); }
.nav a.is-active::after { transform: none; animation: nav-line .35s ease-out; }
@keyframes nav-line { from { transform: scaleX(0); } }
@media (prefers-reduced-motion: reduce) { .nav a.is-active::after { animation: none; } }
.top-header .nav { display: none; flex: 1 1 auto; min-width: 0; margin-left: clamp(16px, 2.5vw, 48px); }

/* Gap and colour match the torypolska.com header (1.3vw apart, same ink as the nav); the icons are bigger than
   its 20px, at 28px */
.social { display: flex; align-items: center; gap: 1.3vw; }
.social__icon { display: block; width: 28px; height: 28px; line-height: 0; transition: color .15s; }
.social__icon svg { width: 100%; height: 100%; fill: currentColor; }
.top-header .social { display: none; flex: none; margin-left: clamp(16px, 2.5vw, 48px); }

/* Two-line menu button, as on both reference sites. The lines are 26px wide and line up with the page margin; the
   tappable area around them is 44px. */
.burger, .mobile-menu__close {
  position: relative; flex: none; width: 44px; height: 44px;
  margin-left: auto; margin-right: -9px; padding: 0; border: 0; background: none; color: inherit; cursor: pointer;
}
.burger span, .mobile-menu__close span {
  position: absolute; left: 9px; right: 9px; height: 2px; background: currentColor;
}
.burger span:first-child { top: calc(50% - 5px); }
.burger span:last-child  { top: calc(50% + 3px); }

@media (min-width: 800px) and (min-height: 501px) {
  .top-header .nav, .top-header .social { display: flex; }
  .top-header .social { margin-left: auto; }
  .burger { display: none; }
}

/* Full-screen menu (below 800px, and on phones turned sideways). It fades in rather than popping up: visibility waits for the fade to end when
   closing, so the hidden menu still can't be tapped or tabbed into. */
.mobile-menu {
  position: fixed; inset: 0; z-index: 30; background: var(--header-bg); color: var(--header-ink);
  display: flex; flex-direction: column; overflow-y: auto;
  visibility: hidden; opacity: 0; transition: opacity .3s ease, visibility 0s .3s;
}
.mobile-menu.is-open { visibility: visible; opacity: 1; transition: opacity .3s ease; }
/* The close button starts as the two lines of the menu button and turns into a cross */
.mobile-menu__close span:first-child { top: calc(50% - 5px); }
.mobile-menu__close span:last-child  { top: calc(50% + 3px); }
.mobile-menu__close span { transition: transform .2s, top .2s .15s; }
.mobile-menu.is-open .mobile-menu__close span { top: calc(50% - 1px); transition: top .2s .1s, transform .25s .25s; }
.mobile-menu.is-open .mobile-menu__close span:first-child { transform: rotate(45deg); }
.mobile-menu.is-open .mobile-menu__close span:last-child  { transform: rotate(-45deg); }
/* The links rise in one after another, then the icons; the current page's line draws once they're in */
.mobile-menu .nav a, .mobile-menu .social {
  opacity: 0; transform: translateY(14px); transition: opacity .2s, transform .2s, color .15s;
}
.mobile-menu.is-open .nav a, .mobile-menu.is-open .social {
  opacity: 1; transform: none;
  transition: opacity .4s ease-out, transform .4s cubic-bezier(.2, .7, .3, 1), color .15s;
  transition-delay: calc(.1s + var(--i, 0) * .06s), calc(.1s + var(--i, 0) * .06s), 0s;
}
.mobile-menu.is-open .social { --i: var(--links); }
.mobile-menu .nav a.is-active::after { animation: none; }
.mobile-menu.is-open .nav a.is-active::after { animation: nav-line-menu .35s ease-out .45s backwards; }
@keyframes nav-line-menu { from { transform: scaleX(0); } }
/* With reduced motion the menu only fades */
@media (prefers-reduced-motion: reduce) {
  .mobile-menu .nav a, .mobile-menu .social { transform: none; }
  .mobile-menu .nav a, .mobile-menu .social, .mobile-menu__close span,
  .mobile-menu.is-open .nav a, .mobile-menu.is-open .social, .mobile-menu.is-open .mobile-menu__close span { transition: none; }
  .mobile-menu.is-open .nav a.is-active::after { animation: none; }
}
.mobile-menu__bar { height: var(--header-h); padding: var(--header-pad-y) var(--pad); display: flex; align-items: center; flex: none; }
.mobile-menu .nav {
  flex: 1; flex-direction: column; flex-wrap: nowrap; align-items: center; justify-content: center;
  gap: 0.35em; padding: 0 var(--pad) 2rem; font-size: clamp(1.5rem, 7vw, 2.4rem); line-height: 1.25;
}
.mobile-menu .social { justify-content: center; gap: 1.6rem; padding: 0 0 max(2rem, 8vw); }
.mobile-menu .social__icon { width: 40px; height: 40px; }
/* Sideways phones: smaller links so the menu fits without scrolling */
@media (max-height: 500px) {
  .mobile-menu .nav { font-size: 1.35rem; gap: 0.1em; padding-bottom: 0.5rem; }
  .mobile-menu .social { padding-bottom: 1rem; }
  .mobile-menu .social__icon { width: 32px; height: 32px; }
}
@media (min-width: 800px) and (min-height: 501px) { .mobile-menu { display: none; } }

@media (hover: hover) {
  .nav a:hover, .social__icon:hover { color: var(--accent); }
}

/* ---------- Page body ---------- */
.page {
  max-width: var(--max); margin: 0 auto;
  padding: calc(var(--header-h) + var(--page-top)) var(--pad) max(3rem, 6vw);
}
/* The About page's layout is measured on lesia-lu.com's margins (see .about) */
.page--about { --pad: 6vw; }
.section { margin-bottom: max(2.5rem, 5vw); }
.section:last-child { margin-bottom: 0; }
.section__head { margin-bottom: max(0.9rem, 1.2vw); }
.section__title { font-weight: 400; font-size: calc(1.05rem + 0.35vw); letter-spacing: -0.02em; line-height: 1.2; margin: 0 0 0.4rem; }
.section__text { margin: 0; color: var(--muted); white-space: pre-line; }

/* Gallery: thumbnails in justified rows, about as tall as a 16:9 tile in 2 columns (3 from 999px, 4 from 1400px).
   Each is as wide as its shape (--ar, set by app.js: cut wider or narrower in the admin, 16:9 for older ones). Where
   rows end is worked out in gridMarks (theme.js), for 2, 3 and 4 columns: an .is-2, .is-3 or .is-4 .grid-break starts
   the next row, and only the current number's are shown. A row's tiles grow in proportion to their shape, so they
   fill the width and share one height; .grid-fills keep a short last row at the usual height, centered. --tile-h is that
   height, for the admin. */
/* The space under each row is the tiles' margin rather than a row gap, which the breaks' own lines would double. */
.grid-169 {
  --cols: 2; --tile-h: calc((100cqw - (var(--cols) - 1) * var(--gap)) / var(--cols) * 9 / 16);
  container-type: inline-size; display: flex; flex-wrap: wrap; align-items: flex-start; column-gap: var(--gap);
  margin-bottom: calc(-1 * var(--gap));
}
.grid-169 > :not(.grid-break, .grid-fill) { margin-bottom: var(--gap); }
/* (Grow factors are scaled up, as in .stack__item: when they add up to less than 1, flexbox leaves the rest empty) */
.grid-169 .thumb { flex: calc(var(--ar, 16 / 9) * 1000) 1 0; width: auto; min-width: 0; aspect-ratio: var(--ar, 16 / 9); }
.grid-169 img { width: 100%; height: 100%; object-fit: cover; }
.grid-break { flex: 0 0 100%; height: 0; }
.grid-fill { flex-basis: 0; height: 0; }
/* 3 per row from 999px, and on short screens (phones turned sideways), where 2 would make rows taller than the screen */
@media (min-width: 999px), (max-height: 500px) { .grid-169 { --cols: 3; } .grid-169 > .is-2 { display: none; } }
@media (min-width: 1400px) and (min-height: 501px) { .grid-169 { --cols: 4; } }
@media (max-width: 998px) and (min-height: 501px), (min-width: 1400px) and (min-height: 501px) { .grid-169 > .is-3 { display: none; } }
@media (max-width: 1399px), (max-height: 500px) { .grid-169 > .is-4 { display: none; } }
/* The phone's copy of the image is in a <picture> (app.js, thumb()) */
.thumb picture { display: block; width: 100%; height: 100%; }
/* Upright phones (under 600px): one image per row, the full width, whole and in its own shape (--whole), as on
   victoriaying.com */
@media (max-width: 599px) and (min-height: 501px) {
  .grid-169 .thumb { flex: 0 0 100%; aspect-ratio: var(--whole, var(--ar)); }
  .grid-169 > :is(.grid-break, .grid-fill) { display: none; }
}

.thumb {
  display: block; width: 100%; padding: 0; border: 0; background: #111;
  cursor: zoom-in; overflow: hidden;
}
/* Thumbnails fade in as they arrive (app.js adds .is-in when one has loaded) */
.thumb img { opacity: 0; transition: opacity .45s ease, transform .4s ease; }
.thumb img.is-in { opacity: 1; }
@media (hover: hover) { .thumb:hover img { transform: scale(1.03); } }

/* Videos in the grid play in their tile */
.thumb--video { cursor: auto; }
.grid-169 .thumb--video video { width: 100%; height: 100%; object-fit: contain; background: #000; }

/* YouTube videos: their cover with a play button. From the grid they play in a box over the page, in the large layout
   in their place (app.js). */
.thumb--youtube, .stack__youtube { position: relative; cursor: pointer; }
.stack__youtube { display: block; width: 100%; height: 100%; padding: 0; border: 0; background: #000; }
.yt-play {
  position: absolute; left: 50%; top: 50%; width: 68px; height: 48px; margin: -24px 0 0 -34px; border-radius: 14px;
  background: rgb(0 0 0 / .62); pointer-events: none; transition: background-color .2s;
}
.yt-play::after {
  content: ''; position: absolute; left: 27px; top: 14px;
  border-style: solid; border-width: 10px 0 10px 17px; border-color: transparent transparent transparent #fff;
}
@media (hover: hover) { :is(.thumb--youtube, .stack__youtube):hover .yt-play { background: rgb(0 0 0 / .85); } }
.stack__item iframe { display: block; width: 100%; height: 100%; border: 0; background: #000; }
.yt-box {
  position: fixed; inset: 0; z-index: 50; display: flex; align-items: center; justify-content: center;
  background: rgb(0 0 0 / .97);
}
.yt-box__frame { width: min(92vw, 86vh * 16 / 9); aspect-ratio: 16 / 9; background: #000; }
.yt-box__frame iframe { display: block; width: 100%; height: 100%; border: 0; }
.yt-box__close {
  position: absolute; top: 0.5rem; right: 1vw; padding: 1rem; border: 0; background: none;
  color: #fff; font: inherit; font-size: 1.2rem; line-height: 1; cursor: pointer;
}
body.is-playing { overflow: hidden; }

/* Large layout (app.js, stack()): images whole, in rows down the page, as wide as the grid. A row is --row percent
   of the page wide (centered); its images share one height because each grows in proportion to its shape
   (--ar = width / height, of the part in view when the image has a view). Phones use the full width for every row. */
.section--large .section__head { text-align: center; }
.stack { --stack-gap: var(--gap); margin: 0 auto; display: flex; flex-direction: column; align-items: center; gap: var(--stack-gap); }
.stack__row { display: flex; gap: var(--stack-gap); width: calc(var(--row) * 1%); }
/* (Grow factors are scaled up: when they add up to less than 1, flexbox leaves the rest of the row empty) */
.stack__item { position: relative; flex: calc(var(--ar) * 1000) 1 0; min-width: 0; aspect-ratio: var(--ar); overflow: hidden; }
/* An image with a view: the whole picture, larger than its tile and moved so the part in view fills it */
.stack img.is-cropped { position: absolute; max-width: none; }
.stack .thumb { background: none; }
.stack img, .stack video { width: 100%; height: 100%; }
.stack video { display: block; background: #000; }
@media (hover: hover) { .stack .thumb:hover img { transform: none; } }
@media (max-width: 767px) { .stack__row { width: 100%; } }

/* Back to top, at the end of long pages: quiet like the dimmed menu links, coral on hover */
.to-top {
  display: block; margin: 0 auto; padding: 0.6rem 1rem; border: 0; background: none;
  font: inherit; color: var(--muted); cursor: pointer; transition: color .15s;
}
.to-top[hidden] { display: none; }
.to-top span { display: inline-block; margin-left: 0.3em; transition: transform .2s ease; }
@media (hover: hover) {
  .to-top:hover { color: var(--accent); }
  .to-top:hover span { transform: translateY(-3px); }
}

/* ---------- About page ---------- */
/* Phones: the CV at full width, uncropped (Squarespace crops it to a fixed 1039px height there, which hides most of it).
   From 768px: lesia-lu.com's fluid grid, measured at 768-1920px wide. Container = min(1535px, page width - 12vw),
   24 columns with 11px gaps, row height = 2.15% of min(1535px, 88vw). The CV block spans 16 columns x 65 rows and
   the image fills it with object-fit: cover, like Squarespace, so it renders slightly zoomed with the black side
   margins trimmed. 44px social icons, 18px apart, centered below. */
.about { container-type: inline-size; max-width: 1535px; margin: 0 auto; display: flex; flex-direction: column; align-items: center; }
.about__cv { width: 100%; height: auto; }
.about__social { margin-top: 2rem; }
.about__social .social { gap: 18px; justify-content: center; }
.about__social .social__icon { width: 44px; height: 44px; }
@media (min-width: 768px) {
  .about__cv {
    width: calc((100cqw - 23 * 11px) * 16 / 24 + 15 * 11px);
    height: calc(65 * 0.0215 * min(1535px, 88vw) + 64 * 11px);
    object-fit: cover;
  }
  .about__social { margin-top: calc(2 * 11px + 0.0215 * min(1535px, 88vw)); }
}

/* The CV as text, laid out like the image it replaces: Myriad-like type, white on black, a bar left of the name
   and of each heading, lines across under the top block and above Skills. As wide as the image was shown, but at
   least 680px when the page has room, so it doesn't turn into a narrow column on half-screen windows. The gutter
   is in px so the bars and the lines start at the same place whatever the text size. When the CV is narrow, the
   contact lines go under the titles, left-aligned. */
.cv {
  --gutter: 44px;
  container: cv / inline-size;
  width: 100%; box-sizing: border-box; padding: 2.5rem 1.25rem 0 calc(var(--gutter) + 4px);
  font: 17px/1.45 "Myriad Pro", "Segoe UI", system-ui, sans-serif; color: #fff;
}
@media (min-width: 768px) {
  .cv { width: max(calc((100cqw - 23 * 11px) * 16 / 24 + 15 * 11px), min(100cqw, 680px)); padding-right: 0; }
}
.cv h1, .cv h2, .cv p, .cv ul { margin: 0; }
.cv strong, .cv h1, .cv h2 { font-weight: 600; }
.cv a { text-decoration: underline; text-decoration-color: rgba(255,255,255,.35); text-underline-offset: 3px; }
.cv a:hover { color: var(--accent); }
.cv__top { display: flex; justify-content: space-between; align-items: flex-start; gap: 1rem 2rem; }
.cv__id { position: relative; font-size: 1.55em; font-weight: 600; line-height: 1.2; }
.cv__id h1 { font-size: 1.45em; margin-bottom: .9em; }
.cv__contact { text-align: right; padding-top: 1.6em; }
.cv__contact p + p { font-weight: 600; }
@container cv (max-width: 640px) {
  .cv__top { flex-direction: column; }
  .cv__contact { text-align: left; padding-top: 0; }
}
.cv__id::before, .cv h2::before {
  content: ""; position: absolute; left: calc(-1 * var(--gutter)); top: .1em; bottom: .1em; width: 6px; background: currentColor;
}
.cv h2 { position: relative; font-size: 1.45em; line-height: 1.2; margin: 1.4em 0 1.2em; }
.cv hr { height: 5px; margin: 1.5em 0 0 calc(-1 * var(--gutter)); border: 0; background: #eee; }
.cv__entry { margin-bottom: 1.4em; }
.cv ul { padding-left: 1.1em; }
.cv li::marker { font-size: .8em; }
@media (max-width: 767px) { .cv { --gutter: 18px; font-size: 15px; } }

/* Shown only on the admin's preview of unpublished changes */
.preview-badge {
  position: fixed; left: 50%; bottom: 16px; z-index: 60; transform: translateX(-50%); pointer-events: none;
  padding: 7px 14px; border-radius: 999px; background: rgba(24,24,27,.9); color: #fff;
  font: 500 13px/1.3 system-ui, sans-serif; box-shadow: 0 0 0 1px rgba(255,255,255,.12), 0 6px 20px rgba(0,0,0,.35);
}

/* ---------- Locked page ---------- */
/* Before its password is typed: each image a tiny copy blown up and blurred (enlarged so the blur's soft edges stay
   outside the tile), and the password box over the whole screen, header included, which it blurs too. The page
   doesn't scroll meanwhile (app.js locks the body). While app.js checks whether this browser already has the
   password, everything is blurred but the box isn't shown yet. */
.thumb.is-blurred { pointer-events: none; background: #1a1a1a; }
.thumb.is-blurred img { width: 100%; height: 100%; object-fit: cover; opacity: 1; filter: blur(22px) saturate(1.2); transform: scale(1.25); transition: none; }
.unlock {
  position: fixed; inset: 0; z-index: 40; display: flex; align-items: center; justify-content: center;
  padding: 1.5rem var(--pad); background: rgb(0 0 0 / .35);
  -webkit-backdrop-filter: blur(14px); backdrop-filter: blur(14px);
  overscroll-behavior: contain; touch-action: none;
}
.unlock.is-checking .unlock__box { visibility: hidden; }
.unlock__box {
  width: min(100%, 22rem); padding: 2rem 1.75rem 1.75rem; border-radius: 14px; text-align: center;
  background: rgb(14 14 14 / .82); color: #fff; box-shadow: 0 0 0 1px rgb(255 255 255 / .1), 0 20px 60px rgb(0 0 0 / .5);
  -webkit-backdrop-filter: blur(12px); backdrop-filter: blur(12px);
}
.unlock__box svg { width: 30px; height: 30px; fill: none; stroke: var(--accent); stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.unlock__title { margin: .6rem 0 .3rem; font-weight: 400; font-size: 1.3rem; line-height: 1.2; }
.unlock__text { margin: 0 0 1.25rem; color: hsl(0 0% 70%); font-size: .85rem; line-height: 1.45; }
.unlock__input, .unlock__button { display: block; width: 100%; font: inherit; font-size: 1rem; border-radius: 8px; }
.unlock__input { padding: .6rem .8rem; border: 1px solid rgb(255 255 255 / .2); background: rgb(255 255 255 / .06); color: #fff; outline: none; }
.unlock__input:focus { border-color: var(--accent); }
.unlock__error { min-height: 1.4em; margin: .4rem 0; color: var(--accent); font-size: .8rem; }
.unlock__button { padding: .6rem; border: 0; background: var(--accent); color: #111; cursor: pointer; transition: filter .15s; }
.unlock__button:hover { filter: brightness(1.08); }
.unlock__box.is-busy .unlock__button { opacity: .6; cursor: progress; }
/* Phones turned sideways: a smaller box, so it fits the short screen */
@media (max-height: 500px) {
  .unlock__box { padding: 1rem 1.25rem; }
  .unlock__box svg { display: none; }
  .unlock__title { margin-top: 0; }
  .unlock__text { margin-bottom: .75rem; }
}

/* ---------- Lightbox ---------- */
.lightbox {
  --ink: #fff; --muted: hsl(0 0% 65%); /* always light on black, whatever the page colors are */
  --lb-w: 88vw; --lb-h: 86vh; /* the most room an image may take */
  /* --fade goes from 1 to 0 while the image is dragged down to close (app.js), letting the page show through */
  position: fixed; inset: 0; z-index: 50; background: rgb(0 0 0 / calc(.97 * var(--fade, 1)));
  display: none; align-items: center; justify-content: center; transition: background-color .25s ease-out;
}
.lightbox.is-open { display: flex; }
.lightbox.is-dragging { transition: none; }
/* The page shows faintly through the lightbox; the header's menu button would look like a line under "Close".
   While the image unfolds from the grid, is dragged down or folds back, the page and its header show. */
body:has(.lightbox.is-open:not(.is-opening):not(.is-dragging):not(.is-closing)) .top-header { visibility: hidden; }
.lightbox.is-closing button, .lightbox.is-closing .lb-count { opacity: 0; transition: opacity .15s; }
/* The strip of full-size images: one screen-wide slide per image, snapping so one is always centered */
.lb-track {
  position: absolute; inset: 0; display: flex; overflow-x: auto; overflow-y: hidden;
  scroll-snap-type: x mandatory; overscroll-behavior-x: contain; touch-action: pan-x; scrollbar-width: none;
}
.lb-track::-webkit-scrollbar { display: none; }
.lb-slide { flex: 0 0 100%; display: flex; align-items: center; justify-content: center; scroll-snap-align: center; scroll-snap-stop: always; }
/* The frame has the image's shape and final size before the file arrives (never larger than the file itself). Until
   then it shows the blurred thumbnail and a spinner; the image fades in over them. */
.lb-frame {
  position: relative; flex: none; overflow: hidden; aspect-ratio: var(--ar); transform-origin: 0 0;
  background: #000; /* the page never shows through the image's box, even while it unfolds before loading */
  width: min(var(--w), var(--lb-w), var(--lb-h) * var(--ar));
}
.lb-frame::before {
  content: ""; position: absolute; inset: -24px; background: var(--ph) center / cover;
  filter: blur(18px); opacity: .55; transition: opacity .3s;
}
.lb-frame::after {
  content: ""; position: absolute; top: 50%; left: 50%; width: 34px; height: 34px; margin: -17px 0 0 -17px;
  border: 2px solid rgba(255,255,255,.25); border-top-color: #fff; border-radius: 50%;
  animation: lb-spin .8s linear infinite;
}
@keyframes lb-spin { to { transform: rotate(1turn); } }
.lb-frame img { position: relative; width: 100%; height: 100%; opacity: 0; transition: opacity .35s; transform-origin: 0 0; }
.lb-frame.is-loaded img { opacity: 1; }
.lb-frame.is-instant img { transition: none; }
.lb-frame.is-loaded::before { opacity: 0; }
.lb-frame.is-loaded::after { display: none; }
.lightbox button { position: absolute; background: none; border: 0; font: inherit; color: var(--ink); cursor: pointer; font-size: 2rem; padding: 1rem; line-height: 1; }
.lightbox button, .lb-count { opacity: var(--fade, 1); transition: opacity .25s; }
.lightbox.is-dragging button, .lightbox.is-dragging .lb-count { transition: none; }
/* Zoomed in (app.js): the image may grow past its frame, and the strip stops swiping so a finger moves the image */
.lightbox.is-zoomed .lb-track { overflow: hidden; touch-action: none; }
.lightbox.is-zoomed .lb-frame { overflow: visible; }
@media (hover: hover) {
  .lb-frame img { cursor: zoom-in; }
  .lightbox.is-zoomed .lb-frame img { cursor: zoom-out; }
}
/* While the lightbox opens, the thumbnail covers its part of the big image until that has loaded (app.js) */
.lb-crop { position: absolute; max-width: none; object-fit: cover; }
.lb-prev { left: 1vw; } .lb-next { right: 1vw; }
.lb-close { top: 0.5rem; right: 1vw; font-size: 1.2rem !important; }
.lb-count { position: absolute; bottom: 1.2rem; left: 50%; transform: translateX(-50%); font-size: .85rem; color: var(--muted); }
@media (hover: hover) { .lightbox button:hover { color: var(--accent); } }
/* Phones: the image uses the full width; arrows move to the bottom corners, out of the way of the artwork. Swipe also works. */
@media (max-width: 767px) {
  .lightbox { --lb-w: 100vw; --lb-h: 78vh; }
  .lb-prev, .lb-next { top: auto; bottom: 0.2rem; }
  .lb-count { bottom: 1.6rem; }
}
/* Phones turned sideways: the image takes the full height, between the arrows */
@media (max-height: 500px) {
  .lightbox { --lb-w: calc(100vw - 8rem); --lb-h: calc(100vh - 4.5rem); }
  .lb-prev, .lb-next { bottom: auto; }
  .lb-close { top: 0; }
  .lb-count { bottom: 0.6rem; }
}
