/* stephenmotherwell.com — mobile-first, no dependencies. */

:root {
  /* Hero field. Set together with the ball palette — see [data-palette] below,
     which balls.js stamps on <html> from ?palette=. */
  --field: #bdbdbd;          /* ball field background */
  --ink: #101010;            /* hero text on the field */
  --ink-muted: #4a4a4a;
  --page: #0d0d0d;
  --page-ink: #f2f2f2;
  --page-ink-muted: #a8a8a8;
  --accent: #42dca3;
  --accent-deep: #1d9b6c;
  --rule: rgba(255, 255, 255, 0.14);

  --measure: 62ch;
  --gutter: clamp(1.25rem, 5vw, 4rem);
  --shell: 72rem;

  --font: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}

/* Field and hero ink move together with the ball palette, so a dark ball set
   is never rendered against the light field. */
:root[data-palette="dark"] {
  --field: #151515;
  --ink: #f4f4f4;
  --ink-muted: #a0a0a0;
}

:root[data-palette="mint"] {
  --field: #bdbdbd;
  --ink: #101010;
  --ink-muted: #4a4a4a;
}

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

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

body {
  margin: 0;
  font-family: var(--font);
  font-size: 1rem;
  line-height: 1.6;
  color: var(--page-ink);
  background: var(--page);
  -webkit-font-smoothing: antialiased;
}

a { color: var(--accent); text-decoration-thickness: 1px; text-underline-offset: 0.2em; }
a:hover { color: var(--accent-deep); }

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

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

/* ---------- site header ---------- */

.masthead {
  position: absolute;
  inset-block-start: 0;
  inset-inline: 0;
  z-index: 2;
  padding-block: clamp(1rem, 2.5vw, 1.75rem);
}

.masthead__inner {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem 1.5rem;
  align-items: baseline;
  justify-content: space-between;
}

.wordmark {
  font-size: clamp(0.95rem, 2vw, 1.05rem);
  font-weight: 600;
  letter-spacing: 0.01em;
  color: var(--ink);
  text-decoration: none;
}
.wordmark:hover { color: var(--ink); opacity: 0.7; }

.sitenav {
  display: flex;
  gap: clamp(1rem, 3vw, 2rem);
  font-size: 0.9375rem;
}

.sitenav a {
  color: var(--ink-muted);
  text-decoration: none;
  padding-block: 0.25rem;
  border-block-end: 1px solid transparent;
}
.sitenav a:hover,
.sitenav a[aria-current="page"] {
  color: var(--ink);
  border-block-end-color: currentColor;
}

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

.hero {
  position: relative;
  isolation: isolate;
  min-height: 100vh;          /* fallback */
  min-height: 100svh;         /* excludes mobile browser chrome */
  display: grid;
  align-content: center;
  overflow: hidden;
  background: var(--field);
  color: var(--ink);
}

/* The canvas is absolutely positioned inside the hero, NOT fixed — it scrolls
   away with the section rather than following the viewport. */
#ballfield {
  position: absolute;
  inset: 0;
  z-index: 0;
  display: block;
  width: 100%;
  height: 100%;
  /* Split the gesture by direction rather than choosing between a usable field
     and a scrollable page. pan-y leaves vertical scrolling to the browser, so a
     visitor can always swipe away; horizontal drags are never scrolls on this
     page, so those reach the canvas untouched and drive the field. Holding
     still isn't a scroll either, so hold-to-flare keeps working. */
  touch-action: pan-y;
}

.hero__content {
  position: relative;
  z-index: 1;
  /* Lets the cursor reach the canvas through the copy. Costs text selection
     here, which is fine for a headline. Links opt back in below. */
  pointer-events: none;
}

.hero__content a { pointer-events: auto; }

.hero__name {
  margin: 0;
  font-size: clamp(2.25rem, 8vw, 4.5rem);
  line-height: 1.05;
  font-weight: 700;
  letter-spacing: -0.02em;
}

.hero__line {
  margin: 1rem 0 0;
  max-width: 34ch;
  font-size: clamp(1.05rem, 2.6vw, 1.5rem);
  line-height: 1.4;
  color: var(--ink-muted);
  text-wrap: balance;
}

.hero__hint {
  margin: 2.5rem 0 0;
  font-size: 0.875rem;
  color: var(--ink-muted);
  opacity: 0.8;
}

/* The hint has to match the input the visitor actually has. Telling a phone to
   move a cursor is the kind of detail that makes a site feel unmaintained. */
.hint--coarse { display: none; }
@media (pointer: coarse) {
  .hint--fine { display: none; }
  .hint--coarse { display: inline; }
}

