:root {
  /* dark is the default. murdered out, no colour anywhere. */
  --bg: #0a0a0b;
  --ink: #ecebe7;
  --muted: #75757a;
  --faint: rgba(236, 235, 231, 0.09);

  /* No accent colour. This is hers to pick. Set it in both themes and it
     reappears on the period, link hovers and focus rings at once. */
  --accent: #ecebe7;

  --panel: #0a0a0b;      /* the code panel field */
  --card: #f4f2ec;       /* the plate the code sits on */
  --card-edge: transparent;
  --code-ink: #0a0a0b;   /* the modules themselves */

  --sans: "Zen Kaku Gothic New", system-ui, sans-serif;
  --serif: "Shippori Mincho", Georgia, serif;

  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
}

/* Light is a considered palette, not an inversion. Warm bone rather than
   white, warm near-black rather than pure black, so it reads as paper
   instead of as a screen with the lights on. */
:root[data-theme="light"] {
  --bg: #f2f0ea;
  --ink: #16130f;
  --muted: #75706a;
  --faint: rgba(22, 19, 15, 0.13);
  --accent: #16130f;

  --panel: #f2f0ea;
  /* the plate flips to a dark mat in light mode. the code inside it does
     not, because it cannot. see the note on the card below. */
  --card: #16130f;
  --card-edge: rgba(22, 19, 15, 0.16);
  --code-ink: #0a0a0b;
}

/* Colour only animates after the first paint, so loading the page does
   not look like a transition. */
html.mode-ready,
html.mode-ready body,
html.mode-ready .lining {
  transition: background-color 0.35s var(--ease), color 0.35s var(--ease);
}

* { box-sizing: border-box; }


html {
  scroll-behavior: smooth;
  /* keep anchor targets clear of the fixed header */
  scroll-padding-top: 7rem;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--sans);
  font-weight: 400;
  line-height: 1.75;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

body.is-locked { overflow: hidden; }

.sprite { position: absolute; width: 0; height: 0; overflow: hidden; }

/* the period is dimmed rather than coloured. still a detail, no hue. */
.dot { color: var(--muted); }

/* ---- header ---- */
.bar {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 40;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 1.6rem clamp(1.25rem, 5vw, 3.5rem);
  /* a scrim built from the theme token, so content scrolling underneath
     never collides with the header in either mode */
  background: linear-gradient(to bottom, var(--bg) 55%, transparent);
}

.wordmark {
  font-family: var(--serif);
  font-size: 0.85rem;
  font-weight: 400;
  letter-spacing: 0.08em;
  color: var(--ink);
  text-decoration: none;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.4s var(--ease);
}

.wordmark.is-shown {
  opacity: 1;
  pointer-events: auto;
}

.bar-right {
  display: flex;
  align-items: center;
  gap: 1.35rem;
}

/* A circle, half filled. No sun, no moon, no cartoon. It turns over
   rather than swapping to a different icon, so the switch reads as one
   object changing its mind. */
.mode-btn {
  display: flex;
  align-items: center;
  padding: 0.35rem;
  margin: -0.35rem;
  background: none;
  border: none;
  color: var(--ink);
  cursor: pointer;
  opacity: 0.55;
  transition: opacity 0.3s var(--ease);
}

.mode-btn:hover,
.mode-btn:focus-visible { opacity: 1; }

.mode-glyph {
  width: 15px;
  height: 15px;
  display: block;
  transition: transform 0.5s var(--ease);
}

:root[data-theme="light"] .mode-glyph { transform: rotate(180deg); }


/* ---- hero ---- */
.hero {
  position: relative;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 6rem clamp(1.25rem, 5vw, 3.5rem) clamp(2.5rem, 7vh, 4rem);
}

/* the mark is small on purpose. understatement is the whole argument,
   and an 11rem logo was arguing the opposite. */
.hero h1 {
  margin: 0;
  font-family: var(--serif);
  font-weight: 400;
  font-size: clamp(1.5rem, 3.4vw, 2rem);
  letter-spacing: 0.14em;
  text-indent: 0.14em;
  line-height: 1;
}

