* { box-sizing: border-box; }
html { color-scheme: light dark; }
body {
  margin: 0;
  background: var(--canvas-background);
  color: var(--text-primary);
  font: 1rem/1.6 var(--font-sans);
}
h1, h2, h3, p, ul, ol, dl, dd { margin: 0 0 1rem; }
a { color: var(--accent-interactive); text-underline-offset: .2em; }
a:hover { color: var(--accent-primary); }
:focus-visible { outline: 3px solid var(--accent-interactive); outline-offset: 3px; }

.skip {
  position: absolute; left: -999px; top: 0;
  background: var(--surface-card); padding: .6rem 1rem; z-index: 10;
}
.skip:focus { left: .5rem; }

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

.site-head, .site-foot {
  max-width: 1100px; margin-inline: auto; padding: 1rem 1.25rem;
}
main {
  display: block; max-width: 1100px; margin-inline: auto;
  padding: 0 1.25rem 3rem;
}
.site-head {
  display: flex; flex-wrap: wrap; gap: 1rem;
  align-items: baseline; justify-content: space-between;
  border-bottom: 1px solid var(--border-subtle);
}
.brand { font-weight: 600; margin: 0; }
.brand a { color: var(--text-primary); text-decoration: none; }
.brand span {
  display: block; font: .78rem var(--font-mono);
  color: var(--text-muted);
}
.site-head nav, .site-foot nav { display: flex; gap: 1.1rem; align-items: center; flex-wrap: wrap; }
.site-head nav a, .site-foot nav a { text-decoration: none; font-weight: 600; }
.site-foot { border-top: 1px solid var(--border-subtle); color: var(--text-muted); font-size: .92rem; }
.site-foot .fine { font-size: .8rem; }

*, *::before, *::after {
  transition: background-color 0.2s ease, color 0.2s ease,
              border-color 0.2s ease, box-shadow 0.2s ease;
}

.head-toggles { display: inline-flex; gap: .5rem; align-items: center; }
#theme-toggle, #ads-toggle {
  font: 1rem var(--font-sans); padding: .35rem .7rem;
  border: 1px solid var(--border-subtle); border-radius: 8px;
  background: var(--surface-card); color: var(--text-primary); cursor: pointer;
}
/* The Ads switch (static/js/ads.js). It stays hidden until the head script
   has read the visitor's choice into data-ads, so a visitor without JS, who
   could never load an ad anyway, never sees a control that does nothing. */
#ads-toggle { display: none; }
:root[data-ads] #ads-toggle {
  display: inline-flex; align-items: center; gap: .45rem;
  font-size: .88rem; font-weight: 600; line-height: 1.5; padding-inline: .6rem;
}
.ads-toggle__track {
  position: relative; flex: none; width: 1.85rem; height: 1.05rem;
  border: 1.5px solid var(--text-muted); border-radius: 999px;
}
.ads-toggle__track::after {
  content: ""; position: absolute; top: 50%; left: .14rem;
  width: .62rem; height: .62rem; margin-top: -.31rem; border-radius: 50%;
  background: var(--text-muted);
  transition: transform .2s ease, background-color .2s ease;
}
:root[data-ads="on"] .ads-toggle__track { background: var(--accent-brand); border-color: var(--accent-brand); }
:root[data-ads="on"] .ads-toggle__track::after { transform: translateX(.8rem); background: var(--on-accent); }
@media (prefers-reduced-motion: reduce) { .ads-toggle__track::after { transition: none; } }
/* The links push right and the toggles follow them. On a phone the links
   fill a row of their own, so the toggles sit beside the name instead, where
   the tagline wraps around them rather than running underneath. */