.hero__scroll {
  position: absolute;
  inset-block-end: clamp(1.5rem, 4vh, 2.5rem);
  inset-inline-start: 50%;
  translate: -50% 0;
  z-index: 1;
  display: grid;
  place-items: center;
  width: 2.75rem;
  height: 2.75rem;
  color: var(--ink);
  border-radius: 50%;
  text-decoration: none;
  transition: background-color 0.2s ease, translate 0.2s ease;
}
.hero__scroll:hover {
  color: var(--ink);
  background: rgba(0, 0, 0, 0.08);
}
.hero__scroll svg { width: 1.5rem; height: 1.5rem; }

/* ---------- sections ---------- */

.section {
  padding-block: clamp(4rem, 12vh, 8rem);
  border-block-start: 1px solid var(--rule);
}

.section__label {
  margin: 0 0 0.75rem;
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--accent);
}

.section__title {
  margin: 0 0 1.5rem;
  font-size: clamp(1.75rem, 4.5vw, 2.5rem);
  line-height: 1.15;
  font-weight: 700;
  letter-spacing: -0.015em;
}

/* Accent-coloured line sitting under a section heading. Same colour as
   .section__label but NOT uppercased or letter-spaced — those work on a
   one-word label and wreck a full sentence. */
.section__subtitle {
  margin: -0.75rem 0 2rem;
  max-width: 46ch;
  font-size: clamp(1.0625rem, 1.8vw, 1.25rem);
  line-height: 1.45;
  font-weight: 500;
  color: var(--accent);
  text-wrap: pretty;
}

/* ---------- button ---------- */

.btn {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  margin-block-start: 1.75rem;
  padding: 0.8rem 1.4rem;
  border: 1px solid var(--accent);
  border-radius: 0.5rem;
  color: var(--accent);
  font-size: 0.9375rem;
  font-weight: 600;
  text-decoration: none;
  transition: background-color 0.2s ease, color 0.2s ease;
}
.btn:hover,
.btn:focus-visible {
  background: var(--accent);
  color: #0d0d0d;
}
.btn svg { width: 1.15rem; height: 1.15rem; flex: none; }

.prose {
  max-width: var(--measure);
  color: var(--page-ink-muted);
  font-size: clamp(1rem, 1.6vw, 1.125rem);
}
.prose > * + * { margin-block-start: 1.25rem; }
.prose strong { color: var(--page-ink); font-weight: 600; }

.placeholder {
  border: 1px dashed var(--rule);
  border-radius: 0.5rem;
  padding: 1.25rem;
  color: var(--page-ink-muted);
  font-size: 0.9375rem;
}

.sitefoot {
  padding-block: 3rem;
  border-block-start: 1px solid var(--rule);
  color: var(--page-ink-muted);
  font-size: 0.875rem;
}

/* ---------- /play ---------- */

.is-play { overflow: hidden; }              /* the game owns the viewport */

.masthead--dark .wordmark,
.masthead--dark .sitenav a { color: var(--page-ink-muted); }
.masthead--dark .wordmark:hover,
.masthead--dark .sitenav a:hover,
.masthead--dark .sitenav a[aria-current="page"] { color: var(--page-ink); }

.play {
  position: relative;
  height: 100vh;
  height: 100svh;
  background: #000;
}

#gamefield {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  touch-action: none;                       /* pointer drives the ship */
}

.play__hud {
  position: absolute;
  inset-block-end: clamp(1rem, 4vh, 2.5rem);
  inset-inline: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1.5rem;
  align-items: baseline;
  justify-content: space-between;
  padding-inline: var(--gutter);
  pointer-events: none;
  color: var(--page-ink-muted);
  font-size: 0.875rem;
}

.play__note { margin: 0; }

.play__score {
  margin: 0;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.play__score span { color: #bfff00; font-weight: 600; }

/* ---------- page header for interior pages ---------- */

.page-head {
  padding-block: clamp(6rem, 14vh, 9rem) clamp(2rem, 5vh, 3rem);
}

/* A head with no lede under it loses the ~75px that paragraph was contributing,
   which leaves the title sitting on top of whatever follows. Restore it here
   rather than on a modifier class, so a page written without a lede is spaced
   correctly without anyone having to remember a class name. */
.page-head:not(:has(.page-head__lede)):not(:has(.backlink)) {
  padding-block-end: clamp(4.5rem, 9vh, 6.5rem);
}

.page-head__kicker {
  margin: 0 0 0.85rem;
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--accent);
}

.page-head__title {
  margin: 0;
  font-size: clamp(2rem, 6vw, 3.25rem);
  line-height: 1.05;
  font-weight: 700;
  letter-spacing: -0.025em;
}

.page-head__lede {
  margin: 1rem 0 0;
  max-width: 48ch;
  font-size: clamp(1.05rem, 2vw, 1.25rem);
  color: var(--page-ink-muted);
  text-wrap: pretty;
}

.backlink {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  margin-block-end: 1.5rem;
  font-size: 0.9375rem;
  color: var(--page-ink-muted);
  text-decoration: none;
}
.backlink:hover { color: var(--accent); }
.backlink::before { content: "\2190"; }

/* ---------- portfolio index ---------- */

.cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(20rem, 100%), 1fr));
  gap: clamp(1.25rem, 3vw, 2rem);
  padding-block-end: clamp(4rem, 10vh, 7rem);
  list-style: none;
  margin: 0;
  padding-inline: 0;
}