.hero-line {
  margin: 1.6rem 0 0;
  font-size: 0.78rem;
  letter-spacing: 0.1em;
  color: var(--muted);
}

.ghost {
  position: absolute;
  bottom: clamp(2.5rem, 7vh, 4rem);
  left: 50%;
  transform: translateX(-50%);
  padding: 0 0 0.3rem;
  background: none;
  border: none;
  border-bottom: 1px solid var(--faint);
  color: var(--muted);
  font-family: var(--sans);
  font-size: 0.82rem;
  letter-spacing: 0.05em;
  cursor: pointer;
  transition: color 0.3s var(--ease), border-color 0.3s var(--ease);
}

.ghost:hover {
  color: var(--ink);
  border-bottom-color: var(--ink);
}

.ghost:active { transform: translateX(-50%) scale(0.98); }

/* ---- blocks ---- */
.block {
  max-width: 46rem;
  /* spacing lives in margin, not padding, so an anchor jump lands on the
     content instead of on a screenful of empty padding */
  margin: clamp(4.5rem, 14vh, 8rem) auto 0;
  padding: 0 clamp(1.25rem, 5vw, 3.5rem);
}

/* The reveal only exists when scripting does. If script.js never runs,
   the content is simply there, which is how it should have been built
   the first time. */
html.js .block,
html.js .plate {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 0.45s var(--ease), transform 0.45s var(--ease);
}

html.js .block.is-visible,
html.js .plate.is-visible {
  opacity: 1;
  transform: translateY(0);
}

.block:last-of-type { margin-bottom: clamp(4.5rem, 14vh, 8rem); }

/* tail space so the final section can still scroll up to the anchor
   position, otherwise the last nav link lands lower than the others */
main { padding-bottom: 26vh; }

.block h2 {
  margin: 0 0 1.5rem;
  font-family: var(--serif);
  font-weight: 400;
  font-size: 1.05rem;
  letter-spacing: 0.02em;
  color: var(--muted);
}

.block p {
  margin: 0 0 1.25rem;
  max-width: 40ch;
  font-size: 1rem;
  color: var(--ink);
}

.block p:last-child { margin-bottom: 0; }

.lede {
  font-family: var(--serif);
  font-size: clamp(1.35rem, 4vw, 1.9rem) !important;
  line-height: 1.5;
  max-width: 26ch !important;
}

/* The turn gets its own air. The list is setup, this is the line, and
   putting it in the same paragraph lets a reader scroll past it. */
.turn {
  margin-top: 2.25rem !important;
  font-family: var(--serif);
  font-size: clamp(1.35rem, 4vw, 1.9rem) !important;
  line-height: 1.5;
  max-width: 26ch !important;
}

/* the machine readable sentence. kept because it is the only plain
   statement of what this place actually is. */
.plain {
  margin-top: 3.5rem !important;
  color: var(--muted) !important;
  font-size: 0.95rem !important;
}

/* ---- channels ---- */
.channels { list-style: none; margin: 0; padding: 0; }

.channel {
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 1.05rem 0;
  border-bottom: 1px solid var(--faint);
  color: var(--ink);
  text-decoration: none;
  transition: color 0.3s var(--ease), padding-left 0.35s var(--ease);
}

a.channel:hover,
a.channel:focus-visible {
  color: var(--accent);
  padding-left: 0.55rem;
}

.glyph {
  width: 19px;
  height: 19px;
  flex-shrink: 0;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.3;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.channel-name { font-size: 0.95rem; }

.channel-note {
  margin-left: auto;
  font-size: 0.78rem;
  color: var(--muted);
  letter-spacing: 0.03em;
}

.channel.is-quiet { color: var(--muted); cursor: default; }
.channel.is-quiet .glyph { opacity: 0.4; }

/* ---- the lining ---- */
.lining {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
  background: var(--panel);
  transform: translateY(100%);
  visibility: hidden;
  transition: transform 0.6s var(--ease), visibility 0.6s;
}

.lining.is-open { transform: translateY(0); visibility: visible; }

.lining-close {
  position: absolute;
  top: 1.6rem;
  right: clamp(1.25rem, 5vw, 3.5rem);
  background: none;
  border: none;
  color: var(--muted);
  font-family: var(--sans);
  font-size: 0.8rem;
  letter-spacing: 0.06em;
  cursor: pointer;
  padding: 0.4rem;
}

.lining-close:hover { color: var(--ink); }

.lining-inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  opacity: 0;
  transition: opacity 0.4s var(--ease) 0.25s;
}

