/* jaredpemberton.com — v1 shared styles
   One accent (#FF4F00): links, hover, active nav underline, project numbers.
   Light theme only: no dark mode, whatever the OS/browser prefers.
   Chromatic aberration (--fringe) only on the home name, live index links
   and project titles. */

:root {
  color-scheme: light;
  --bg: #ffffff;
  --ink: #000000;
  --ink-2: #5c5c5c;       /* captions, meta values */
  --rule: #dcdcdc;
  --accent: #ff4f00;

  --display: "Inter Tight", "Inter", system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
  --body: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
  --mono: "IBM Plex Mono", ui-monospace, Consolas, monospace;   /* mount labels */

  --pad: clamp(20px, 4vw, 48px);
  --max: 1360px;
  --gap: clamp(16px, 2vw, 32px);
  --radius: 14px;
  /* space under the last content block; the footer adds its own top padding */
  --foot-gap: clamp(40px, 6vw, 96px);

  /* chromatic fringe for display titles: in em so it scales with the text */
  --fringe: -0.02em 0 #19d3ff, 0.02em 0 #ff3d7f;
}

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

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

body {
  position: relative;   /* the footer version mark is placed against the page */
  /* a column at least one window tall, so the footer sits at the bottom
     of the window on short pages (Contact, 404) */
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  min-height: 100dvh;
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--body);
  font-size: 16px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}

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

p, ul, dl, dd, figure { margin: 0; }

.wrap {
  max-width: var(--max);
  margin-inline: auto;
  padding-inline: var(--pad);
}
/* centred with auto margins, so say the width outright in the body column */
body > .wrap { width: 100%; }

.visually-hidden {
  position: absolute !important;
  width: 1px; height: 1px;
  overflow: hidden; clip: rect(0 0 0 0);
  white-space: nowrap;
}

.skip {
  position: absolute; left: var(--pad); top: -100px;
  background: var(--ink); color: var(--bg);
  padding: 8px 12px; z-index: 10;
}
.skip:focus { top: 12px; }

/* ---------- Type ---------- */

.display {
  font-family: var(--display);
  font-weight: 900;
  text-transform: uppercase;
  line-height: 0.92;
  letter-spacing: -0.02em;
}

.meta {
  font-size: 12px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  line-height: 1.3;
}

/* Body links: black text, orange underline, orange on hover.
   (Orange text alone is too low-contrast at 16px on white.) */
a {
  color: inherit;
  text-decoration-line: underline;
  text-decoration-color: var(--accent);
  text-decoration-thickness: 2px;
  text-underline-offset: 0.22em;
}
a:hover { color: var(--accent); }

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

/* ---------- Nav ---------- */

.nav ul {
  display: flex;
  gap: clamp(16px, 2.2vw, 32px);
  list-style: none;
  padding: 0;
}
.nav a {
  text-decoration: none;
  font-weight: 600;
  font-size: 15px;
}
.nav a:hover { color: var(--accent); }
/* current page: black text, 2px orange underline about 5px below */
.nav a[aria-current="page"] {
  color: var(--ink);
  text-decoration-line: underline;
  text-decoration-color: var(--accent);
  text-decoration-thickness: 2px;
  text-underline-offset: 5px;
}
.nav a[aria-current="page"]:hover { color: var(--accent); }

/* ---------- Home header ---------- */

.home-head { padding-top: 28px; }

.name {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 0.2em;
  margin: 0;
  font-size: clamp(44px, 8.4vw, 124px);
  letter-spacing: -0.02em;
  line-height: 0.92;
}

.home-sub {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 24px;
  margin-top: 16px;
}
.roles {
  font-size: 16px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  line-height: 1.5;
  color: var(--ink);
}
.home-sub .nav a { font-weight: 500; font-size: 16px; }

@media (max-width: 759px) {
  .name {
    flex-direction: column;
    gap: 0.04em;
    font-size: clamp(36px, 13vw, 120px);
  }
  .name span:last-child { align-self: flex-end; }
  /* same row as desktop: roles on the left, nav on the right. Both scale
     with the window so they keep fitting side by side. */
  .home-sub { gap: 12px; }
  .roles { font-size: clamp(9.5px, 2.9vw, 16px); line-height: 1.45; }
  .home-sub .nav ul { gap: clamp(10px, 3.4vw, 24px); }
  .home-sub .nav a { font-size: clamp(12.5px, 3.6vw, 16px); }
}

/* Static chromatic aberration, one shared value (--fringe):
   home name, live home index links and project page titles */
.name,
.project-title {
  text-shadow: var(--fringe);
}

/* ---------- Home: project index ---------- */

.work { padding-block: clamp(80px, 12vw, 160px); }

.index {
  list-style: none;
  padding: 0;
  text-align: center;
  /* index size lives on the list so the trim below scales with it */
  font-size: clamp(34px, 6.2vw, 92px);
  line-height: 0.92;
  /* trim the line box below the last item's baseline (about 0.1em) */
  margin-bottom: -0.1em;
}
.index li + li { margin-top: 0; }
.index a,
.index .soon {
  display: inline-block;
  font-size: 1em;
  line-height: 0.92;
  /* reference sample: tracking stays at -0.02em of 16px, not of the big size */
  letter-spacing: -0.32px;
  text-decoration: none;
  padding-block: 0;
}
.index a {
  text-shadow: var(--fringe);
}
/* unfinished projects: listed, but greyed and not links */
.index .soon {
  color: #C4C4BF;
  text-shadow: none;
  cursor: default;
}
.index a:hover,
.index a:focus-visible {
  color: var(--accent);
  text-shadow: none;
}