.card { display: flex; }

.card__link {
  display: flex;
  flex-direction: column;
  width: 100%;
  color: inherit;
  text-decoration: none;
  border: 1px solid var(--rule);
  border-radius: 0.75rem;
  overflow: hidden;
  background: #131313;
  transition: border-color 0.2s ease, transform 0.2s ease;
}
.card__link:hover,
.card__link:focus-visible {
  border-color: var(--accent);
  transform: translateY(-2px);
  color: inherit;
}

.card__media {
  aspect-ratio: 16 / 10;
  overflow: hidden;
  background: #1a1a1a;
  border-block-end: 1px solid var(--rule);
}
.card__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Anchor the crop to the top. Card images are 16:10 but source screenshots
     are often much taller — a centred crop lands on an arbitrary mid-page band,
     while the top shows the header and hero, which is what identifies it. */
  object-position: center top;
  display: block;
  transition: scale 0.3s ease;
}
.card__link:hover .card__media img { scale: 1.02; }

/* Body fills the remaining card height so .card__meta can pin to the bottom.
   Cards in a row are equal height (grid stretch), so the meta line lands on the
   same baseline across the row no matter how long each description runs. */
.card__body {
  flex: 1;
  display: flex;
  flex-direction: column;
  padding: 1.1rem 1.25rem 1.35rem;
}

.card__title {
  margin: 0 0 0.4rem;
  font-size: 1.125rem;
  font-weight: 650;
  letter-spacing: -0.01em;
}

.card__desc {
  margin: 0;
  color: var(--page-ink-muted);
  font-size: 0.9375rem;
  line-height: 1.5;
}

.card__meta {
  margin: 0;
  padding-block-start: 0.75rem;
  margin-block-start: auto; /* pushes the line to the bottom of the body */
  font-size: 0.8125rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--accent);
}

/* ---------- portfolio item ---------- */

.item__body { max-width: var(--measure); }

.item__body ul {
  margin: 0;
  padding-inline-start: 1.1rem;
  color: var(--page-ink-muted);
  font-size: clamp(1rem, 1.6vw, 1.125rem);
}
.item__body li { margin-block-end: 0.4rem; }
.item__body li::marker { color: var(--accent); }
.item__body .lead-in { margin-block-end: 0.85rem; }
.item__body > * + * { margin-block-start: 1.35rem; }
.item__body p { color: var(--page-ink-muted); font-size: clamp(1rem, 1.6vw, 1.125rem); }

.item__facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
  gap: 1.25rem 2rem;
  margin-block: 2.5rem;
  padding-block: 1.5rem;
  border-block: 1px solid var(--rule);
}
.item__facts dt {
  font-size: 0.75rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--accent);
}
.item__facts dd { margin: 0.35rem 0 0; font-size: 0.9375rem; }

/* Masonry via multi-column. Screenshots arrive at wildly different shapes —
   tall pages, wide consoles, panoramic wireframe comparisons — so nothing is
   cropped to a common ratio. Each tile keeps its own proportions and the
   columns simply fill unevenly. Reading order runs down a column rather than
   across a row, which is the accepted trade for not mangling the images. */
.gallery {
  columns: 1;
  column-gap: clamp(0.75rem, 2vw, 1.25rem);
  margin: 0;
  padding: 0 0 clamp(4rem, 10vh, 7rem);
  list-style: none;
}

@media (min-width: 48rem) { .gallery { columns: 2; } }
@media (min-width: 90rem) { .gallery { columns: 3; } }

/* Uniform-ratio galleries. Masonry exists to protect mixed aspect ratios; a set
   where every capture shares one ratio has none to protect, and plain column
   counts make the tile size lurch with viewport — one column on a 600px screen
   turned a phone capture into a 1166px-tall poster. These variants drop out of
   multicol into a grid with a floor and a ceiling on tile width, so a capture
   stays near its natural reading size at every viewport. Detail lives in the
   lightbox, so the tile only has to carry the shape of the screen. */
