/* ==========================================================================
   Design tokens. This is the one place to edit colour and spacing.
   --accent may be changed to any hex, but test/contrast.test.js fails if it drops below 4.5:1 against --paper.
   ========================================================================== */
:root {
  --paper: #fafafa;
  --ink: #1e00df;
  --accent: #1e00df ; /* placeholder: owner may change; must stay >= 4.5:1 on --paper */
  /* --font: "Inconsolata", ui-monospace, Menlo, Consolas, monospace; */
  --font: monospace;
  --page-margin: clamp(1rem, 5vw, 4rem);
  --column-max: 80rem;
  --measure: 50rem;
  --grid-gap: 1rem;
  --block-gap: 2rem;
  color-scheme: light;
}

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

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font);
  font-size: 0.9375rem;
  font-weight: 400;
  line-height: 1.4;
}

a,
a:visited {
  color: var(--accent);
  text-decoration: underline;
  text-decoration-thickness: 0.08em;
  text-underline-offset: 0.2em;
}

/* the current nav or filter item: black and bold, still underlined. Same specificity as the
   rule above, so it must stay after it. */
a[aria-current] {
  /* color: var(--ink); */
  /* font-weight: 700; */
  text-decoration: underline;
  text-decoration-thickness: 0.08em;
  text-underline-offset: 0.2em;
}

/* ==========================================================================
   Keyboard: focus ring and skip link
   ========================================================================== */
:is(a, video, iframe, summary, button, [tabindex]):focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 3px;
}

/* the programmatic skip target only; every other element keeps its ring.
   (0,2,1) beats the (0,2,0) ring rule above, so the whole content area gets no ring. */
main[tabindex="-1"]:focus,
main[tabindex="-1"]:focus-visible {
  outline: none;
}

/* inside the black dialog the accent ring is only about 2.2:1; white on black is 21:1 */
dialog.lightbox :focus-visible {
  outline-color: var(--paper);
}

.skip-link {
  position: absolute;
  top: 0;
  left: 0;
  padding: 0.75rem 1rem;
  background: var(--ink);
  color: var(--paper);
  font-weight: 700;
  text-decoration: none;
  clip-path: inset(50%);
  white-space: nowrap;
}

.skip-link:focus,
.skip-link:focus-visible {
  clip-path: none;
}

.skip-link:visited {
  color: var(--paper);
}

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

/* ==========================================================================
   Page column: one wide, left-aligned column on a named-line grid.
   full-start..full-end is the viewport; content-start..content-end is the
   column. Full-bleed blocks use the outer lines, so no 100vw tricks and no
   horizontal scrollbar.
   ========================================================================== */
body > header,
main {
  display: grid;
  grid-template-columns:
    [full-start] minmax(var(--page-margin), 1fr)
    [content-start] min(100% - 2 * var(--page-margin), var(--column-max))
    [content-end] minmax(var(--page-margin), 1fr)
    [full-end];
}

body > header > *,
main > * {
  grid-column: content;
  margin: 0;
  min-width: 0;
}

main > .full {
  grid-column: full;
}

body > header {
  padding-block: 1rem;
}

/* site header: owner name then the nav, wrapping instead of scrolling */
.header-row {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: baseline;
  gap: 0.5rem 1.5rem;
  margin-bottom: 1rem;
  font-size: 0.9375rem;
  
}

header nav ul {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1.5rem;
  list-style: none;
  margin: 0;
  padding: 0;  
}

header a {
  text-decoration: none;
}

a.site-name {
  font-weight: 700;
  font-size: 1.1rem;
}

/* tag filter row: plain links under the h1, wrapping onto more lines instead of scrolling */
.filters {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1.5rem;
  list-style: none;
  margin: 0;
  padding: 0;
}

/* project pager row: same shape as the filter row */
.pager ul {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1.5rem;
  list-style: none;
  margin: 0;
  padding: 0;
}

.pager a {
  padding-block: 0.25rem;
}

