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

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