.site-head nav { margin-left: auto; }
@media (max-width: 640px) {
  .site-head {
    display: grid; grid-template-columns: minmax(0, 1fr) auto;
    align-items: start; gap: .75rem .5rem;
  }
  /* A little narrower, so the tagline still fits on one line at 390px. */
  .head-toggles { grid-area: 1 / 2; gap: .4rem; }
  #theme-toggle { padding-inline: .55rem; }
  :root[data-ads] #ads-toggle { padding-inline: .5rem; }
  .site-head nav { grid-column: 1 / -1; margin-left: 0; }
}

.btn {
  display: inline-block; padding: .6rem 1.1rem; font-weight: 600;
  border: 1px solid var(--border-subtle); border-radius: 8px;
  background: var(--surface-card); color: var(--text-primary);
  text-decoration: none; cursor: pointer;
}
.btn:hover { background: var(--surface-hover); }
.btn.primary { background: var(--accent-brand); border-color: var(--accent-brand); color: var(--on-accent); }
.btn.primary:hover { filter: brightness(0.9); background: var(--accent-brand); }
/* White on the light-theme orange is 3.56:1; this shade clears 4.5:1. */
:root[data-theme="light"] .btn.primary { background: #C2410C; border-color: #C2410C; }
:root[data-theme="light"] .btn.primary:hover { background: #C2410C; }
.cta-row { display: flex; gap: .8rem; flex-wrap: wrap; align-items: center; }
.link-plain { font-weight: 600; text-decoration: none; }
.link-plain:hover { text-decoration: underline; }

.mono {
  font-family: var(--font-mono); font-size: .78rem;
  color: var(--text-muted);
}
/* Section headings outrank everything inside a section (cards, folded rows,
   group headings), with the accent tick from motion.css above them. The first
   heading of a section block leaves the spacing to the block. */
:root { --h2-size: clamp(1.45rem, 1.1rem + 1.1vw, 1.85rem); }
.section-head {
  font: 700 var(--h2-size)/1.2 var(--font-sans); letter-spacing: -.015em;
  color: var(--text-primary); text-wrap: balance;
  margin: clamp(2.75rem, 6vw, 4rem) 0 1.1rem;
}
section > .section-head:first-child { margin-top: 0; }
.fine { color: var(--text-muted); font-size: .85rem; }
.tool-meta {
  display: flex; flex-wrap: wrap; gap: .5rem .75rem; align-items: center;
  font-family: var(--font-mono); font-size: .78rem; color: var(--text-muted);
  margin-bottom: .6rem;
}
.tag {
  color: var(--text-primary); border: 1px solid var(--accent-brand);
  border-radius: 4px; padding: 0 .35rem;
}

.ad-slot {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  min-height: 110px;
  padding: 0.75rem;
  margin-block: 2rem;
  border: 1px solid var(--border-subtle);
  border-radius: 8px;
  background: var(--surface-card);
}
.ad-slot--leaderboard { min-height: 110px; margin-top: 2.5rem; }
.ad-slot--footer { height: 90px; margin: 1.5rem auto 0; }
.ad-slot--inline { min-height: 110px; margin-block: 2rem; }
.ad-slot .adsbygoogle { display: block; width: 100%; }
.ad-slot:has(.adsbygoogle[data-ad-status="unfilled"]) { display: none; }
/* Auto ads puts its units in its own containers and leaves an unfilled one
   at full height, an empty gap in the page. Google's help article "Hide
   unfilled ad units" recommends this rule. */
.google-auto-placed:has(> .adsbygoogle[data-ad-status="unfilled"]) { display: none !important; }
.ad-slot--blocked {
  border: 1px solid var(--border-subtle); border-radius: var(--radius);
  background: var(--surface-card); min-height: 80px;
}
/* Ads are off until the visitor turns them on, and then a slot shows one
   quiet line where the ad would be instead of the ad unit, which AdSense never
   fills while it is display:none. With no JS there is no line either, since
   ads could not be turned on, so the slot goes away entirely. */
.ad-slot__off { display: none; }
:root:not([data-ads]) .ad-slot { display: none; }
:root[data-ads="off"] .ad-slot {
  min-height: 0; padding: .6rem 1rem; border-style: dashed; background: none;
}
:root[data-ads="off"] .ad-slot > :not(.ad-slot__off) { display: none; }
:root[data-ads="off"] .ad-slot__off {
  display: block; margin: 0; font-size: .85rem; color: var(--text-muted); text-align: center;
}
.link-btn {
  font: inherit; padding: 0; border: 0; background: none; cursor: pointer;
  color: var(--accent-interactive); text-decoration: underline; text-underline-offset: .2em;
}
.link-btn:hover { color: var(--accent-primary); }
.ad-slot__label {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.04em;
  color: var(--text-muted);
  margin: 0;
}
.site-foot .ad-slot { max-width: 1100px; }

.page-hero { padding: 2rem 0 1rem; max-width: 60ch; }
.page-hero h1 { font-size: clamp(1.7rem, 3.6vw, 2.6rem); line-height: 1.18; letter-spacing: -.02em; }
.lede { font-size: 1.08rem; color: var(--text-muted); }
/* ===== Stats line (rendered near top of page, not buried in footer) ===== */
.stats-line {
  font-family: var(--font-mono); font-size: .82rem; color: var(--text-muted);
  margin: 0 0 1.5rem; padding: .6rem 1rem; border: 1px solid var(--border-subtle);
  border-radius: 8px; background: var(--surface-card); display: inline-block;
}
.stats-line strong { color: var(--text-primary); font-weight: 600; }

.prose { max-width: 68ch; }
.prose h1 { font-size: clamp(1.7rem, 3.6vw, 2.4rem); line-height: 1.18; letter-spacing: -.02em; }
.prose h2 { font-size: var(--h2-size); line-height: 1.2; letter-spacing: -.015em; margin-top: 2.75rem; }
.prose h3 { font-size: 1.15rem; margin-top: 1.8rem; }

.project-grid {
  display: grid; gap: 1.5rem;
  grid-template-columns: repeat(auto-fill, minmax(18rem, 1fr));
}
.portfolio-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 2rem;
}
@media (min-width: 768px) {
  .portfolio-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 1200px) {
  .portfolio-grid { grid-template-columns: repeat(3, 1fr); }
}
.ad-status {
  font: .75rem var(--font-mono);
  color: var(--text-muted);
  margin: .25rem 0 0;
}
.adblock-notice {
  font-size: .85rem;
  color: var(--text-muted);
  margin: .25rem 0 0;
  max-width: 60ch;
  text-align: center;
}
.project-card {
  display: grid; overflow: hidden;
  border: 1px solid var(--border-subtle); border-radius: var(--radius);
  background: var(--surface-card);
}
.project-card__media {
  background: var(--surface-card);
  border-bottom: 1px solid var(--border-subtle);
  overflow: hidden;
  aspect-ratio: 16 / 9;
}
.project-card__media img { display: block; width: 100%; height: 100%; object-fit: contain; }
.project-card__body { display: grid; gap: .5rem; align-content: start; padding: 1rem; }
/* Groups for the spotlight card's two text columns; everywhere else they
   step aside and their children sit straight in the body's grid. */
.project-card__main, .project-card__aside { display: contents; }
.project-card__name { font-size: 1.15rem; margin: 0; }
.project-card__name a { color: var(--text-primary); text-decoration: none; }
.project-card__name a:hover { color: var(--accent-primary); text-decoration: underline; }
.project-card__tagline { margin: 0; color: var(--text-muted); }
.project-card__desc { margin: 0; }
.project-card__links { display: flex; flex-wrap: wrap; gap: .5rem 1.1rem; margin: 0; font-weight: 600; }
.project-card__links a { text-decoration: none; }
.project-card__links a:hover { text-decoration: underline; }
.project-card--full .project-card__name { font-size: 1.45rem; }

@media (min-width: 900px) {
  .project-card__media {
    border-bottom: 1px solid var(--border-subtle);
    aspect-ratio: 16 / 9;
  }
  .project-card__media img {
    width: 100%; height: 100%; object-fit: contain;
  }
  .project-card__body { padding: 1rem; }
}
/* The spotlight card (the flagship, on the home and portfolio pages) shows
   the whole screenshot, full width at 16:9 and uncropped, because the
   picture is what tells you what the app does. On wide screens the text
   goes in two columns under it: the story on the left, tags and links on
   the right. */
@media (min-width: 900px) {
  .project-card--full .project-card__media img { object-fit: cover; }
  .project-card--full .project-card__body {
    grid-template-columns: minmax(0, 1.7fr) minmax(13rem, 1fr);
    gap: 2.25rem; padding: 1.4rem 1.6rem 1.6rem;
  }
  .project-card--full .project-card__main,
  .project-card--full .project-card__aside { display: grid; gap: .5rem; align-content: start; }
  .project-card--full .project-card__aside { gap: 1rem; padding-top: .3rem; }
  .project-card--full .project-card__links { flex-direction: column; gap: .55rem; }
}

/* ===== Click-to-zoom image overlay (static/js/lightbox.js) =====
   Any image on the site opens here at full size. The overlay is built by the
   script on first click, so nothing in the templates carries it. */
.zoomable { cursor: zoom-in; }
html.lightbox-open { overflow: hidden; }
.lightbox {
  position: fixed; inset: 0; z-index: 100;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: .75rem;
  padding: 1.5rem;
  background: rgba(6, 8, 12, .94);
}
.lightbox[hidden] { display: none; }
.lightbox__img {
  max-width: min(94vw, 1600px); max-height: 82vh;
  width: auto; height: auto; object-fit: contain;
  border-radius: 10px; background: var(--surface-card);
  box-shadow: 0 18px 60px rgba(0, 0, 0, .55);
  cursor: default;
}
.lightbox__caption {
  margin: 0; max-width: 70ch; text-align: center;
  font: .82rem var(--font-mono); color: #e8eaee;
}
.lightbox__caption[hidden] { display: none; }
.lightbox__close {
  position: absolute; top: .75rem; right: .75rem;
  width: 2.6rem; height: 2.6rem; padding: 0;
  font-size: 1.5rem; line-height: 1;
  border: 1px solid rgba(255, 255, 255, .35); border-radius: 50%;
  background: rgba(0, 0, 0, .55); color: #fff; cursor: pointer;
}
.lightbox__close:hover { background: rgba(255, 255, 255, .2); }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { scroll-behavior: auto !important; transition: none !important; }
}