.lining.is-open .lining-inner { opacity: 1; }

/* The plate flips with the theme. The code on it does not.
   A QR with light modules on a dark field is outside the format, which
   assumes dark on light. Tested against a real decoder at three sizes:
   standard passed every time, inverted failed every time. So in light
   mode the mat goes dark and the code keeps its own white ground inside
   it. The plate flips, the code stays scannable. */
.code-card {
  width: min(74vw, 320px);
  padding: 1.1rem;
  background: var(--card);
  border: 1px solid var(--card-edge);
  transition: background-color 0.35s var(--ease);
}

/* in light mode the code needs its own light ground inside the dark mat */
:root[data-theme="light"] .code-art {
  background: #ffffff;
  padding: 0.75rem;
}

/* With the panel matching the page, the slide up and the card are the
   whole transition. Nothing else marks the change, and nothing needs to. */

.code-art { line-height: 0; }

.qr { display: block; width: 100%; height: auto; }

.code-hint {
  margin-top: 1.75rem;
  font-family: var(--serif);
  font-size: 0.85rem;
  letter-spacing: 0.04em;
  color: var(--muted);
}

/* ---- footer ---- */
.foot {
  padding: 2.5rem clamp(1.25rem, 5vw, 3.5rem) 3.5rem;
  font-size: 0.78rem;
  color: var(--muted);
}

.foot-row {
  display: flex;
  justify-content: space-between;
}

/* Static on purpose. A copyright year marks first publication, so it
   should not walk forward every January off the system clock. */
.notice {
  letter-spacing: 0.04em;
}

/* left for whoever actually looks. no hover required, so it reads the
   same on a phone as it does on a laptop. */
.whisper {
  margin: 2.5rem 0 0;
  font-family: var(--serif);
  font-size: 0.8rem;
  letter-spacing: 0.04em;
  color: color-mix(in srgb, var(--ink) 17%, transparent);
}

.whisper--fainter {
  margin-top: 0.35rem;
  color: color-mix(in srgb, var(--ink) 9%, transparent);
}

/* ---- quality floor ---- */
:focus-visible {
  outline: 1px solid var(--accent);
  outline-offset: 4px;
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .block { opacity: 1; transform: none; transition: none; }
  .lining, .lining-inner, .channel, .ghost { transition: none; }
  .lining { transform: translateY(0); }
  .lining:not(.is-open) { display: none; }
}


/* ---- art plate ----
   Medium agnostic on purpose. She works in watercolor, oils, pencil,
   chalk, crayon, clay, digital, photography, animation and video, so
   this holds a still or a moving piece without changing shape.

   Nothing is cropped. Native aspect ratio is preserved and the height is
   capped so a tall piece still fits one screen. Photographed analog work
   keeps its paper edge and uneven color, which is the good part. */

.plate {
  margin: clamp(5rem, 16vh, 9rem) auto 0;
  padding: 0 clamp(1.25rem, 5vw, 3.5rem);
  max-width: 72rem;
}

.plate figure {
  margin: 0;
}

.plate img,
.plate video {
  display: block;
  margin: 0 auto;
  max-width: 100%;
  max-height: 82svh;
  width: auto;
  height: auto;
}

/* a piece that wants the whole screen, for clay, sculpture, anything
   that reads better big */
.plate--full {
  max-width: none;
  padding: 0;
}

/* full still means one screen. a piece you cannot see all of at once is
   a piece nobody sees. this lets a wide panorama reach both edges while
   a square simply sits taller and centered. */
