/* ==========================================================================
   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 mobile breakpoint, though — on a
     narrow viewport the centered content column is as wide as the whole
     window, so a fixed top-right toggle pair sits directly on top of it
     while scrolling. 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: var(--content-margin-x);
  z-index: 10;
  display: flex;
  align-items: center;
  /* Equal to the circles' own diameter (16.8px), per request. */
  gap: 16.8px;
}
@media (max-width: 47.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);
}

/* 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;
}

/* ==========================================================================
   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);
}