/* ===== Icons (inline SVG, see the i-* partials in layout.html) ===== */
.icon {
  width: 1.15em; height: 1.15em; flex: none;
  fill: none; stroke: currentColor; stroke-width: 2;
  stroke-linecap: round; stroke-linejoin: round;
  vertical-align: -.2em;
}
.btn .icon, .shots__btn .icon { transition: transform .25s var(--ease, ease); }
.btn:hover .icon-nudge-x, .btn:hover .icon { transform: none; }

/* ===== The short version, and read-more =====
   Long pages open with a TL;DR box, and long sections fold down to their lead
   sentences so a visitor can scan the page and open only what they want. All
   native <details>: it works with no JS, keyboards and screen readers already
   know it, and search engines still read the folded text. */
.tldr {
  max-width: 68ch; margin: 0 0 2rem; padding: 1rem 1.25rem;
  background: var(--surface-card); border: 1px solid var(--border-subtle);
  border-left: 3px solid var(--accent-brand); border-radius: var(--radius);
}
.tldr__label {
  margin: 0 0 .45rem; font: 600 .75rem var(--font-mono);
  letter-spacing: .08em; text-transform: uppercase; color: var(--accent-brand);
}
.tldr ul { display: grid; gap: .35rem; margin: 0; padding-left: 1.1rem; }
.tldr__jump { display: flex; flex-wrap: wrap; gap: .4rem 1.2rem; margin: .8rem 0 0; font-weight: 600; }
.tldr__jump a { text-decoration: none; }
.tldr__jump a:hover { text-decoration: underline; }

