/* No More Names Studio — one page, one typeface, three pastel accents.
   No build step, no dependencies, no JavaScript. The FAQ is native
   <details>, the accents are decoration, the text is plain HTML. */

/* Geist Mono (Vercel), self-hosted from ./fonts — no CDN, no third-party
   request. Variable weight axis 100-900, so one file covers the page.
   Licence: SIL OFL 1.1, see fonts/LICENSE.txt */
@font-face {
  font-family: "Geist Mono";
  src:
    url("fonts/GeistMonoVF.woff2") format("woff2"),
    url("fonts/GeistMonoVF.woff") format("woff");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

:root {
  --bg: #101014; /* near-black with the faintest violet cast */
  --ink: #e7e5e1; /* warm off-white, carries all reading text */
  --ink-dim: #9d9aa6; /* secondary text: meta, status, summaries */
  --rule: rgba(231, 229, 225, 0.11);

  /* Pastels. Used for one thing each: the three tools, plus the single
     emotional beat in the hero. Nothing else on the page is coloured. */
  --purple: #c2aef7;
  --peach: #f5c3a0;
  --moss: #a9c68b;

  /* Status dots only. The green doubles as the moss accent; the yellow is
     the one colour on the page that is not a tool's. */
  --status-live: #a9c68b;
  --status-wip: #e9ce86;

  --measure: 60ch;
  --shell: 44rem;
  --pad: 1.5rem;
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

* {
  margin: 0;
}

html {
  color-scheme: dark;
  /* No `scroll-behavior: smooth`: it animates scrolling on the main thread in
     Chromium and competes with the scroll-driven reveals, which showed up as
     hitching on a machine without GPU compositing. The only in-page jump is the
     skip link, and landing instantly is fine for a keyboard user. */
}

body {
  background: var(--bg);
  color: var(--ink);
  font-family:
    "Geist Mono", ui-monospace, SFMono-Regular, Menlo, "Cascadia Mono",
    "Roboto Mono", monospace;
  font-size: 0.9375rem;
  font-weight: 400;
  line-height: 1.7;
  letter-spacing: -0.01em;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

a {
  color: inherit;
  text-decoration-color: var(--rule);
  text-decoration-thickness: 1px;
  text-underline-offset: 0.25em;
  transition: color 120ms ease;
}

a:hover {
  color: var(--peach);
}

h1,
h2,
h3 {
  font-weight: 500;
  line-height: 1.3;
  letter-spacing: -0.02em;
}

p {
  max-width: var(--measure);
  text-wrap: pretty;
}

:focus-visible {
  outline: 2px solid var(--peach);
  outline-offset: 3px;
  border-radius: 2px;
}

.shell {
  width: 100%;
  max-width: var(--shell);
  margin-inline: auto;
  padding-inline: var(--pad);
}

.skip {
  position: absolute;
  left: -9999px;
  top: 0;
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: 0;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.skip:focus {
  left: var(--pad);
  top: 0.75rem;
  z-index: 2;
  padding: 0.5rem 0.75rem;
  background: var(--bg);
  border: 1px solid var(--rule);
}

/* ---------- masthead ---------- */

.masthead {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1.5rem;
  align-items: baseline;
  justify-content: space-between;
  padding-block: 2rem 0;
}

.wordmark {
  font-weight: 600;
  letter-spacing: -0.03em;
  max-width: none;
}

.elsewhere {
  display: flex;
  gap: 1.25rem;
  font-size: 0.8125rem;
  color: var(--ink-dim);
  max-width: none;
}

.elsewhere a {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  text-decoration-color: currentColor;
}

/* ---------- hero ---------- */

.hero {
  padding-block: 5.5rem 5rem;
}

.hero h1 {
  max-width: 24ch;
  font-size: clamp(1.5rem, 1.05rem + 2.4vw, 2.125rem);
  font-weight: 400;
}

.promise {
  margin-top: 1.25rem;
  color: var(--ink-dim);
}

.beat {
  margin-top: 2rem;
  font-size: 1.25rem;
  font-weight: 500;
  line-height: 1.45;
  color: var(--peach);
}

.lede {
  margin-top: 2.25rem;
  color: var(--ink-dim);
}

/* The only motion on the page: one scroll-bound fade from the Tools band down.
   Each block rises and fades as it enters the viewport and runs backwards as it
   leaves, because the animation follows scroll position instead of firing once
   from a trigger. Nothing animates on load.

   The masthead and the hero are deliberately outside it. They are in the first
   view already, so a scroll fade can only hurt them: an element part-way through
   its entry renders part-transparent, and the hero's last paragraph sits right
   at that boundary — measured 0.92 opacity on load when it was included.

   `screen` matters: a print stylesheet would otherwise capture whatever progress
   an element had, and off-screen sections print blank. A browser without
   `animation-timeline` never applies the animation and shows everything, as does
   any reader with reduced motion set. */
@media screen and (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {
    .band-title,
    .tool,
    .principle,
    .faq details,
    .colophon > * {
      animation: reveal linear both;
      animation-timeline: view();
      /* `entry 40% → entry 100%`, not a `cover` range. Two reasons, both
         measured with a compact hero at 1280×900:
         - a `cover`-anchored range leaves the last block on the page (the
           colophon) permanently short of its end, because the page cannot scroll
           far enough to give it that much coverage — three elements never
           reached full opacity, the footer among them;
         - starting at 40% of the element's own entry keeps a block that is only
           peeking over the fold hidden rather than half-visible, which is what
           the Tools heading did at 0.46 when the range started at entry 5%. */
      animation-range: entry 40% entry 100%;
    }
  }
}

@keyframes reveal {
  from {
    opacity: 0;
    transform: translateY(0.75rem);
  }
}

/* ---------- bands ---------- */

.band {
  border-top: 1px solid var(--rule);
  padding-block: 3.5rem;
}

.band-title {
  font-size: 0.9375rem;
  font-weight: 600;
  color: var(--ink-dim);
}

/* ---------- tool roster ---------- */

.roster {
  list-style: none;
  padding: 0;
  margin-top: 2rem;
}

.tool + .tool {
  border-top: 1px solid var(--rule);
  margin-top: 2rem;
  padding-top: 2rem;
}

.tool-head {
  display: flex;
  align-items: baseline;
  gap: 0.625rem;
  max-width: none;
}

/* Each tool carries one mark, drawn inline in its own pastel: a ghost for
   ghosted, a frame for keret, an ID card for noka. No bitmaps, no requests. */
.mark {
  align-self: center;
  flex: none;
  width: 1.25rem;
  height: 1.25rem;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.25;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.tool--purple .mark {
  color: var(--purple);
}

.tool--peach .mark {
  color: var(--peach);
}

.tool--moss .mark {
  color: var(--moss);
}

.tool-name {
  font-size: 1.0625rem;
  font-weight: 500;
  letter-spacing: -0.02em;
  text-decoration: none;
}

.tool--purple .tool-name:hover {
  color: var(--purple);
}

.tool--peach .tool-name:hover {
  color: var(--peach);
}

.tool--moss .tool-name:hover {
  color: var(--moss);
}

.tool-status {
  margin-left: auto;
  font-size: 0.8125rem;
  color: var(--ink-dim);
}

/* A dot in front of the status word: green for shipped, yellow for in flight. */
.tool-status::before {
  content: "";
  display: inline-block;
  width: 0.375rem;
  height: 0.375rem;
  margin-right: 0.5rem;
  border-radius: 50%;
  vertical-align: 0.0625rem;
  background: currentColor;
}

.tool-status--live::before {
  background: var(--status-live);
}

.tool-status--wip::before {
  background: var(--status-wip);
}

.tool-what {
  margin-top: 0.75rem;
  color: var(--ink);
}

.tool-meta {
  margin-top: 0.5rem;
  font-size: 0.8125rem;
  color: var(--ink-dim);
}

/* The row's own links, labelled, at the bottom of the block. */
.tool-links {
  display: flex;
  align-items: center;
  gap: 1.25rem;
  margin-top: 0.875rem;
  font-size: 0.8125rem;
  max-width: none;
}

.tool-links a {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  color: var(--ink-dim);
  text-decoration-color: currentColor;
}

.tool-links a:hover {
  color: var(--peach);
}

/* Hover follows the project's own colour, same as the project name. */
.tool--purple .tool-links a:hover {
  color: var(--purple);
}

.tool--peach .tool-links a:hover {
  color: var(--peach);
}

.tool--moss .tool-links a:hover {
  color: var(--moss);
}

/* Holds the link slot on a tool that has nothing to link to yet, so every row
   is the same shape and the space does not appear later as a layout shift.
   Not dimmed with opacity: at 13px that drops it under 4.5:1 on this
   background (measured 4.37:1), and the missing underline is distinction
   enough. */
.tool-soon {
  color: var(--ink-dim);
}

.icon {
  flex: none;
  width: 0.875rem;
  height: 0.875rem;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.75;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.icon--brand {
  fill: currentColor;
  stroke: none;
}

/* ---------- principles ---------- */

.principles {
  margin-top: 1.75rem;
}

.principles h3 {
  font-size: 0.9375rem;
  font-weight: 600;
}

.principles p {
  margin-top: 0.5rem;
  color: var(--ink-dim);
}

/* The one mail link on the page: underlined in its own text colour so it reads
   as an action without pulling the eye out of the paragraph. Hover is the same
   peach the masthead and colophon links use. */
.principles a {
  color: inherit;
  text-decoration-color: currentColor;
}

.principles a:hover {
  color: var(--peach);
}

.principle + .principle {
  margin-top: 2rem;
}

/* ---------- faq ---------- */

.faq {
  margin-top: 1.5rem;
}

.faq details {
  border-top: 1px solid var(--rule);
}

.faq details:last-child {
  border-bottom: 1px solid var(--rule);
}

.faq summary {
  display: flex;
  align-items: baseline;
  gap: 1rem;
  padding-block: 1.125rem;
  cursor: pointer;
  color: var(--ink-dim);
  list-style: none;
}

.faq summary::-webkit-details-marker {
  display: none;
}

.faq summary::after {
  content: "+";
  margin-left: auto;
  color: var(--ink-dim);
  transition: transform 160ms ease;
}

.faq details[open] summary {
  color: var(--ink);
}

.faq details[open] summary::after {
  transform: rotate(45deg);
}

.faq summary:hover {
  color: var(--ink);
}

.faq p {
  padding-bottom: 0.875rem;
  color: var(--ink-dim);
}

.faq p:last-child {
  padding-bottom: 1.5rem;
}

/* ---------- 404 ---------- */

/* Cloudflare Pages serves this file for unknown paths, with a 404 status. It
   reuses the page's shell, type scale and link treatment so a wrong turn still
   looks like the studio rather than a server. */
.notfound {
  padding-block: 6rem;
}

.notfound .wordmark {
  margin-bottom: 3rem;
}

.notfound h1 {
  max-width: 22ch;
  font-size: clamp(1.5rem, 1.05rem + 2.4vw, 2.125rem);
  font-weight: 400;
}

.notfound p {
  margin-top: 1.5rem;
  color: var(--ink-dim);
}

.notfound a {
  color: inherit;
  text-decoration-color: currentColor;
}

.notfound a:hover {
  color: var(--peach);
}

/* ---------- colophon ---------- */

.colophon {
  border-top: 1px solid var(--rule);
  padding-block: 2.5rem 3.5rem;
  font-size: 0.8125rem;
  color: var(--ink-dim);
}

.colophon p + p {
  margin-top: 1rem;
}

/* Balance the line lengths in the colophon. Left to itself the disclosure breaks
   as 479px + 279px, which reads as a mistake and orphans "No"; balanced it is
   387px + 371px. Balance is meant for short blocks, which this is. */
.colophon p {
  text-wrap: balance;
}

.heart {
  width: 0.8125rem;
  height: 0.8125rem;
  fill: var(--peach);
  vertical-align: -0.0625rem;
}

/* ---------- small screens ---------- */

@media (max-width: 34rem) {
  :root {
    --pad: 1.25rem;
  }

  .hero {
    padding-block: 3.5rem 3.5rem;
  }

  .band {
    padding-block: 2.5rem;
  }
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  .faq summary::after {
    transition: none;
  }

  a {
    transition: none;
  }
}