.plate--full img,
.plate--full video {
  max-height: 92svh;
  max-width: 100%;
  width: auto;
}

.plate figcaption {
  margin: 1.1rem auto 0;
  max-width: 40ch;
  text-align: center;
  font-family: var(--serif);
  font-size: 0.78rem;
  line-height: 1.7;
  letter-spacing: 0.04em;
  color: var(--muted);
}

.plate--full figcaption {
  padding: 0 clamp(1.25rem, 5vw, 3.5rem);
}

@media (prefers-reduced-motion: reduce) {
  .plate { opacity: 1; transform: none; transition: none; }
}


/* ---- 404 ---- */
.lost {
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 6rem clamp(1.25rem, 5vw, 3.5rem) 3rem;
}

.lost-code {
  margin: 0 0 1.75rem;
  font-family: var(--serif);
  font-size: 0.8rem;
  letter-spacing: 0.35em;
  text-indent: 0.35em;
  color: var(--muted);
}

.lost h1 {
  margin: 0;
  font-family: var(--serif);
  font-weight: 400;
  font-size: clamp(1.5rem, 3.4vw, 2rem);
  letter-spacing: 0.1em;
  text-indent: 0.1em;
}

.lost-note {
  margin: 1.5rem 0 0;
  max-width: 34ch;
  text-wrap: balance;
  font-size: 0.9rem;
  color: var(--muted);
}

.lost-back {
  margin-top: 3.25rem;
  padding-bottom: 0.3rem;
  border-bottom: 1px solid var(--faint);
  color: var(--muted);
  text-decoration: none;
  font-size: 0.82rem;
  letter-spacing: 0.05em;
  transition: color 0.3s var(--ease), border-color 0.3s var(--ease);
}

.lost-back:hover {
  color: var(--ink);
  border-bottom-color: var(--ink);
}

/* the 404 has no hero to scroll past, so the mark is present immediately */
.page-404 .wordmark.is-shown { opacity: 1; pointer-events: auto; }


/* ---- the header ----------------------------------------------------
   There is no navigation. Three sections on one page is a scroll, and
   anything persistent sitting on top of the content is the visitor
   feeling the site instead of reading it.

   The header hides the moment you scroll down, because scrolling down
   means you are reading. It returns the moment you scroll up, because
   scrolling up means you are looking for something. Chrome on intent
   rather than chrome on position.

   When there is a shop worth navigating, it goes here, and it will
   already behave correctly. */

.bar {
  transform: translateY(0);
  transition: transform 0.42s cubic-bezier(0.32, 0.72, 0, 1);
}

.bar.is-tucked {
  transform: translateY(-100%);
}

@media (prefers-reduced-motion: reduce) {
  .bar { transition: none; }
}


/* ---- the menu ------------------------------------------------------
   It drops from under the header, because that is where it was pressed.
   It never appears on its own, it covers only what it needs, and it is
   gone the instant you look away.

   The structure is a display case, not a table of contents. When there
   is work to sell, the pieces sit at the top of the panel and the
   section links sit beneath them. A shop's menu should show the thing,
   not describe the category it lives in.

   Every link carries a second line saying what it is. That is the one
   pattern worth keeping from fastpitch.dev and origin.club. */

[hidden] { display: none !important; }

/* Three dots, not three lines. The overflow mark is what this audience
   actually taps a hundred times a day inside the apps they live in; a
   hamburger is a website convention. It also belongs to the family
   already on this site, which is circles and single stroke weights, and
   it rhymes with the period in the wordmark.

   Open state resolves into the cross that already exists in the glyph
   sprite, so both states speak the same language. */
.menu-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  margin: -0.4rem -0.5rem -0.4rem 0;   /* 44px target, no visual bulk */
  padding: 0;
  background: none;
  border: none;
  color: var(--ink);
  cursor: pointer;
  opacity: 0.75;
  transition: opacity 0.25s var(--ease);
}

.menu-btn:hover,
.menu-btn:focus-visible { opacity: 1; }