/* A folded section is a list of lead sentences; a chevron says each opens. */
.fold { max-width: 72ch; margin: 0 0 1.5rem; padding: 0; list-style: none; }
.fold details { border-top: 1px solid var(--border-subtle); }
.fold summary, .faq summary, .more > summary, .cap-fold > summary { cursor: pointer; list-style: none; }
.fold summary::-webkit-details-marker, .faq summary::-webkit-details-marker, .more > summary::-webkit-details-marker,
.cap-fold > summary::-webkit-details-marker { display: none; }
.fold summary { display: flex; gap: .7rem; align-items: baseline; padding: .75rem 0; font-weight: 600; }
.fold summary:hover { color: var(--accent-primary); }
.fold summary::before, .faq summary::before, .more > summary::before, .cap-fold > summary::before {
  content: ""; flex: none; width: .45em; height: .45em;
  border-right: 2px solid var(--accent-interactive); border-bottom: 2px solid var(--accent-interactive);
  transform: translateY(-.1em) rotate(-45deg); transition: transform .2s var(--ease, ease);
}
.fold details[open] > summary::before, .faq details[open] > summary::before, .more[open] > summary::before,
.cap-fold[open] > summary::before { transform: translateY(-.25em) rotate(45deg); }
.fold details > :not(summary) { margin: 0 0 1rem 1.15rem; }