/* a flex item will not shrink below its longest word unless min-width is 0, so a long title wraps instead of widening the page */
.pager li,
.filters li {
  min-width: 0;
}

/* at least 35px tall hit areas */
.site-name,
header nav a,
.filters a {
  padding-block: 0.25rem;
}

main {
  padding-bottom: 4rem;
}

/* ==========================================================================
   Prose: capped at the reading measure, always left-aligned
   ========================================================================== */
main > :is(p:not(.lone-image), h1, h2, h3, ul:not(.cards, .filters), ol, dl, blockquote, pre),
main > :not(.layout, .embed, figure, .lone-image, .cards) :is(p, h1, h2, h3, ul:not(.cards, .filters), ol, dl, blockquote, pre) {
  max-width: var(--measure);
}

h1,
h2,
h3,
p,
li,
dd,
figcaption,
a,
blockquote,
dt,
summary {
  overflow-wrap: break-word;
}

/* anywhere also lowers min-content, so table columns and preformatted tokens shrink to fit; break-word does not */
pre,
td,
th {
  overflow-wrap: anywhere;
}

/* ==========================================================================
   Type scale: four sizes (1.125, 0.875, 1.5, 2.5rem), two weights (400, 700)
   ========================================================================== */
h1,
h2,
h3 {
  font-weight: 700;
  line-height: 1.2;
}

h1 {
  font-size: 1.825rem;
}

h2 {
  font-size: 1.1rem;
}

h3 {
  font-size: 0.9375rem;
}

strong,
b {
  font-weight: 700;
}

em,
i {
  font-style: italic;
}

.meta,
time,
dt,
figcaption {
  font-size: 0.875rem;
}

dt {
  font-weight: 700;
}

dd {
  margin: 0 0 0.5rem;
}

figcaption {
  font-style: italic;
}

code,
pre {
  font-family: ui-monospace, monospace;
  font-size: 0.875rem;
}

pre {
  white-space: pre-wrap;
}

hr {
  border: 0;
  border-top: 2px solid var(--ink);
}

blockquote {
  padding-inline-start: 1rem;
  border-inline-start: 2px solid var(--ink);
}

/* project meta list: two columns when there is room, stacked below */
@media (min-width: 40em) {
  dl {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    gap: 0.5rem 1rem;
  }

  dd {
    margin: 0;
    min-width: 0;
  }
}

/* ==========================================================================
   Vertical rhythm: margin-top only
   ========================================================================== */
main > * + * {
  margin-top: var(--grid-gap);
}

main > h1 + *,
main > dl + * {
  /* margin-top: 1.5rem; */
}

main > :is(.layout, .embed, figure, .lone-image, .cards),
main > :is(.layout, .embed, figure, .lone-image, .cards) + * {
  margin-top: var(--block-gap);
}

/* a block (or card grid) next to a heading: these selectors are one type more specific than the
   block rule above, so the heading gaps (3rem above, 1rem below) win */
main > * + :is(h2, h3),
main > :is(.layout, .embed, figure, .lone-image, .cards) + :is(h2, h3) {
  margin-top: 3rem;
}

main > :is(h2, h3) + *,
main > :is(h2, h3) + :is(.layout, .embed, figure, .lone-image, .cards) {
  margin-top: 1rem;
}

.embed + .embed-link {
  margin-top: 0.5rem;
}

/* project pager: the same 2px ink line as hr; after the block rules above so it wins the specificity tie */
main > .pager {
  margin-top: 3rem;
  padding-top: 1rem;
  border-top: 2px solid var(--ink);
}

/* ==========================================================================
   Images, figures and layout blocks. Images keep their own aspect ratio:
   never cropped, never forced, never clipped.
   ========================================================================== */
picture {
  display: block;
}

img {
  display: block;
  max-width: 100%;
  height: auto;
}

/* the link around a content image that opens it full-size (IMG-05); keeps the global focus ring */
a.zoom {
  display: block;
  cursor: zoom-in;
  text-decoration: none;
}

