.fact-row {
  display: flex; flex-wrap: wrap; gap: 1rem 2.5rem;
  border-block: 1px solid var(--border-subtle);
  margin: 1.5rem 0 2rem; padding: 1rem 0;
}
.fact { margin: 0; }
.fact dt {
  font: .72rem var(--font-mono); letter-spacing: .1em;
  text-transform: uppercase; color: var(--text-muted);
}
.fact dd { margin: .15rem 0 0; font-weight: 600; }

.feature-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: 1.25rem 1.75rem; list-style: none; padding: 0; margin: 0 0 2.5rem;
  max-width: 900px;
}
.feature-grid li { border-top: 1px solid var(--border-subtle); padding-top: .8rem; }
.feature-grid h3 { font-size: 1rem; margin-bottom: .3rem; }
.feature-grid p { font-size: .92rem; color: var(--text-muted); margin: 0; }

.download-row { display: flex; flex-wrap: wrap; gap: .75rem; margin-bottom: 1rem; }
.download-row .btn { min-width: 12rem; text-align: center; }
.platform-note { margin-bottom: 2rem; }

/* Comparison copy on tool pages: reading measure, not a wall of text. */
.why-copy { max-width: 72ch; }
.why-copy p { margin: 0 0 1rem; }

.faq { max-width: 70ch; }
.faq details { border-top: 1px solid var(--border-subtle); padding: .9rem 0; }
.faq summary { cursor: pointer; font-weight: 600; }
.faq details > *:not(summary) { margin-top: .6rem; }
.faq ul { padding-left: 1.25rem; }

.callout {
  border-left: 3px solid var(--status-ok);
  padding: .2rem 0 .2rem .9rem; margin-bottom: 1.5rem;
  color: var(--text-muted);
}

/* Clearly-labeled stand-in for a video that does not exist yet. Never a
   broken embed: the box holds its 16:9 space and says what will go here. */
.video-placeholder {
  aspect-ratio: 16 / 9;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--surface-card);
  border: 1px solid var(--border-subtle);
  border-radius: 12px;
  color: var(--text-muted);
  font-family: var(--font-mono);
  font-size: 0.85rem;
  /* Keeps the box a modest note rather than a full-width band. */
  max-width: 640px;
  margin-bottom: 2rem;
}

.steps { padding-left: 1.3rem; max-width: 62ch; }
.steps li { margin-bottom: .5rem; }

.changelog { padding-left: 1.3rem; max-width: 68ch; }
.changelog li { margin-bottom: .45rem; }

/* ===== Screenshot carousel (static/js/shots.js) =====
   Scroll-snap does the layout, the swipe and the keyboard geometry. The script
   adds the buttons, the counter and the continuous glide, so with no JS at
   all this is still a scrollable strip of screenshots. While it glides,
   snapping is off (it would pull every step back to a slide), and --glide
   carries the sub-pixel part of the position that scrollLeft rounds away. */
.shots { margin-bottom: 2.5rem; }
.shots__controls { display: flex; align-items: center; gap: .5rem; margin-bottom: .6rem; flex-wrap: wrap; }
.shots__btn {
  padding: .35rem .85rem; font: 600 .9rem var(--font-sans);
  border: 1px solid var(--border-subtle); border-radius: 8px;
  background: var(--surface-card); color: var(--text-primary); cursor: pointer;
}
.shots__btn:hover { background: var(--surface-hover); }
.shots__btn[aria-pressed="false"] { outline: 2px solid var(--accent-interactive); outline-offset: 1px; }
.shots__count {
  margin-left: auto; font: .78rem var(--font-mono); color: var(--text-muted);
}
.shots__hint { font: .8rem var(--font-mono); color: var(--text-muted); margin: 0 0 .85rem; }
.shots__track {
  display: flex; gap: 1rem; margin: 0; padding: 0 0 .6rem;
  list-style: none;
  overflow-x: auto; overflow-y: hidden; overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  scrollbar-width: thin;
}
/* Every screenshot is 16:9 (1440x810), and the frame is exactly the
   picture: the slide's width follows from the height there is room for, so
   no window shape can leave bands beside or above the image. --shot-h is the
   tallest the picture may be; the caption goes under it. */
.shots { --shot-h: 100vh; }
@media (min-width: 1024px) { .shots { --shot-h: min(60vh, 560px); } }
@media (min-width: 641px) and (max-width: 1023px) { .shots { --shot-h: 48vh; } }
.shots__slide {
  flex: 0 0 min(92%, 900px, var(--shot-h) * 16 / 9); margin: 0; scroll-snap-align: center;
  display: flex; flex-direction: column;
}
.shots__slide img {
  display: block; width: 100%; height: auto; aspect-ratio: 16 / 9; object-fit: cover;
  border: 1px solid var(--border-subtle); border-radius: 12px;
  background: var(--surface-card);
  box-shadow: 0 1px 2px rgb(0 0 0 / .12), 0 6px 18px -6px rgb(0 0 0 / .28);
}
.shots--free .shots__track { scroll-snap-type: none; }
.shots__slide { translate: var(--glide, 0px) 0; }
.shots__cap { margin: .55rem 0 0; font: .8rem var(--font-mono); color: var(--text-muted); }
/* Window screenshots (/tools/lofi) are close to square: size the slide to the
   image instead of letterboxing it in a 16:9 box. */
.shots--window .shots__slide { flex-basis: min(92%, 640px); height: auto; aspect-ratio: auto; }
.shots--window .shots__slide img { flex: none; height: auto; aspect-ratio: 1076 / 925; }
/* With the script running there are buttons, keys, swipes and the glide, so
   the scrollbar only adds a bar that crawls along under the pictures. */