/* "More about it" under a card's short version. */
.more > summary {
  display: inline-flex; gap: .55rem; align-items: baseline;
  font-weight: 600; color: var(--accent-interactive);
}
.more > summary:hover { text-decoration: underline; }
.more[open] > summary { margin-bottom: .5rem; }

/* A folded group of feature cards: its heading plus the card titles, so the
   folded page still says what is inside. */
.cap-fold { max-width: 900px; margin: 0; border-bottom: 1px solid var(--border-subtle); }
.cap-fold > summary {
  display: grid; grid-template-columns: auto 1fr; column-gap: .7rem; row-gap: .2rem;
  align-items: baseline; padding: .85rem 0;
}
.cap-fold > summary .cap-group { margin: 0; padding: 0; border: 0; }
.cap-fold__peek { grid-column: 2; font-size: .88rem; color: var(--text-muted); }
.cap-fold > summary:hover .cap-group { color: var(--accent-primary); }
.cap-fold[open] > summary { margin-bottom: .6rem; }
.cap-fold[open] .cap-fold__peek { display: none; } /* the cards below say it */
.cap-fold > .feature-grid { margin-bottom: 1.2rem; }
/* An entry too short or too run-on to fold sits in the list as one line. */
.fold__line { margin: 0; padding: .75rem 0 .75rem 1.15rem; border-top: 1px solid var(--border-subtle); }
/* Community verdicts: the name and a one-line verdict, the story folded. */
.reputation.fold { padding: 0; }
.reputation.fold details { border-top: 0; }
.reputation.fold summary { padding: .35rem 0; flex-wrap: wrap; column-gap: .6rem; }
.reputation.fold .verdict { font-weight: 400; color: var(--text-muted); }
.faq summary { display: flex; gap: .7rem; align-items: baseline; }
.faq summary:hover { color: var(--accent-primary); }
.faq details > :not(summary) { margin-left: 1.15rem; }

/* Cards in a row stretch to the tallest; the spare height goes below the
   text, so every image and title lines up across the row. */
.project-card--compact { grid-template-rows: auto 1fr; }

/* Reading pages start below the header with the same air as the tool pages. */
main.prose > h1:first-child { margin-top: clamp(2rem, 5vw, 3rem); }
