/* ==========================================================================
   Design tokens
   ========================================================================== */

:root {
  /* colors — dark theme is the default. The light theme (html.inverted)
     overrides these with real swapped values below; nothing in this
     stylesheet uses filter: invert() for theming, so there's no filter
     composition to break on any browser. --accent-color is the cycling
     accent color, used raw everywhere since it doesn't change with theme. */
  --color-bg: #000000;
  --color-fg: #ffffff;
  --color-muted: #999999;
  --accent-color: #ff00ff;
  --color-rule: gray;

  /* typography */
  --font-body: 'Netto', system-ui, sans-serif;
  --font-icons: 'Netto Icons', sans-serif;
  --text-size: 1.1rem;
  --line-height: 1.4;

  /* spacing */
  --space-sm: 0.75rem;
  --space-md: 1.25rem;
  --space-lg: 2rem;
  --space-xl: 2.5rem;

  /* layout */
  --content-width: 48rem;
  --content-margin-x: 1.25rem;
  --radius: 0px;
}

/* Base font size is 10% larger on desktop; mobile keeps the smaller size
   set above (48rem matches --content-width, i.e. the same point the
   centered layout stops growing). */
@media (min-width: 48rem) {
  :root {
    --text-size: 1.21rem;
  }
}

@font-face {
  font-family: 'Netto';
  src: url('fonts/Netto-Variable.woff2') format('woff2');
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Netto Icons';
  src: url('fonts/Netto-Icons-Variable.woff') format('woff');
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

/* ==========================================================================
   Reset / base
   ========================================================================== */

* {
  box-sizing: border-box;
}

html {
  width: 100%;
}
html.inverted {
  --color-bg: #ffffff;
  --color-fg: #000000;
  --color-muted: #666666;
}

body {
  background: var(--color-bg);
  color: var(--color-fg);
  font-family: var(--font-body);
  font-size: var(--text-size);
  font-weight: 400;
  line-height: var(--line-height);
  margin: 0;
  padding: 0 var(--content-margin-x) 4rem;
  overflow-wrap: break-word;
  /* Not a stylistic slant — a correction. iOS Safari picks a slanted
     instance of this variable font by default regardless of font-style,
     so the upright position has to be pinned explicitly. Nothing anywhere
     sets this axis away from 0, so no text ever actually renders slanted. */
  font-variation-settings: "slnt" 0;
}

a {
  color: var(--color-fg);
  text-decoration: none;
  transition: color 0.1s;
}
a:hover {
  color: var(--accent-color);
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

/* ==========================================================================
   Layout
   ========================================================================== */

header {
  width: 100%;
  max-width: var(--content-width);
  margin: 0 auto;
  padding: var(--space-md) 0 var(--space-sm);
  display: flex;
  align-items: center;
  justify-content: space-between;
}
header .wordmark {
  color: var(--color-fg);
  font-weight: 800;
  text-decoration: none;
  font-size: calc(var(--text-size) * 1.2);
}
header .toggles {
  /* position: sticky only sticks within its containing block's own height —
     header is a single short row, so once it scrolls fully past the top
     there's no room left to stick within. position: fixed has no such
     limit (which is also why it's pinned to the actual viewport edge here,
     not the narrower centered content column — it would also carry the
     wordmark along if header itself were fixed instead, and that should
     scroll away normally). Only above the breakpoint below, though — the
     content column stops growing at --content-width (48rem), but a fixed
     top-right toggle pair is pinned to the actual viewport edge, not the
     column edge. Once the viewport is only a little wider than 48rem,
     the margin outside the column is narrower than the space the toggles
     themselves need (their right offset + row width, ~82px), so they'd
     overlap the top-right corner of the content until the viewport is
     wide enough to clear it — not just below 48rem. Below that width the
     toggles fall back to their normal position in the header's flex row
     instead, and scroll away with the rest of the header. */
  position: fixed;
  top: calc(var(--space-md) + 0.65rem);
  right: calc(var(--content-margin-x) + 0.75rem);
  z-index: 10;
  display: flex;
  align-items: center;
  /* Equal to the circles' own diameter (16.8px), per request. */
  gap: 16.8px;
}
@media (max-width: 59.9375rem) {
  header .toggles {
    position: static;
    top: auto;
    right: auto;
  }
}

main {
  width: 100%;
  max-width: var(--content-width);
  margin: 0 auto;
  /* clip, not hidden — hidden forces the other axis to compute to auto per
     spec (creating an unwanted internal scrollbar); clip just clips without
     turning main into its own scroll container. */
  overflow-x: clip;
}

/* ==========================================================================
   Typography
   ========================================================================== */

main h1 {
  font-size: var(--text-size);
}

main h2 {
  margin-top: calc(var(--space-xl) * 3);
  margin-bottom: 0;
  padding-top: calc(var(--space-md) * 0.7);
  border-top: 1px solid var(--color-rule);
  font-size: var(--text-size);
  font-style: normal;
  font-weight: 800;
}
main h2 + p {
  color: var(--color-muted);
  margin-top: 0;
}

/* The About bio (unlike a one-line caption under a heading) is body
   copy, not a muted subtitle — keep it in the normal fg color even
   though it directly follows an h2. */
.about-text {
  color: var(--color-muted);
  font-size: calc(var(--text-size) * 1.5);
}
h2.section-title + .about-text {
  margin-top: var(--space-md);
}
.about-text + .about-text {
  margin-top: var(--space-sm);
}

main h3 {
  margin-top: var(--space-lg);
  margin-bottom: 0;
  padding-top: var(--space-md);
  border-top: 1px solid var(--color-rule);
  font-size: var(--text-size);
  font-weight: 800;
}
main h3 + p {
  color: var(--color-muted);
  margin-top: 0;
}

.section-title {
  color: var(--accent-color);
}

.imprint {
  margin-top: calc(var(--space-xl) * 3);
  padding-top: calc(var(--space-md) * 0.7);
  border-top: 1px solid var(--color-rule);
}
.imprint summary {
  cursor: pointer;
  list-style: none;
}
.imprint summary::-webkit-details-marker {
  display: none;
}
.imprint .disclosure::before {
  content: "→";
}
.imprint[open] .disclosure::before {
  content: "↓";
}
.imprint p {
  margin-top: var(--space-sm);
}
.imprint summary ~ p:first-of-type {
  margin-top: var(--space-md);
}
.imprint h3 {
  border-top: none;
  padding-top: 0;
}
.imprint h3 + p {
  color: var(--color-fg);
}

/* An entry pairs a gallery with its caption (heading + text) directly
   below it — the gallery leads, the heading/text follow tight underneath,
   like a photo caption. Entries are separated from each other by extra
   space only, no divider line. */
.entry {
  margin-top: var(--space-lg);
}
.entry + .entry {
  margin-top: calc(12rem / 2);
  padding-top: calc(12rem / 2);
  border-top: 1px solid var(--color-rule);
}
.entry .gallery-wrap {
  margin-top: 0;
}
.entry h2,
.entry h3 {
  margin-top: 1.5rem;
  padding-top: 0;
  border-top: none;
}

/* Prev/next buttons under a multi-image gallery — added by site.js
   (initGalleries), not written by hand into index.html, so every gallery
   gets them without per-entry markup. Left-aligned by default (flex row,
   no justify-content) between the gallery and its caption heading. */
.gallery-nav {
  display: flex;
  gap: 0.4rem;
  margin-top: var(--space-md);
  /* Netto Icons' chevron glyphs carry a large built-in left side-bearing
     (~0.45em, measured from the actual glyph outline) — without this, the
     row's layout box starts at the left edge as expected, but the visible
     ink of the first arrow sits well to the right of it, i.e. of the
     gallery/heading edge above and below it. */
  margin-left: -0.45em;
}
.gallery-nav + h2,
.gallery-nav + h3 {
  margin-top: var(--space-md);
}
.gallery-prev,
.gallery-next {
  cursor: pointer;
  appearance: none;
  -webkit-appearance: none;
  background: none;
  border: none;
  padding: 0;
  color: var(--color-fg);
  font-family: var(--font-icons);
  font-style: normal;
  font-size: var(--text-size);
  line-height: 1;
}
.gallery-prev:hover,
.gallery-next:hover {
  color: var(--accent-color);
}

/* ==========================================================================
   Components
   ========================================================================== */

main ul {
  list-style: none;
  margin: var(--space-sm) 0;
  padding: 0;
}
main li {
  padding: 0.6rem 0;
}

.badge {
  color: var(--accent-color);
  font-style: normal;
}

.icon {
  color: var(--accent-color);
  font-family: var(--font-icons);
  font-style: normal;
}

.icon-list li {
  display: flex;
  align-items: flex-start;
  gap: var(--space-sm);
}
.icon-list .meta {
  display: block;
  color: var(--color-muted);
}

.gallery-wrap {
  position: relative;
  margin-top: var(--space-md);
}

.gallery {
  display: flex;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
  cursor: pointer;
  /* Without this, CSS Scroll Anchoring can silently shift scrollLeft to
     compensate whenever a lazily-loaded slide further along the track
     finishes loading and changes size — landing on a random slide instead
     of the first one, differently on every reload depending on image
     load timing. Chrome/Firefox/Safari all do this the same way, so the
     bug wasn't specific to any one of them. */
  overflow-anchor: none;
}
.gallery::-webkit-scrollbar {
  display: none;
}
/* Added by site.js for single-image "galleries" — nothing to scroll or
   click through to, so the pointer cursor shouldn't imply otherwise. */
.gallery.gallery-single {
  cursor: default;
}
.gallery img,
.gallery svg {
  flex: 0 0 100%;
  min-width: 0;
  max-width: 100%;
  scroll-snap-align: start;
  width: 100%;
  height: auto;
  display: block;
  border-radius: var(--radius);
}

/* SVG authoring convention for any inlined SVG inside a .gallery (see the
   Illustrator layer setup notes for how these are produced). Layers are
   named accent/accent2/…
   and theme/theme2/… (prefix-matched, since one SVG can only have one
   group per exact id — extra numbered layers exist purely to control
   paint order relative to each other, e.g. an accent shape behind vs. in
   front of theme shapes). No filter: invert() anywhere — real per-state
   color values only, since nested/cancelling filters on inlined SVGs were
   unreliable on iOS Safari across three separate attempts.
   - accent*: always tracks var(--accent-color) directly, in both themes.
   - theme*: preserves authored black/white contrast by tracking the page
     fg/bg colors instead of fixed black/white. Matches 3- and 6-digit hex
     plus the SVG spec's implicit default (no fill attribute at all = black)
     so elements never explicitly recolored in Illustrator still work. */
.gallery svg [id^="accent"] [fill]:not([fill="none"]) {
  fill: var(--accent-color);
}
.gallery svg [id^="accent"] [stroke]:not([stroke="none"]) {
  stroke: var(--accent-color);
}
.gallery svg [id^="theme"] [fill="#fff"],
.gallery svg [id^="theme"] [fill="#ffffff"] {
  fill: var(--color-bg);
}
.gallery svg [id^="theme"] :not([fill]),
.gallery svg [id^="theme"] [fill="#000"],
.gallery svg [id^="theme"] [fill="#000000"] {
  fill: var(--color-fg);
}
.gallery svg [id^="theme"] [stroke="#fff"],
.gallery svg [id^="theme"] [stroke="#ffffff"] {
  stroke: var(--color-bg);
}
.gallery svg [id^="theme"] [stroke="#000"],
.gallery svg [id^="theme"] [stroke="#000000"] {
  stroke: var(--color-fg);
}
.cursor-tooltip {
  display: none;
  position: fixed;
  top: 0;
  left: 0;
  transform: translate(-50%, calc(-100% - 10px));
  pointer-events: none;
  /* Fixed white + mix-blend-mode: difference (not var(--color-fg)) — this
     makes the arrow invert against whatever is directly underneath it
     (dark image, light image, accent-colored shape, page bg, …) instead
     of just following the page's own fg/bg theme colors. */
  color: #fff;
  mix-blend-mode: difference;
  background: none;
  font-family: var(--font-icons);
  font-style: normal;
  font-size: 1.8rem;
  white-space: nowrap;
  z-index: 20;
  opacity: 0;
  transition: opacity 0.1s;
}
/* Only devices with a real pointer that can hover (i.e. not touch) get the
   cursor-following arrow — on touch there's no hover/mouseleave to ever
   hide it again, so it would get stuck visible after a tap. */
@media (hover: hover) and (pointer: fine) {
  .cursor-tooltip {
    display: block;
  }
}
.cursor-tooltip.visible {
  opacity: 1;
}

/* ==========================================================================
   Color / invert toggles (in the header, not a scroll-over overlay)
   ========================================================================== */

#color-toggle,
#invert-toggle {
  cursor: pointer;
  appearance: none;
  -webkit-appearance: none;
  background: none;
  background-position: 50%;
  background-repeat: no-repeat;
  border: none;
  /* Box matches the circle's own diameter exactly (no extra padding), so
     the flex gap on header .toggles is the actual on-screen distance
     between the two circles, not just between two larger invisible boxes. */
  width: 16.8px;
  height: 16.8px;
  padding: 0;
  font-size: 0;
}
#color-toggle {
  background-image: radial-gradient(circle, var(--accent-color) 8.4px, transparent 8.4px);
}
#invert-toggle {
  background-image: radial-gradient(circle, var(--color-fg) 8.4px, transparent 8.4px);
}