.index-caption {
  margin-top: 5px;
  line-height: 1;
  text-align: center;
  font-size: 12px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: #767676;
}

/* ---------- Home: project summaries ---------- */

.summaries {
  list-style: none;
  padding: 0;
}
.summaries > li {
  border-top: 1px solid var(--rule);
  padding-block: clamp(40px, 6vw, 96px);
}
.summary {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  column-gap: var(--gap);
  row-gap: 24px;
  align-items: center;
  text-decoration: none;
  color: inherit;
}
.summary:hover { color: inherit; }
.summary-media { grid-column: 1 / span 7; }
/* Preview images fill the mount at one shared shape. */
.summary-media img {
  width: 100%;
  aspect-ratio: 16 / 10;
  object-fit: cover;
  object-position: top;
}
/* keep the left edge; any crop comes off the right */
.summary-media img.crop-right { object-position: left top; }
/* a tall screenshot in a row: keep its top edge, crop from the bottom */
.card img.crop-top { object-position: top; }
/* project cards under a page header (a hub page): the same space a
   section leaves above its content */
.project-head + .summaries-wrap { padding-top: clamp(28px, 3.2vw, 40px); }
/* no light rule straight under a page header's facts row: not on the
   first card of a hub, and not on a section that follows the header */
.project-head + .summaries-wrap .summaries > li:first-child,
.project-head + .story { border-top: 0; }
.summary-text { grid-column: 8 / -1; }
.summaries > li:nth-child(even) .summary-media { grid-column: 6 / -1; order: 2; }
.summaries > li:nth-child(even) .summary-text { grid-column: 1 / span 5; order: 1; }

.summary-num {
  display: block;
  color: var(--accent);
  font-size: 36px;
}
.summary-title {
  margin: 10px 0 0;
  font-size: clamp(40px, 5.4vw, 80px);
}
.summary-sub { margin-top: 6px; font-size: 18px; line-height: 1.35; }
.summary-facts {
  display: flex;
  gap: 32px;
  margin-top: 20px;
  padding-top: 12px;
  border-top: 1px solid var(--ink);
}
.summary-facts dt { margin-bottom: 4px; }
.summary-facts dd { font-size: 15px; }
.summary-blurb {
  margin-top: 16px;
  max-width: 44ch;
  color: var(--ink-2);
  font-size: 15px;
  line-height: 1.5;
}
/* stack capsules on the home cards: the .tags style, a size smaller */
.tags.summary-tags { margin-top: 16px; gap: 6px; }
.tags.summary-tags li { font-size: 12px; padding: 3px 10px; }
.summary-cta {
  display: inline-block;
  margin-top: 20px;
  font-weight: 600;
  text-decoration: underline;
  text-decoration-color: var(--accent);
  text-decoration-thickness: 2px;
  text-underline-offset: 0.22em;
}
.summary:hover .summary-title,
.summary:hover .summary-cta,
.summary:focus-visible .summary-title,
.summary:focus-visible .summary-cta { color: var(--accent); }

@media (max-width: 899px) {
  .summary-media,
  .summary-text,
  .summaries > li:nth-child(even) .summary-media,
  .summaries > li:nth-child(even) .summary-text { grid-column: 1 / -1; }

  /* Stacked: number, title, subtitle and its rule, then the image,
     then Role/Years, blurb and "View project". */
  .summary { display: flex; flex-direction: column; align-items: stretch; gap: 0; }
  .summary-text { display: contents; }
  .summary-num,
  .summary-title,
  .summary-sub { order: 1; }
  .summary-sub {
    padding-bottom: 20px;
    border-bottom: 1px solid var(--ink);
    margin-bottom: 20px;
  }
  .summary-media,
  .summaries > li:nth-child(even) .summary-media { order: 2; }
  .summary-facts,
  .summary-blurb,
  .summary-tags,
  .summary-cta { order: 3; }
  .summary-facts { border-top: 0; padding-top: 0; }
  .summary-cta { align-self: flex-start; }
}

/* ---------- Story grid (About, Contact, case study sections) ---------- */

.story {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  column-gap: var(--gap);
  row-gap: clamp(16px, 1.8vw, 24px);
  padding-block: clamp(28px, 3.2vw, 40px) clamp(24px, 2.9vw, 36px);
  border-top: 1px solid var(--rule);
}
.story > .story-h { grid-column: 1 / span 4; }
.story > .prose { grid-column: 5 / -1; }
.story > .full { grid-column: 1 / -1; }

.story-h {
  margin: 0;
  font-size: clamp(1.9rem, 3.4vw, 3.6rem);
}

.prose { max-width: 62ch; font-size: 17px; }
.prose > * + * { margin-top: 1em; }
.prose ul:not([class]) { padding-left: 1.1em; }
.prose ul:not([class]) li + li { margin-top: 0.4em; }
.prose .lead { font-size: 19px; line-height: 1.45; }

@media (max-width: 899px) {
  .story > .story-h,
  .story > .prose { grid-column: 1 / -1; }
}

/* Project pages: heading in the left third, prose starting exactly one
   third of the content width in. Figures and grids stay full width. */
@media (min-width: 900px) {
  article > .story {
    grid-template-columns: minmax(0, 1fr) minmax(0, 2fr);
    column-gap: 0;
  }
  article > .story > .story-h { grid-column: 1; padding-right: var(--gap); }
  /* the text runs to the right edge of the content, where the mounts end */
  article > .story > .prose { grid-column: 2; max-width: none; }
  article > .story > .full { grid-column: 1 / -1; }
}