.gallery--phone,
.gallery--wide {
  columns: auto;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(var(--tile-min), 100%), 1fr));
  gap: clamp(0.75rem, 2vw, 1.25rem);
  justify-items: center;
}
.gallery--phone li,
.gallery--wide li {
  width: 100%;
  max-width: var(--tile-max);
  margin-block-end: 0;
}

/* Handset captures (9:19.5): hold near 208–272px so they read as phones. */
.gallery--phone { --tile-min: 16rem; --tile-max: 17rem; }

/* Authored columns. CSS multicol balances by height and chooses its own break
   points, so it cannot be told which column an image belongs in. When the set
   is composed rather than incidental, the columns are written out instead:
   the same masonry look, but the grouping is the author's. Reading order runs
   down each column and then on to the next, which is also the order it
   collapses to on a narrow screen. */
.gallery--cols {
  display: grid;
  grid-template-columns: 1fr;
  gap: clamp(0.75rem, 2vw, 1.25rem);
  align-items: start;
  columns: auto;
}
/* Two columns, not three. Dense screenshots — a territory map with a sidebar,
   a six-column pipeline, a timeline grid — go sub-pixel at a third of the
   shell. Two puts them near 490px, where the layout is still readable, and the
   page ends up no longer than a three-wide one holding half again as many
   images. Add a third column here if an item's captures are simple enough to
   survive it. */
@media (min-width: 48rem) { .gallery--cols { grid-template-columns: repeat(2, 1fr); } }

.gallery__col {
  display: flex;
  flex-direction: column;
  gap: clamp(0.75rem, 2vw, 1.25rem);
  margin: 0;
  padding: 0;
  list-style: none;
}
.gallery--cols li { margin-block-end: 0; }

/* Desktop captures (16:9): wide enough that the page layout stays legible. */
.gallery--wide  { --tile-min: 22rem; --tile-max: 34rem; }

.gallery li {
  break-inside: avoid;         /* never split a tile across a column break */
  margin-block-end: clamp(0.75rem, 2vw, 1.25rem);
}

.gallery button {
  display: block;
  width: 100%;
  padding: 0;
  border: 1px solid var(--rule);
  border-radius: 0.5rem;
  overflow: hidden;
  background: #1a1a1a;
  cursor: zoom-in;
  transition: border-color 0.2s ease;
}
.gallery button:hover { border-color: var(--accent); }

/* Intrinsic size, no crop. width/height on the <img> reserves the right box
   before the file loads, so the column layout does not jump. */
.gallery img {
  display: block;
  width: 100%;
  height: auto;
}

/* ---------- lightbox ---------- */

.lb-open { overflow: hidden; }

.lb[hidden] { display: none; }   /* `display: grid` below otherwise beats the
                                    hidden attribute, leaving an invisible
                                    overlay swallowing every click on the page */

.lb {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: grid;
  place-items: center;
  padding: clamp(1rem, 5vw, 3rem);
  background: rgba(0, 0, 0, 0.92);
  backdrop-filter: blur(2px);
}

.lb__frame {
  margin: 0;
  max-width: min(100%, 90rem);
  max-height: 100%;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.lb__img {
  max-width: 100%;
  max-height: calc(100svh - 9rem);
  object-fit: contain;
  border-radius: 0.35rem;
}

.lb__caption {
  display: flex;
  gap: 1rem;
  justify-content: space-between;
  align-items: baseline;
  color: var(--page-ink-muted);
  font-size: 0.875rem;
}
.lb__count { font-variant-numeric: tabular-nums; flex: none; }

.lb__close,
.lb__nav {
  position: absolute;
  display: grid;
  place-items: center;
  width: 2.75rem;
  height: 2.75rem;
  border: 1px solid var(--rule);
  border-radius: 50%;
  background: rgba(20, 20, 20, 0.8);
  color: var(--page-ink);
  font-size: 1.5rem;
  line-height: 1;
  cursor: pointer;
  transition: border-color 0.2s ease, background-color 0.2s ease;
}
.lb__close:hover,
.lb__nav:hover { border-color: var(--accent); background: #1a1a1a; }

.lb__close { inset-block-start: clamp(0.75rem, 3vw, 1.5rem); inset-inline-end: clamp(0.75rem, 3vw, 1.5rem); }
.lb__nav--prev { inset-inline-start: clamp(0.5rem, 2vw, 1.5rem); }
.lb__nav--next { inset-inline-end: clamp(0.5rem, 2vw, 1.5rem); }

@media (max-width: 40rem) {
  .lb__nav { inset-block-end: 1rem; }
  .lb__nav--prev { inset-inline-start: 25%; }
  .lb__nav--next { inset-inline-end: 25%; }
  .lb__img { max-height: calc(100svh - 13rem); }
}