.menu-glyph {
  width: 20px;
  height: 20px;
  display: block;
  overflow: visible;
}

.glyph-dots,
.glyph-cross {
  transform-box: fill-box;
  transform-origin: center;
  transition: opacity 0.24s var(--ease), transform 0.34s cubic-bezier(0.32, 0.72, 0, 1);
}

.glyph-cross {
  opacity: 0;
  transform: rotate(-40deg) scale(0.65);
}

.menu-btn[aria-expanded="true"] .glyph-dots {
  opacity: 0;
  transform: scale(0.4);
}

.menu-btn[aria-expanded="true"] .glyph-cross {
  opacity: 1;
  transform: rotate(0deg) scale(1);
}

@media (prefers-reduced-motion: reduce) {
  .glyph-dots, .glyph-cross, .menu-btn { transition: none; }
}

.menu-scrim {
  position: fixed;
  inset: 0;
  z-index: 36;
  background: color-mix(in srgb, var(--bg) 55%, transparent);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  opacity: 0;
  transition: opacity 0.4s var(--ease);
}

.menu-scrim.is-open { opacity: 1; }

.menu {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 38;   /* under .bar, which is 40. it drops from beneath the header */
  max-height: 86svh;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: calc(4.6rem + env(safe-area-inset-top, 0px)) clamp(1.25rem, 5vw, 3.5rem) 2.25rem;
  background: var(--bg);
  border-bottom: 1px solid var(--faint);
  transform: translateY(-101%);
  transition: transform 0.46s cubic-bezier(0.32, 0.72, 0, 1);
}

.menu.is-open { transform: translateY(0); }

.menu-inner {
  max-width: 62rem;
  margin: 0 auto;
}

/* the work rail. horizontal on a phone so a thumb can flick through it. */
.menu-work {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(150px, 1fr);
  gap: 0.9rem;
  margin-bottom: 2.5rem;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
}

.menu-work::-webkit-scrollbar { display: none; }

.work {
  scroll-snap-align: start;
  text-decoration: none;
  color: var(--ink);
}

.work-frame {
  display: block;
  aspect-ratio: 4 / 5;
  overflow: hidden;
  background: color-mix(in srgb, var(--ink) 5%, transparent);
}

.work-frame img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.6s var(--ease);
}

.work:hover .work-frame img { transform: scale(1.03); }

.work-name {
  display: block;
  margin-top: 0.6rem;
  font-size: 0.82rem;
  letter-spacing: 0.03em;
}

.work-meta {
  display: block;
  margin-top: 0.15rem;
  font-family: var(--serif);
  font-size: 0.75rem;
  color: var(--muted);
}

.work.is-gone .work-frame img { opacity: 0.4; }

/* the links. label plus one line saying what it is. */
.menu-links {
  display: grid;
  gap: 0;
}

.menu-links a,
.menu-code {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  padding: 1.05rem 0;
  border-top: 1px solid var(--faint);
  background: none;
  border-left: none;
  border-right: none;
  border-bottom: none;
  text-align: left;
  text-decoration: none;
  color: var(--ink);
  cursor: pointer;
  transition: padding-left 0.3s var(--ease);
}

.menu-links > *:last-child { border-bottom: 1px solid var(--faint); }

.menu-links a:hover,
.menu-links a:focus-visible,
.menu-code:hover,
.menu-code:focus-visible { padding-left: 0.6rem; }

.menu-label {
  font-family: var(--serif);
  font-size: 1.35rem;
  letter-spacing: 0.02em;
  line-height: 1.2;
}

.menu-note {
  font-family: var(--sans);
  font-size: 0.78rem;
  letter-spacing: 0.03em;
  color: var(--muted);
}

@media (min-width: 46rem) {
  .menu-work {
    grid-auto-flow: row;
    grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
    overflow: visible;
  }
}

@media (prefers-reduced-motion: reduce) {
  .menu, .menu-scrim, .menu-links a, .menu-code, .work-frame img { transition: none; }
}