.shots--js .shots__track { scrollbar-width: none; }
.shots--js .shots__track::-webkit-scrollbar { display: none; }

/* An empty ad slot reserves no space and shows no label: a labelled empty
   box reads as broken, so the frame only exists when there is an ad. */
.ad-slot:empty { display: none; }
.ad-slot:not(:has(ins, iframe, .adblock-notice, .ad-status)) .ad-slot__label { display: none; }
.ad-slot:not(:has(ins, iframe, .adblock-notice, .ad-status)) { padding: 0; border: 0; margin: 0; min-height: 0; }

/* Footer-only sections used by the community guide page. */
.reputation { max-width: 68ch; }
.reputation li { margin-bottom: .5rem; }
.reputation--good li {
  border-left: 3px solid var(--status-ok);
  padding-left: .8rem;
}
.reputation--bad li {
  border-left: 3px solid var(--status-danger);
  padding-left: .8rem;
}

@media (max-width: 640px) {
  .download-row .btn { flex: 1 1 100%; min-width: 0; }
}

/* ===== Portfolio: stack line, featured project, case study ===== */
.stack-line, .contact-line { margin: 0 0 .6rem; font-size: .95rem; }
.contact-line { font-weight: 600; margin-bottom: 1rem; }
/* One rhythm for every portfolio section. Each block owns its top margin, so
   a section can never sit flush against whatever precedes it, whether that is
   a grid of cards, a boxed case study or the hero. */
.featured-section, .case-study, .projects-section, .writing-section {
  margin-top: clamp(3rem, 7vw, 4.75rem);
}
.featured-section .project-card--full { max-width: 1000px; }
.formerly { margin: .8rem 0 0; font-size: .9rem; color: var(--text-muted); }
.case-study {
  max-width: 1000px; padding: 1.6rem 1.6rem 1.2rem;
  background: var(--surface-card);
  border: 1px solid var(--border-subtle);
  border-left: 6px solid var(--accent-brand);
  border-radius: var(--radius);
}
.case-study h2 { margin: 0 0 .8rem; font-size: var(--h2-size); line-height: 1.2; letter-spacing: -.015em; }
.case-study__kicker {
  margin: 0 0 .3rem; font: .75rem var(--font-mono);
  letter-spacing: .08em; text-transform: uppercase; color: var(--text-muted);
}
.case-study__body { max-width: 68ch; }
.case-study__link { margin: .4rem 0 0; font-weight: 600; }
.writing-section { padding-bottom: 1rem; }
.writing-intro { max-width: 68ch; margin-bottom: 1.5rem; }
.writing-cards {
  display: grid; gap: 1.25rem;
  grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
  max-width: 1000px;
}
.writing-card {
  display: grid; gap: .45rem; align-content: start; padding: 1.25rem 1.3rem;
  text-decoration: none; color: var(--text-primary);
  background: var(--surface-card);
  border: 1px solid var(--border-subtle); border-left: 4px solid var(--accent-brand);
  border-radius: var(--radius);
  transition: transform .25s var(--ease, ease), border-color .2s ease, box-shadow .25s ease;
}
.writing-card:hover {
  transform: translateY(-4px);
  border-color: var(--accent-brand);
  box-shadow: 0 16px 34px -20px rgba(0, 0, 0, .55);
  color: var(--text-primary);
}
.writing-card__kicker {
  font: .72rem var(--font-mono); letter-spacing: .1em;
  text-transform: uppercase; color: var(--text-muted);
}
.writing-card__title { font-size: 1.15rem; font-weight: 600; line-height: 1.3; }
.writing-card__go {
  display: inline-flex; align-items: center; gap: .4rem;
  font-weight: 600; color: var(--accent-interactive);
}
.writing-card__go .icon { transition: transform .25s var(--ease, ease); }
.writing-card:hover .writing-card__go .icon { transform: translateX(5px); }

/* Changelog entries quote identifiers with no break points; wrap them rather than widen the page. */
.changelog li { overflow-wrap: anywhere; }
/* Uniform grid: extra row height goes to the text, so image rows line up across cards. */
.portfolio-grid .project-card { grid-template-rows: auto 1fr; }

/* Demo video: a privacy-friendly YouTube embed that holds its 16:9 box, so
   the page does not jump when the frame loads. */
.video-embed {
  position: relative; aspect-ratio: 16 / 9; max-width: 860px; margin-bottom: 1rem;
  border: 1px solid var(--border-subtle); border-radius: 12px; overflow: hidden;
  background: var(--surface-card);
}
.video-embed iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }

.release-headline { font-size: 1.1rem; font-weight: 600; margin-bottom: .4rem; }
.changelog-group { font-size: 1.12rem; margin: 2rem 0 .4rem; }

/* Buttons that carry an arrow: label and icon on one line, icon nudging the way it points. */
.shots__btn { display: inline-flex; align-items: center; gap: .45rem; }
.shots__btn[data-shots-prev]:hover .icon { transform: translateX(-3px); }
.shots__btn[data-shots-next]:hover .icon { transform: translateX(3px); }
.notes-jump .btn { gap: .5rem; }
.notes-jump .btn:hover .icon { transform: translateY(3px); }

/* Capability groups on the tool pages: a heading over each feature grid. */
.cap-group {
  font: 700 1.12rem var(--font-sans);
  margin: 1.8rem 0 .9rem;
  padding-bottom: .4rem;
  border-bottom: 1px solid var(--border-subtle);
  max-width: 900px;
}
.cap-group:first-of-type { margin-top: 1rem; }