main figure,
.layout > figure {
  margin: 0;
  min-width: 0;
}

figcaption {
  margin-top: 0.5rem;
}

.layout {
  display: grid;
  gap: var(--grid-gap);
  align-items: start;
}

.layout img {
  width: 100%;
  height: auto;
}

.grid-2 {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

.grid-3 {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

.grid-4 {
  grid-template-columns: repeat(4, minmax(0, 1fr));
}

.layout.full {
  grid-template-columns: minmax(0, 1fr);
}

/* captions in a full-bleed block line up with the text column */
.full figcaption {
  padding-inline-start: max(var(--page-margin), calc((100% - var(--column-max)) / 2));
  padding-inline-end: var(--page-margin);
}

/* ==========================================================================
   Card grid: one card per project on /work/, tag pages and the homepage.
   Covers are the only cropped images on the site (4:3, centred).
   ========================================================================== */
.cards {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--block-gap) var(--grid-gap);
  align-items: start;
}

.cards img {
  display: block;
  width: 100%;
  height: auto;
  /* the one allowed crop: every cover shows at the same 4:3 shape so card rows line up */
  aspect-ratio: 4 / 3;
  object-fit: cover;
  object-position: center;
}

a.card {
  display: block;
  color: inherit;
  text-decoration: none;
}

.card-title {
  display: block;
  margin-top: 0.5rem;
  /* font-weight: 700; */
  color: var(--accent);
  /* text-decoration: underline; */
  text-decoration-thickness: 0.08em;
  text-underline-offset: 0.2em;
  font-style: italic;
}

.card-date {
  display: block;
  color: var(--ink);
  text-decoration: none;
}

/* collapse with CSS only; custom properties cannot be used in media queries */
@media (max-width: 59.99em) {
  .grid-3,
  .grid-4,
  .cards {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 39.99em) {
  .grid-2,
  .grid-3,
  .grid-4,
  .cards {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* ==========================================================================
   Embeds: reserved ratios so nothing jumps while loading
   ========================================================================== */
.embed iframe {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  border: 0;
}

.embed video {
  display: block;
  width: 100%;
  height: auto;
  background: var(--ink);
}

.embed.itch {
  max-width: calc(var(--w) * 1px);
}

.embed.itch iframe {
  aspect-ratio: var(--w) / var(--h);
}

.embed.itch.full {
  max-width: none;
}

/* ==========================================================================
   Lightbox: one opaque, full-viewport ink stage opened by lightbox.js. It never animates and never
   crops the image; a long caption scrolls the dialog, not the page behind it.
   ========================================================================== */
dialog.lightbox {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  max-width: none;
  max-height: none;
  margin: 0;
  border: 0;
  padding: var(--grid-gap) var(--page-margin);
  background: var(--ink);
  color: var(--paper);
  overflow: auto;
  overscroll-behavior: contain;
}

dialog.lightbox::backdrop {
  background: var(--ink);
}

/* grid only while open, or the closed dialog would show */
dialog.lightbox[open] {
  display: grid;
  justify-items: center;
  align-content: start;
  gap: var(--grid-gap);
}

.lightbox-close {
  justify-self: end;
  font: inherit;
  font-weight: 700;
  background: var(--paper);
  color: var(--ink);
  border: 2px solid var(--paper);
  border-radius: 0;
  padding: 0.75rem 1rem;
}

.lightbox figure {
  margin: 0;
  width: fit-content;
  max-width: 100%;
}

/* fits the viewport, never cropped, never upscaled */
.lightbox img {
  display: block;
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: calc(100dvh - 10rem);
}

.lightbox figcaption {
  margin-top: 0.5rem;
  text-align: left;
}

/* ==========================================================================
   Reduced motion: nothing on this site moves, this guard keeps it that way
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation: none !important;
    transition: none !important;
    scroll-behavior: auto !important;
  }
}