/* a block inside a section: small heading, then figures at full width */
.sub-block { margin-top: clamp(28px, 3.4vw, 44px); }
.sub-block > .story-sub { margin-bottom: 16px; }
/* prose that follows a figure, and the figures after it */
.after-fig { margin-top: clamp(20px, 2.4vw, 32px); }
.after-fig + .media-grid { margin-top: clamp(20px, 2.4vw, 32px); }
/* prose on the left edge of the content instead of the prose column */
.story > .prose.left,
article > .story > .prose.left { grid-column: 1 / -1; max-width: 62ch; justify-self: start; }

/* small subheading inside prose (e.g. Acrediant "Why we built it") */
.story-sub {
  margin: 0;
  font-size: 20px;
  line-height: 1;
}
.prose > .story-sub + * { margin-top: 10px; }

/* Acrediant "Phase 1 → MVP": three-step plan as designed text */
.mvp-head {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: baseline;
  gap: 8px 24px;
}
.mvp-note { font-size: 14px; }
.mvp-steps {
  list-style: none;
  padding: 0;
  margin: 20px 0 0;
  display: grid;
  gap: 32px;
}
.mvp-steps > li {
  border-top: 3px solid var(--ink);
  padding-top: 14px;
}
.mvp-num {
  display: block;
  font-size: 56px;
  line-height: 1;
  color: var(--accent);
}
.mvp-steps .story-sub { margin-top: 12px; }
.mvp-items {
  list-style: none;
  padding: 0;
  margin: 12px 0 0;
  font-size: 14px;
  line-height: 1.45;
}
.mvp-items li {
  padding-block: 8px;
  border-top: 1px solid var(--rule);
}
.mvp-close {
  margin-top: 24px;
  padding-top: 14px;
  border-top: 1px solid var(--rule);
  font-size: 14px;
}
/* a step shown as screens: small heading, then a row of stills */
.mvp-shots .media-grid { margin-top: 20px; }
@media (min-width: 700px) {
  .mvp-steps { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (max-width: 699px) {
  .mvp-head { flex-direction: column; align-items: flex-start; }
}

/* definition rows (About "Shipped", case-study facts) */
.facts {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--gap);
  padding-top: 14px;
  border-top: 1px solid var(--ink);
}
.facts dt { margin-bottom: 6px; }
.facts dd { font-size: 15px; line-height: 1.45; }
@media (max-width: 699px) {
  .facts { grid-template-columns: repeat(2, minmax(0, 1fr)); row-gap: 20px; }
}

.prose > .shipped { margin-top: 2em; }
.shipped dt { margin-bottom: 6px; }
.shipped dd { font-size: 15px; }

/* About page: heading in columns 1 to 3, text from column 4 to the right
   edge with no measure cap. Stacks like every other page below 900px. */
.story > .resume-body { grid-column: 1 / -1; }
@media (min-width: 900px) {
  .about > .story > .story-h { grid-column: 1 / span 3; }
  .about > .story > .prose,
  .about > .story > .resume-body { grid-column: 4 / -1; max-width: none; }
}
.about .shipped dt { color: var(--accent); }
.about .shipped dt::before { content: "\25B8\00A0"; }

/* About and Contact page titles end in an accent dot (project pages do not) */
.story-h .dot { display: inline-block; width: .28em; height: .28em; border-radius: 50%; background: var(--accent); margin-left: .05em; vertical-align: baseline; }

/* About: a small tag over the text, and a larger opening line */
.tag { display: block; font-style: italic; font-weight: 600; font-size: 15px; color: var(--ink); margin-bottom: .5em; }
#about .prose p.first { font-size: 21px; line-height: 1.4; font-weight: 600; max-width: 30ch; }

/* About: the portrait. A plain hairline-bordered print, not a black
   mount: a tall strip with a frame number and a vertical side caption.
   The strip is as tall as its figure and 1 : 2.6; where that shape can't
   be met the picture keeps its bottom edge and crops from the top. */
.portrait { position: relative; display: flex; gap: 12px; align-items: stretch; margin: 0; }
.portrait .strip { position: relative; flex: 0 0 auto; height: 100%; aspect-ratio: 1 / 2.6; max-width: 100%; border: 1px solid var(--ink); }
.portrait img { width: 100%; height: 100%; object-fit: cover; object-position: 50% 100%; }
.portrait .idx { position: absolute; top: 12px; left: 12px; font: 600 12px/1 var(--display); letter-spacing: .04em; color: #fff; text-shadow: 0 0 6px rgba(0, 0, 0, .45); }
.portrait .side { writing-mode: vertical-rl; font: 400 9px/1.5 var(--mono); letter-spacing: .16em; text-transform: uppercase; color: var(--ink-2); white-space: nowrap; }
.portrait .side b { font-weight: 500; color: var(--ink); }
/* desktop: title over portrait on the left, the text on the right. The
   text sets the height: the portrait takes no part in sizing its row
   (contain: size) and stretches to fill it, so its bottom edge ends
   level with the last line of the text at any width. */
@media (min-width: 900px) {
  .about > #about { grid-template-rows: auto 1fr; row-gap: 28px; }
  .about > #about > .story-h { grid-column: 1 / span 3; grid-row: 1; }
  .about > #about > .portrait { grid-column: 1 / span 3; grid-row: 2; align-self: stretch; contain: size; }
  .about > #about > .prose { grid-column: 6 / -1; grid-row: 1 / span 2; }
  .portrait .strip { width: auto; }
}
/* below that: the portrait follows the text, at a fixed height */
@media (max-width: 899px) {
  .portrait { grid-column: 1 / -1; margin-top: 32px; height: min(70vh, 560px); }
  .portrait .strip { max-width: 62%; }
}

/* About page: resume editions. Three buttons pick the edition; the
   download link, the note beside it and the preview mount follow.
   Without scripting the plain list of links shows instead
   (assets/js/resume.js). */
.story.resume { border-top: 0; }
.resume-links { list-style: none; padding: 0; font-weight: 600; }
.resume-links li + li { margin-top: 10px; }
.resume-options {
  display: grid;
  gap: 0 var(--gap);
  border-top: 1px solid var(--ink);
}
@media (min-width: 600px) {
  .resume-options { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
.resume-opt {
  /* a column from the top, so the three line up whatever wraps */
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
  width: 100%;
  padding: 14px 0 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.resume-opt > span { display: block; }
.resume-opt-name {
  margin-top: 8px;
  font-size: 22px;
  line-height: 1;
  color: #C4C4BF;   /* same grey as the unfinished entries in the home index */
}
.resume-opt-desc { margin-top: 8px; font-size: 14px; line-height: 1.45; color: var(--ink-2); }
.resume-opt[aria-pressed="true"] .resume-opt-label { color: var(--accent); }
.resume-opt[aria-pressed="true"] .resume-opt-name { color: var(--ink); text-shadow: var(--fringe); }
.resume-opt:hover .resume-opt-name { color: var(--accent); text-shadow: none; }
@media (max-width: 599px) {
  .resume-opt { padding-bottom: 14px; }
  .resume-opt + .resume-opt { border-top: 1px solid var(--rule); }
}
.resume-get {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: baseline;
  gap: 6px 24px;
  margin-top: 28px;
}
.resume-download { font-weight: 600; }
.resume-note { font-size: 13px; color: var(--ink-2); }
.resume-pick .figure { margin-top: 20px; }

/* contact links */
.contact-links {
  list-style: none;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 12px 32px;
  font-size: clamp(20px, 2vw, 26px);
  font-weight: 600;
}
.place { color: var(--ink-2); font-size: 15px; }

/* ---------- Project page ---------- */

.site-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 16px;
  padding-block: 20px 14px;
}
.wordmark {
  font-size: clamp(13px, 4.1vw, 24px);
  white-space: nowrap;
  text-decoration: none;
  line-height: 1;
}
.wordmark:hover { color: var(--accent); }
/* phones: keep the one-line wordmark and the nav side by side */
@media (max-width: 419px) {
  .site-head { gap: 12px; }
  .site-head .nav ul { gap: 10px; }
  .site-head .nav a { font-size: 13px; }
}

.project-head { padding-top: clamp(40px, 8vw, 120px); }

.project-title {
  margin: 0;
  /* one fixed size at every width; only titles.js makes it smaller */
  font-size: 136px;
}
/* titles.js shrinks a title only when its longest word would not fit.
   Hide it until fitted so there is no visible jump; reveal anyway after
   1.5s in case the script never finishes (and let a long word break so it
   can't overflow in that case). Without scripting, fall back to a vw clamp
   that can break a long word rather than overflow. */
@media (scripting: enabled) {
  .project-title:not(.is-fit) {
    visibility: hidden;
    overflow-wrap: anywhere;
    animation: title-reveal 0s 1.5s forwards;
  }
}
@keyframes title-reveal { to { visibility: visible; } }
@media (scripting: none) {
  .project-title.display { font-size: clamp(40px, 9vw, 136px); overflow-wrap: anywhere; }
}

.lede {
  margin-top: clamp(12px, 1.1vw, 14px);
  max-width: 34ch;
  font-size: clamp(20px, 2vw, 28px);
  line-height: 1.3;
}

/* short summary under the lede: body text, not a heading */
.project-summary { margin-top: 14px; max-width: 62ch; font-size: 17px; }

.project-head .facts { margin-top: clamp(18px, 2.1vw, 26px); }

.hero { padding-block: clamp(20px, 2.5vw, 32px) clamp(28px, 3.5vw, 44px); }

/* media mounts: every image, video and placeholder sits in a black
   film-frame mount (home previews, project pages and Misc). The media is
   centred with equal left and right margins, white mono labels run up
   the two side edges and along the bottom, and the bottom space is
   deeper than the top. */
.card {
  --mount-x: 16px;
  --mount-top: 16px;
  --mount-bottom: clamp(52px, 5.4vw, 72px);
  position: relative;
  background: #000;
  border-radius: var(--radius);
  padding: var(--mount-top) var(--mount-x) 0;
  overflow: hidden;
}
/* mount labels: decorative, aria-hidden in the markup. Every mount has
   all three: text up the left edge, text down the right edge and the
   bottom row. Every mount has the same margins, so the side text always
   has its strip to sit in. */
.frame-edge,
.frame-nums {
  font-family: var(--mono);
  font-weight: 500;
  line-height: 1.3;
  color: #fff;
}
/* side text: runs up the left edge and down the right, beside the media.
   assets/js/mounts.js lines each edge up with the media and shortens the
   text until it fits (see the fit-* levels below). */
.frame-edge {
  position: absolute;
  top: var(--mount-top);
  bottom: var(--mount-bottom);
  width: var(--mount-x);
  font-size: 9.5px;
  white-space: nowrap;
  overflow: hidden;
}
.frame-edge.l { left: 0; }
.frame-edge.r { right: 0; }
.frame-edge > span {
  position: absolute;
  left: 50%;
  top: 0;
  height: 100%;
  writing-mode: vertical-rl;
  transform: translateX(-50%) rotate(180deg);
  text-align: right;
}
/* the right-hand text sits centred along its edge */
.frame-edge.r > span { transform: translateX(-50%); text-align: center; }
/* shortening, in order, for mounts too short for the full text:
   fit-a drops the site name (left) and the place (right);
   fit-b also drops the project name and steps the size down;
   fit-x is the last resort when even that would clip. */
.card.fit-a .frame-edge .e-site,
.card.fit-b .frame-edge .e-site,
.card.fit-b .frame-edge .e-proj { display: none; }
.card.fit-b .frame-edge { font-size: 8.5px; }
.card.fit-x .frame-edge > span { visibility: hidden; }
/* hold the side text back until it has been fitted (no visible jump);
   show it anyway after 1.5s in case the script never runs */
@media (scripting: enabled) {
  .card:not(.is-fit) .frame-edge {
    visibility: hidden;
    animation: title-reveal 0s 1.5s forwards;
  }
}
/* bottom row: frame number, label, frame number. The label wraps onto a
   second line under the numbers' level when the row is narrow. */
.frame-nums {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 10px;
  height: var(--mount-bottom);
  padding-top: 12px;
  font-size: 10px;
  container-type: inline-size;
}
.frame-nums > span { flex: none; white-space: nowrap; }
.frame-nums > span:nth-child(2) {
  flex: 0 1 auto;
  min-width: 0;
  white-space: normal;
  text-align: center;
}
/* very narrow row: the label without the project name */
@container (width < 250px) {
  .frame-nums .e-proj { display: none; }
}
/* phones: a hairline mount. The labels stay, on both sides and along the
   bottom, but at this size they are decoration rather than reading text. */
@media (max-width: 599px) {
  .card { --mount-x: 8px; --mount-top: 8px; --mount-bottom: 28px; }
  .frame-edge { font-size: 5px; }
  .card.fit-b .frame-edge { font-size: 4.5px; }
  .frame-nums { padding-top: 7px; font-size: 5.5px; }
}
/* media always fills its mount, scaling up or down as needed, so the
   black border is the same width on every mount */
.card img {
  width: 100%;
  border-radius: 5px;
}
/* keyboard focus on a lightbox image has to show against the mount */
.card img:focus-visible { outline-color: #fff; }
/* half its pixel width, still centred; narrow screens fill the card */
.card img.img-half { max-width: min(50%, 500px); }
@media (max-width: 699px) { .card img.img-half { max-width: 100%; } }
.card img[src$=".svg"],
.card video {
  width: 100%;
  border-radius: 5px;
}
.figure figcaption {
  margin-top: 10px;
  max-width: 62ch;
  font-size: 14px;
  line-height: 1.45;
  color: var(--ink-2);
}

.media-grid {
  display: grid;
  gap: var(--gap);
  align-items: start;
}
@media (min-width: 900px) {
  .media-grid.cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .media-grid.cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  /* two images of different shapes at equal height: set --col-a/--col-b
     on the element from each image's width at a shared height */
  .media-grid.ratio-row { grid-template-columns: minmax(0, var(--col-a, 1fr)) minmax(0, var(--col-b, 1fr)); }
}
.media-grid.ratio-row { align-items: stretch; }
/* row of matching slides: three across from 600px, stacked below;
   images fill their cards so different pixel sizes still match */
@media (min-width: 600px) {
  .media-grid.slides-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
.media-grid.ratio-row .figure { display: flex; flex-direction: column; }
.media-grid.ratio-row .card { flex: 1; display: flex; flex-direction: column; }
.media-grid.ratio-row .card img { width: 100%; margin-block: auto; }

/* row of stills: three across from 900px, two from 600px, stacked below */
@media (min-width: 600px) {
  .media-grid.trio { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (min-width: 900px) {
  .media-grid.trio { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* equal-height mounts: every mount in a row is as tall as the tallest,
   whatever the captions do, with the image centred vertically in it.
   Mounts and captions share the grid's rows; the space between rows of
   figures sits under each caption. */
.media-grid.equal {
  align-items: stretch;
  row-gap: 0;
  margin-bottom: calc(-1 * var(--gap));
}
.media-grid.equal .figure {
  display: grid;
  grid-template-rows: subgrid;
  grid-row: span 2;
}
.media-grid.equal .card { display: flex; flex-direction: column; }
.media-grid.equal .card > img,
.media-grid.equal .card > video,
.media-grid.equal .card > .placeholder { flex: none; margin-block: auto; }
.media-grid.equal figcaption { margin-bottom: var(--gap); }
/* images side by side share one shape. --fit-ar on each image is the
   row's width / height; the image fills that shape, cropped from the
   centre (the lightbox still opens the whole image). Applied only while
   the images sit in a row: stacked, each shows whole at its own shape.
   Not used on SVG diagrams, scroll strips or the three-column screen. */
@media (min-width: 900px) {
  .media-grid .card > .fit {
    aspect-ratio: var(--fit-ar);
    object-fit: cover;
  }
}
@media (min-width: 600px) {
  .media-grid.trio .card > .fit,
  .media-grid.thirds .card > .fit,
  .media-grid.slides-3 .card > .fit {
    aspect-ratio: var(--fit-ar);
    object-fit: cover;
  }
}
/* three across from 600px, stacked below: for portrait media that would
   be far too tall stacked at tablet width */
@media (min-width: 600px) {
  .media-grid.thirds { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* one mount holding a tall screen cut into three columns, read top to
   bottom then left to right; one column on phones */
.triptych {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 12px;
  align-items: start;
}
.card .triptych img { width: 100%; }
@media (max-width: 699px) {
  .triptych { grid-template-columns: 1fr; }
}

/* YouTube, click to load: the poster and a play button first; the player
   (youtube-nocookie) loads only when asked (assets/js/media.js). Without
   scripting the link opens the video on YouTube. 16:9, fills the mount. */
.card .yt {
  position: relative;
  display: block;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  border-radius: 5px;
  background: #111;
}
.card .yt img,
.card .yt iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  max-width: none;
  border: 0;
  border-radius: 0;
}
.card .yt img { object-fit: cover; }
.yt-play {
  position: absolute;
  left: 50%;
  top: 50%;
  display: grid;
  place-items: center;
  width: clamp(52px, 7vw, 84px);
  aspect-ratio: 1;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.72);
  font-size: clamp(15px, 2vw, 24px);
  transform: translate(-50%, -50%);
}
.yt-play::before {
  content: "";
  margin-left: 0.2em;
  border-style: solid;
  border-width: 0.55em 0 0.55em 0.95em;
  border-color: transparent transparent transparent #fff;
}
.card .yt:hover .yt-play,
.card .yt:focus-visible .yt-play { background: var(--accent); }
.card .yt:focus-visible { outline-color: #fff; outline-offset: 2px; }

/* placeholder media: missing files render as a labeled grey block
   at the intended aspect ratio (see assets/js/media.js) */
.placeholder {
  display: grid;
  place-items: center;
  aspect-ratio: 16 / 10;
  width: 100%;
  padding: 16px;
  background: #e9e9e7;
  border-radius: 5px;
  color: var(--ink-2);
  font-size: 13px;
  text-align: center;
}

/* wide strip: full content width on desktop, scrolls sideways on mobile.
   The image scrolls inside .strip, so the side text and the bottom row
   stay where they are. A strip keeps a thinner top margin than the
   standard mount where the standard is wider than it (600px and up). */
@media (min-width: 600px) {
  .card.scroll-x { --mount-top: 9px; }
}
.card .strip {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  border-radius: 5px;
}
/* keyboard focus on the scroller has to show against the mount */
.card .strip:focus-visible { outline-color: #fff; outline-offset: 2px; }
.swipe-hint { display: none; }
@media (max-width: 899px) {
  .card .strip img { width: auto; max-width: none; height: clamp(220px, 60vw, 320px); }
  .swipe-hint { display: inline; }
}

/* stack tags */
.tags {
  list-style: none;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
.tags li {
  border: 1px solid var(--ink);
  border-radius: 999px;
  padding: 4px 12px;
  font-size: 14px;
  line-height: 1.4;
}
/* a stack split into labelled groups, one above the other */
.stack-group + .stack-group { margin-top: 24px; }
.stack-group > .meta { margin: 0 0 10px; }

/* "← All work" above the project title */
.back-top { padding-top: 5px; line-height: 1.2; }
.back-top + article .project-head { padding-top: clamp(16px, 1.9vw, 24px); }
.back { font-weight: 600; }

/* About / Contact pages: no rule above the first section */
.page-main > .story:first-child { border-top: 0; }
/* last block on a page without a pager: same space above the footer */
.page-main > .story:last-child,
main.project-head { padding-bottom: var(--foot-gap); }

/* ---------- Miscellaneous: gallery of single pieces ---------- */

.misc {
  padding-block: clamp(40px, 6vw, 96px) clamp(56px, 7vw, 112px);
  border-top: 1px solid var(--rule);
  margin-top: clamp(32px, 4vw, 56px);
}
.misc-grid {
  list-style: none;
  padding: 0;
  display: grid;
  gap: clamp(48px, 6vw, 96px) var(--gap);
  align-items: start;
}
@media (min-width: 900px) {
  .misc-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
.misc-info { margin-top: 20px; max-width: 52ch; }
.misc-title {
  margin: 8px 0 0;
  font-size: clamp(24px, 2.6vw, 36px);
}
.misc-desc { margin-top: 12px; font-size: 15px; color: var(--ink-2); }
.misc-facts { margin-top: 16px; }
.misc-facts dt { margin-bottom: 4px; }
.misc-facts dd { font-size: 15px; }
.misc-info .tags { margin-top: 16px; }

/* Titles with data-short swap to the short form when the full word
   would overflow at its fixed size (see assets/js/titles.js). */
[data-short] { overflow-wrap: normal; }

/* next-project pager: "All work" and the "Next project" label share the
   first line (baseline to baseline), the large title sits under the label */
.pager {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 24px;
  padding-block: 36px 0;
  border-top: 1px solid var(--ink);
}
.pager .next { text-align: right; }
.pager .meta { font-size: 14px; }
.pager .next a {
  display: inline-block;
  margin-top: 8px;
  font-size: clamp(40px, 6vw, 88px);
  text-decoration: none;
}
.pager .next a:hover,
.pager .next a:focus-visible { color: var(--accent); }
.pager .back { font-weight: 600; }
/* pages that end in a pager: the footer's light rule sits 52px under the
   foot of the title's letters. The rule is drawn 21px above the bottom of
   the footer's top space, and the letters stop 0.096em above the bottom
   of the title's box, hence the sum. Pages without a pager keep the
   footer's own spacing. */
main:has(> .pager) + .site-foot {
  --foot-top: calc(73px - 0.096 * clamp(40px, 6vw, 88px));
}
@media (max-width: 599px) {
  .pager { padding-top: 24px; }
  .pager .meta { font-size: 13px; }
  main:has(> .pager) + .site-foot {
    --foot-top: calc(50px - 0.096 * clamp(40px, 6vw, 88px));   /* 32px under the letters */
  }
}
/* a long one-word next title shrinks on phones so it fits beside the
   back link instead of pushing the page wider than the screen */
@media (max-width: 599px) {
  .pager .next a.long { font-size: min(40px, calc((100vw - 150px) / 8.2)); }
}

/* ---------- Lightbox (assets/js/lightbox.js) ---------- */

img[data-lightbox] { cursor: zoom-in; }

html.lightbox-open { overflow: hidden; }

/* A dark tint over a lightly blurred page. Three values to adjust: how
   dark the tint is, how far the page behind is blurred, and the dark
   backing behind the controls. Browsers without backdrop blur show the
   tint alone.
   The overlay is a grid: controls along the top, the image stage in the
   middle with Previous and Next either side of it, the caption below.
   The stage only ever gets the room the controls leave, so nothing
   overlaps the image. */
.lightbox {
  --lightbox-dim: 0.56;                  /* tint opacity, 0 to 1 */
  --lightbox-blur: 5px;                  /* blur of the page behind */
  --lightbox-chip: rgba(0, 0, 0, 0.7);   /* backing behind the controls */
  position: fixed;
  inset: 0;
  z-index: 100;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  grid-template-rows: auto minmax(0, 1fr) auto;
  background: rgba(0, 0, 0, var(--lightbox-dim));
  -webkit-backdrop-filter: blur(var(--lightbox-blur));
  backdrop-filter: blur(var(--lightbox-blur));
  color: #fff;
  opacity: 0;
  pointer-events: none;
}
.lightbox.is-open { opacity: 1; pointer-events: auto; }
.lightbox[hidden],
.lightbox [hidden] { display: none; }
/* the backdrop fades in and out; the image eases up from 97% as it
   appears. Neither happens under reduced motion. Keep 220ms in step
   with FADE in lightbox.js. */
@media (prefers-reduced-motion: no-preference) {
  .lightbox { transition: opacity 220ms ease; }
  .lightbox-stage { transform: scale(0.97); transition: transform 220ms ease-out; }
  .lightbox.is-open .lightbox-stage { transform: none; }
}

/* zoom in, zoom out, Reset and Close share one dark bar, top right.
   White text; Close in the accent colour. */
.lightbox-bar {
  grid-area: 1 / 2 / 2 / 4;
  justify-self: end;
  display: flex;
  gap: 2px;
  margin: 10px var(--pad) 8px 0;
  padding: 2px 6px;
  border-radius: 999px;
  background: var(--lightbox-chip);
}
.lightbox-btn {
  min-width: 36px;
  padding: 8px 6px;
  border: 0;
  border-radius: 999px;
  background: none;
  color: #fff;
  font-family: var(--body);
  font-size: 16px;
  font-weight: 600;
  line-height: 1;
  cursor: pointer;
}
.lightbox-btn:hover { color: var(--accent); }
.lightbox-btn:focus-visible { outline: 2px solid #fff; outline-offset: 1px; }
.lightbox-btn:disabled { color: #fff; opacity: 0.4; cursor: default; }
.lightbox-close { color: var(--accent); }
.lightbox-close:hover { color: #fff; }

/* image sets: a "2 / 3" counter in a dark pill, top left */
.lightbox-count {
  grid-area: 1 / 1 / 2 / 3;
  justify-self: start;
  align-self: center;
  margin: 10px 0 8px var(--pad);
  padding: 7px 12px;
  border-radius: 999px;
  background: var(--lightbox-chip);
  color: #fff;
  font-family: var(--mono);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.04em;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}

/* image sets: round Previous and Next buttons just outside the image's
   left and right edges, each a solid white triangle on a dark disc;
   orange on hover. lightbox.js places them from the image's
   fit-to-screen box and keeps room for them beside it (keep 56px in
   step with STEP there). */
.lightbox-step {
  position: absolute;
  z-index: 1;
  display: grid;
  place-items: center;
  width: 56px;
  height: 56px;
  min-width: 0;
  padding: 0;
  border-radius: 50%;
  background: var(--lightbox-chip);
}
.lightbox-step:hover { background: var(--accent); color: #fff; }
.lightbox-step svg { display: block; }

/* the image is laid out at its opening size and moved with transform */
.lightbox-stage {
  grid-area: 2 / 1 / 3 / 4;
  position: relative;
  min-width: 0;
  min-height: 0;
  overflow: hidden;
  touch-action: none;
}
.lightbox-img {
  position: absolute;
  top: 0;
  left: 0;
  max-width: none;
  border-radius: 4px;
  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.14);   /* a thin light edge, so dark images part from the backdrop */
  transform-origin: 0 0;
  user-select: none;
  -webkit-user-drag: none;
}
@media (prefers-reduced-motion: no-preference) {
  .lightbox-img.anim { transition: transform 0.18s ease-out; }
  .lightbox-img.swap { animation: lightbox-swap 0.16s ease-out; }
}
@keyframes lightbox-swap { from { opacity: 0; } }
.lightbox.can-pan .lightbox-stage { cursor: grab; }
.lightbox.can-pan.is-dragging .lightbox-stage { cursor: grabbing; }

/* a video opened from a small mount: the player in place of the image */
.lightbox-video {
  position: absolute;
  border: 0;
  border-radius: 4px;
  background: #000;
  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.14);
}
.lightbox.is-video .lightbox-img { display: none; }

/* the image's caption, centred below it in a dark pill */
.lightbox-cap {
  grid-area: 3 / 1 / 4 / 4;
  justify-self: center;
  max-width: min(72ch, calc(100% - 2 * var(--pad)));
  margin: 0 0 14px;
  padding: 8px 14px;
  border-radius: 16px;
  background: var(--lightbox-chip);
  color: #fff;
  font-size: 14px;
  line-height: 1.4;
  text-align: center;
}

/* phones: Previous, the counter and Next sit together along the bottom
   edge, in thumb reach, and the image gets the full width */
@media (max-width: 599px) {
  .lightbox {
    --lightbox-foot: max(12px, env(safe-area-inset-bottom));
    grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
    grid-template-rows: auto minmax(0, 1fr) auto auto;
  }
  .lightbox-bar { grid-area: 1 / 1 / 2 / 4; }
  .lightbox-cap { grid-area: 3 / 1 / 4 / 4; margin-bottom: 10px; font-size: 13px; }
  .lightbox-step { position: static; align-self: center; width: 44px; height: 44px; }
  .lightbox-prev { grid-area: 4 / 1; justify-self: end; margin: 0 14px var(--lightbox-foot) 0; }
  .lightbox-count { grid-area: 4 / 2; margin: 0 0 var(--lightbox-foot); }
  .lightbox-next { grid-area: 4 / 3; justify-self: start; margin: 0 0 var(--lightbox-foot) 14px; }
}

/* ---------- Footer ---------- */

/* One layout at every width. A light rule on every page, drawn inside the
   footer's top space 20px above the icons and name. On a short page the
   footer drops to the bottom of the window. The page ends 28px below the
   round buttons. */
.site-foot {
  --foot-top: calc(66px + clamp(20px, 3vw, 48px));
  margin-top: auto;
  background: linear-gradient(var(--rule), var(--rule)) no-repeat 0 calc(var(--foot-top) - 21px) / 100% 1px;
  font-size: 11px;
  line-height: 1.5;
  padding-block: var(--foot-top) 28px;
}
/* icons and name on one line. The black rule under them stops 3px short
   of the T (drawn as a background so it can). The row under the rule is
   exactly as wide as the rule: page links on the left, 6px below it; the
   three role lines on the right, ending flush with the rule's right end. */
.foot-head { display: grid; grid-template-columns: max-content max-content; justify-content: start; }
.foot-brand { grid-column: 1 / -1; display: grid; grid-template-columns: subgrid; align-items: end; }
.foot-ruled {
  display: flex;
  align-items: center;
  gap: 16px;
  padding-bottom: 9.5px;
  background: linear-gradient(var(--ink), var(--ink)) no-repeat left bottom / calc(100% - 3px) 1.5px;
}
.foot-brand > .foot-mark { padding-bottom: 9.5px; }
.foot-icons { list-style: none; padding: 0; display: flex; gap: 6px; }
.foot-icons svg { display: block; width: 24px; height: 24px; }
.foot-mark {
  position: relative;
  font-size: 33px;
  line-height: 24px;
  text-shadow: var(--fringe);
  white-space: nowrap;
}
.foot-under {
  grid-column: 1;
  margin-right: 3px;
  display: flex;
  flex-direction: row-reverse;
  justify-content: space-between;
  align-items: flex-start;
  gap: 16px;
}
.foot-tiny { margin-top: 5px; font-size: 8.5px; line-height: 1.25; text-transform: lowercase; text-align: right; }
.foot-links {
  list-style: none;
  padding: 0;
  margin-top: 6px;
  display: flex;
  flex-wrap: wrap;
  gap: 6px 18px;
}
.foot-links a { text-decoration-thickness: 1px; text-underline-offset: 0.3em; }
/* the role lines beside the links are taller than the links, so the
   copyright line is pulled up to sit about 13px under the links */
.foot-copy { margin-top: -1px; font-size: 9.5px; }
.foot-social { list-style: none; padding: 0; margin-top: 14px; display: flex; gap: 8px; }
.foot-social a {
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--ink);
  color: #fff;
  text-decoration: none;
  font-weight: 600;
  font-size: 16px;
  letter-spacing: -0.03em;
  line-height: 1;
}
.foot-social a:hover { background: var(--accent); color: #fff; }
.foot-social svg { display: block; }
/* version: bottom-right corner of the page, measured from the window
   edge rather than the content column */
.site-foot .version {
  position: absolute;
  right: 28px;
  bottom: 26px;
  color: #C4C4BF;   /* the light grey of the unfinished index entries */
  font-family: var(--display);
  font-weight: 900;
  font-size: 40px;
  line-height: 0.92;
  letter-spacing: -0.02em;
}
/* 33px wherever it fits (about 530px and up); below that the name scales
   down smoothly so the icons and name stay on one line without widening
   the page */
.foot-mark { font-size: clamp(12px, calc((100vw - 220px) / 9.4), 33px); }
@media (max-width: 599px) {
  .site-foot .version { font-size: 30px; }
}

/* ---------- Motion: simple fades only ---------- */

/* Hover goes straight to the accent: no colour transition. */
@media (prefers-reduced-motion: no-preference) {
  main { animation: fade-in 0.6s ease-out both; }
}

@keyframes fade-in {
  from { opacity: 0; }
  to { opacity: 1; }
}
