/* ==========================================================================
   kerseyfabrications.com

   Paper light. A rail carries identity and every link; the right side is a
   short, glanceable page. The brief is a link tree with a face on it, not a
   portfolio — if a section starts growing, it belongs on a sub-page.

   Nothing on this site reports a number that changes on its own: no view
   counts, no subscriber count, no "latest video". Everything here stays
   true without anyone maintaining it.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Typefaces
   -------------------------------------------------------------------------- */

@font-face {
   font-family: 'Big Shoulders Display';
   src: url('../fonts/big-shoulders.woff2') format('woff2');
   font-weight: 100 900;
   font-style: normal;
   font-display: swap;
}
@font-face {
   font-family: 'Instrument Sans';
   src: url('../fonts/instrument-sans.woff2') format('woff2');
   font-weight: 400 700;
   font-style: normal;
   font-display: swap;
}
@font-face {
   font-family: 'JetBrains Mono';
   src: url('../fonts/jetbrains-mono.woff2') format('woff2');
   font-weight: 100 800;
   font-style: normal;
   font-display: swap;
}

/* --------------------------------------------------------------------------
   2. Tokens
   -------------------------------------------------------------------------- */

:root {
   --paper: #fbfaf8;
   --paper-2: #f2f1ec;
   --rule: #e4e2dc;
   --rule-soft: #eeece7;

   --ink: #14181c; /* 17.2:1 on paper */
   --ink-2: #5c6570; /* 5.7:1  — all secondary text and mono labels */

   /* Kris's KF cyan. The bright original is far too light for text on paper,
      so links use a darkened cut; the logo keeps the real color.
      This cut is darker than strictly needed on flat paper (5.5:1 rather than
      4.6:1) so it still clears AA where a grid line crosses it — at #0e7c9e
      that overlap measured 4.2:1. */
   --accent: #0b6f8e; /* 5.5:1 on paper, 5.0:1 over a grid line */
   --accent-deep: #0a6280; /* hover, and white-on-this for solid buttons */
   --mark: #5dd6ff;

   /* Graph-paper ruling. Real engineering pads are ruled in blue-green, which
      happens to be the KF hue.

      Single level, deliberately. A fine grid plus heavier every-fifth lines
      looks better but cannot be made accessible: the two layers coincide, so
      a major intersection stacks four translucent lines into an effective 23%
      alpha and drops body text to 4.1:1. One layer means the worst case is a
      simple crossing at 9.8%, which holds every text color above 4.7:1. */
   --rule-grid: rgba(11, 111, 142, 0.05);

   --font-display: 'Big Shoulders Display', 'Arial Narrow', system-ui, sans-serif;
   --font-body: 'Instrument Sans', system-ui, -apple-system, 'Segoe UI', sans-serif;
   --font-mono: 'JetBrains Mono', ui-monospace, 'Cascadia Code', Consolas, monospace;

   --rail-w: 300px;

   --text-xs: 0.75rem;
   --text-sm: 0.875rem;
   --text-base: 1rem;
   --text-md: 1.0625rem;
   --text-lg: 1.1875rem;
   --text-xl: 1.375rem;

   --sp-1: 0.375rem;
   --sp-2: 0.625rem;
   --sp-3: 1rem;
   --sp-4: 1.5rem;
   --sp-5: 2.25rem;
   --sp-6: 3.25rem;

   --ease: cubic-bezier(0.2, 0.7, 0.3, 1);
}

/* --------------------------------------------------------------------------
   3. Base
   -------------------------------------------------------------------------- */

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

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

body {
   margin: 0;
   background-color: var(--paper);
   /* 28px ruling. See --rule-grid for why there is no heavier every-fifth
      line, tempting as it is. */
   background-image:
      linear-gradient(var(--rule-grid) 1px, transparent 1px),
      linear-gradient(90deg, var(--rule-grid) 1px, transparent 1px);
   background-size: 28px 28px;
   color: var(--ink);
   font-family: var(--font-body);
   font-size: var(--text-md);
   line-height: 1.6;
   -webkit-font-smoothing: antialiased;
}

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

/* None of the three faces carry U+2082, so a literal ₂ would fall back to a
   system font mid-word. <sub> subscripts the font's own numeral instead;
   line-height:0 keeps it from opening up the line box. */
sub {
   font-size: 0.72em;
   line-height: 0;
   vertical-align: -0.22em;
}

a {
   color: inherit;
}

h1,
h2,
h3 {
   margin: 0;
   font-family: var(--font-display);
   font-weight: 700;
   line-height: 0.94;
   letter-spacing: 0.005em;
}

p {
   margin: 0 0 var(--sp-3);
}

p:last-child {
   margin-bottom: 0;
}

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

.skip {
   position: absolute;
   left: 1rem;
   top: -5rem;
   z-index: 200;
   background: var(--ink);
   color: var(--paper);
   font-family: var(--font-mono);
   font-size: var(--text-xs);
   font-weight: 500;
   text-transform: uppercase;
   letter-spacing: 0.09em;
   padding: 0.7rem 1.1rem;
   text-decoration: none;
   transition: top 0.16s var(--ease);
}

.skip:focus {
   top: 1rem;
}

/* --------------------------------------------------------------------------
   4. Shell
   -------------------------------------------------------------------------- */

/* Capped and centered. Without a ceiling the work column reaches 2244px on a
   2560px display — far too wide to read, and it stretched the hero photo to
   81% of the viewport while upscaling its source. */
.shell {
   display: grid;
   grid-template-columns: var(--rail-w) minmax(0, 1fr);
   max-width: 1500px;
   margin-inline: auto;
}

.work {
   min-width: 0;
   border-left: 1px solid var(--rule);
}

/* --------------------------------------------------------------------------
   5. Rail
   -------------------------------------------------------------------------- */

.rail {
   position: sticky;
   top: 0;
   height: 100vh;
   overflow-y: auto;
   overscroll-behavior: contain;
   padding: var(--sp-4);
   display: flex;
   flex-direction: column;
   gap: var(--sp-3);
}

/* Grouping wrappers for the mobile layout. On desktop they dissolve, so their
   children stay direct flex items of the rail and the foot still pins to the
   bottom with margin-top: auto. */
.rail__bar,
.rail__menu {
   display: contents;
}

/* Mobile only, and only with JS — see the responsive section. */
.rail__toggle {
   display: none;
}

.rail__home {
   display: block;
   width: 42px;
   flex: none;
   transition: opacity 0.14s var(--ease);
}

.rail__home:hover {
   opacity: 0.7;
}

.rail__mark {
   width: 42px;
   height: 42px;
}

/* Portrait beside the name rather than above it: a face helps someone learn
   who this is at a glance, but it must not push the nav down the rail. */
.rail__ident {
   display: flex;
   align-items: flex-start;
   gap: var(--sp-3);
}

.rail__portrait {
   width: 104px;
   height: 104px;
   flex: none;
   object-fit: cover;
   border: 1px solid var(--rule);
}

.rail__name {
   font-family: var(--font-display);
   font-size: 2.6rem;
   font-weight: 800;
   text-transform: uppercase;
   line-height: 0.84;
   margin: 0;
}

.rail__role {
   margin: 0;
   font-family: var(--font-mono);
   font-size: var(--text-xs);
   font-weight: 500;
   text-transform: uppercase;
   letter-spacing: 0.1em;
   color: var(--accent);
}

.rail__spans {
   display: flex;
   gap: var(--sp-4);
   margin: 0;
   padding: var(--sp-2) 0;
   border-top: 1px solid var(--rule);
   border-bottom: 1px solid var(--rule);
}

.rail__spans dt {
   font-family: var(--font-mono);
   font-size: 0.6875rem;
   text-transform: uppercase;
   letter-spacing: 0.1em;
   color: var(--ink-2);
}

.rail__spans dd {
   margin: 0.1rem 0 0;
   font-family: var(--font-display);
   font-size: 1.6rem;
   font-weight: 700;
   line-height: 1;
}

.rail__spans dd span {
   font-family: var(--font-mono);
   font-size: 0.6875rem;
   font-weight: 500;
   color: var(--ink-2);
   margin-left: 0.2rem;
   vertical-align: 0.5em;
}

.rail__label {
   font-family: var(--font-mono);
   font-size: 0.6875rem;
   text-transform: uppercase;
   letter-spacing: 0.13em;
   color: var(--ink-2);
   margin: 0 0 0.3rem;
}

.rail__group {
   margin: 0;
   padding: 0;
   list-style: none;
}

.rail__link {
   display: flex;
   align-items: baseline;
   justify-content: space-between;
   gap: var(--sp-2);
   padding: 0.34rem 0;
   border-bottom: 1px solid var(--rule-soft);
   text-decoration: none;
   font-size: var(--text-base);
   transition: color 0.14s var(--ease), padding-left 0.14s var(--ease);
}

.rail__link:hover,
.rail__link:focus-visible {
   color: var(--accent);
   padding-left: 0.3rem;
}

/* Absolutely positioned so it does not become a third item in the
   space-between row and shove the label into the middle. */
.rail__link[aria-current='page'] {
   position: relative;
   padding-left: 0.9rem;
   color: var(--accent);
   font-weight: 500;
}

.rail__link[aria-current='page']::before {
   content: '';
   position: absolute;
   left: 0;
   top: 0.62em;
   width: 0.42rem;
   height: 0.42rem;
   background: var(--accent);
}

.rail__foot {
   margin-top: auto;
   padding-top: var(--sp-3);
   font-family: var(--font-mono);
   font-size: 0.6875rem;
   line-height: 1.7;
   color: var(--ink-2);
}

.rail__foot a {
   color: var(--accent);
}

/* --------------------------------------------------------------------------
   6. Home
   -------------------------------------------------------------------------- */

.lede {
   padding: var(--sp-5) var(--sp-5) var(--sp-4);
}

/* The break between the two sentences is authored with <br>, so no balancing
   here — and the measure has to be wide enough that neither sentence wraps
   again on its own. */
.lede__line {
   font-size: clamp(1.75rem, 3.4vw, 2.9rem);
   font-weight: 800;
   text-transform: uppercase;
   line-height: 0.94;
   max-width: 32ch;
}

.lede__sub {
   margin: var(--sp-3) 0 0;
   font-size: var(--text-lg);
   line-height: 1.5;
   color: var(--ink-2);
   max-width: 56ch;
}

.lede__line em {
   font-style: normal;
   color: var(--accent);
}

/* Second belt: on a short, wide window the ratio alone would still make this
   dominate, so cap it against viewport height too. object-fit crops rather
   than distorts when the cap bites. */
.shot {
   display: block;
   width: 100%;
   aspect-ratio: 2.7 / 1;
   max-height: 38vh;
   object-fit: cover;
   object-position: 50% 38%;
   border-block: 1px solid var(--rule);
}

/* Three things, said plainly. Each row is also a route out. */
.doing {
   margin: 0;
   padding: var(--sp-5) var(--sp-5);
}

.doing__row {
   display: grid;
   grid-template-columns: 7.5rem minmax(0, 1fr);
   gap: var(--sp-3) var(--sp-4);
   padding: var(--sp-3) 0;
   border-bottom: 1px solid var(--rule-soft);
   align-items: baseline;
}

.doing__row:first-child {
   border-top: 1px solid var(--rule);
}

.doing dt {
   font-family: var(--font-display);
   font-size: var(--text-xl);
   font-weight: 700;
   text-transform: uppercase;
   line-height: 1;
}

.doing dd {
   margin: 0;
   color: var(--ink-2);
   max-width: 56ch;
}

.doing a {
   color: var(--accent);
   text-decoration-thickness: 1px;
   text-underline-offset: 3px;
   white-space: nowrap;
}

.doing a:hover {
   color: var(--ink);
}

/* --------------------------------------------------------------------------
   7. Bands and tiles
   -------------------------------------------------------------------------- */

.band {
   padding: var(--sp-5);
   border-top: 1px solid var(--rule);
}

.band__head {
   display: flex;
   align-items: baseline;
   justify-content: space-between;
   gap: var(--sp-3);
   flex-wrap: wrap;
   margin-bottom: var(--sp-4);
}

.band__title {
   font-family: var(--font-mono);
   font-size: var(--text-xs);
   font-weight: 500;
   text-transform: uppercase;
   letter-spacing: 0.14em;
   color: var(--ink);
   margin: 0;
}

.band__more {
   font-family: var(--font-mono);
   font-size: var(--text-xs);
   text-transform: uppercase;
   letter-spacing: 0.1em;
   color: var(--accent);
   text-decoration: none;
}

.band__more:hover {
   color: var(--ink);
}

.grid {
   display: grid;
   grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr));
   gap: var(--sp-4) var(--sp-3);
}

.tile {
   display: flex;
   flex-direction: column;
   text-decoration: none;
   color: inherit;
}

.tile__media {
   position: relative;
   overflow: hidden;
   background: var(--paper-2);
   border: 1px solid var(--rule);
   margin-bottom: var(--sp-2);
}

.tile__media img {
   width: 100%;
   aspect-ratio: 16 / 9;
   object-fit: cover;
   transition: transform 0.45s var(--ease);
}

.tile:hover .tile__media img,
.tile:focus-visible .tile__media img {
   transform: scale(1.04);
}

/* Always visible, never a hover-only cue. */
.tile__play {
   position: absolute;
   left: 0;
   bottom: 0;
   width: 2.4rem;
   height: 2.4rem;
   background: var(--accent);
   display: grid;
   place-items: center;
}

.tile__play::after {
   content: '';
   border-style: solid;
   border-width: 0.38rem 0 0.38rem 0.6rem;
   border-color: transparent transparent transparent #fff;
   margin-left: 0.1rem;
}

.tile__kind {
   font-family: var(--font-mono);
   font-size: 0.6875rem;
   font-weight: 500;
   text-transform: uppercase;
   letter-spacing: 0.11em;
   color: var(--ink-2);
   margin: 0 0 0.25rem;
}

.tile__title {
   font-size: var(--text-lg);
   font-weight: 700;
   text-transform: uppercase;
   line-height: 1.02;
   margin: 0;
   transition: color 0.14s var(--ease);
}

.tile:hover .tile__title,
.tile:focus-visible .tile__title {
   color: var(--accent);
}

/* --------------------------------------------------------------------------
   8. Buttons
   -------------------------------------------------------------------------- */

.btns {
   display: flex;
   flex-wrap: wrap;
   gap: var(--sp-2);
}

.btns--head {
   margin-top: var(--sp-4);
}

.btn {
   display: inline-flex;
   align-items: center;
   font-family: var(--font-mono);
   font-size: var(--text-xs);
   font-weight: 500;
   text-transform: uppercase;
   letter-spacing: 0.1em;
   text-decoration: none;
   padding: 0.75rem 1.2rem;
   border: 1px solid var(--ink);
   color: var(--ink);
   transition: background-color 0.14s var(--ease), color 0.14s var(--ease),
      border-color 0.14s var(--ease);
}

.btn:hover {
   background: var(--ink);
   color: var(--paper);
}

/* White on --accent is only 4.0:1; on --accent-deep it is 6.1:1. */
.btn--go {
   background: var(--accent-deep);
   border-color: var(--accent-deep);
   color: #fff;
}

.btn--go:hover {
   background: var(--ink);
   border-color: var(--ink);
   color: var(--paper);
}

/* --------------------------------------------------------------------------
   9. Sub-pages
   -------------------------------------------------------------------------- */

.phead {
   padding: var(--sp-5) var(--sp-5) var(--sp-4);
}

.phead__title {
   font-size: clamp(2.4rem, 5vw, 4rem);
   font-weight: 800;
   text-transform: uppercase;
   line-height: 0.88;
   margin-bottom: var(--sp-3);
}

.phead__sub {
   color: var(--ink-2);
   font-size: var(--text-lg);
   max-width: 58ch;
}

/* Credentials, sized down deliberately. The story in .phead__sub carries the
   page; these are the receipts underneath it, not the headline. */
.creds {
   display: flex;
   flex-wrap: wrap;
   gap: var(--sp-2) var(--sp-5);
   list-style: none;
   margin: var(--sp-4) 0 0;
   padding: 0;
   font-family: var(--font-mono);
   font-size: var(--text-xs);
   text-transform: uppercase;
   letter-spacing: 0.08em;
   color: var(--ink-2);
}

.creds b {
   font-family: var(--font-display);
   font-size: 1.3rem;
   font-weight: 700;
   color: var(--ink);
   letter-spacing: 0;
   margin-right: 0.35rem;
}

.prose a {
   color: var(--accent);
   text-underline-offset: 3px;
}

.prose a:hover {
   color: var(--ink);
}

/* Project rows. Text leads and stays full-measure; the photograph is a
   supporting thumbnail on the right. A row without a photo just goes without
   one — the left column keeps its width either way, so the list stays aligned
   rather than looking half-finished. */
.projs {
   list-style: none;
   margin: 0;
   padding: 0;
   border-top: 1px solid var(--rule);
}

.proj {
   display: grid;
   grid-template-columns: minmax(0, 1fr) 240px;
   gap: var(--sp-4);
   align-items: start;
   padding: var(--sp-4) 0;
   border-bottom: 1px solid var(--rule-soft);
}

.proj__name {
   font-size: var(--text-xl);
   font-weight: 700;
   text-transform: uppercase;
   margin: 0 0 var(--sp-2);
}

.proj__desc {
   color: var(--ink-2);
   max-width: 62ch;
   margin: 0 0 var(--sp-2);
}

.proj__meta {
   font-family: var(--font-mono);
   font-size: var(--text-xs);
   color: var(--ink-2);
   margin: 0;
}

.proj__meta a {
   color: var(--accent);
   text-underline-offset: 3px;
   white-space: nowrap;
}

.proj__meta a:hover {
   color: var(--ink);
}

.proj__shot {
   grid-column: 2;
   width: 100%;
   aspect-ratio: 16 / 10;
   object-fit: cover;
   border: 1px solid var(--rule);
}

.band__note {
   margin-top: var(--sp-4);
   font-size: var(--text-sm);
   color: var(--ink-2);
}

.topics {
   display: grid;
   grid-template-columns: repeat(auto-fit, minmax(min(340px, 100%), 1fr));
   gap: var(--sp-5);
}

.topic {
   border-top: 2px solid var(--accent);
   padding-top: var(--sp-2);
}

.topic__title {
   font-size: var(--text-xl);
   font-weight: 700;
   text-transform: uppercase;
   margin-bottom: var(--sp-2);
}

.topic__desc {
   color: var(--ink-2);
   font-size: var(--text-sm);
   margin-bottom: var(--sp-2);
}

.topic__for {
   font-family: var(--font-mono);
   font-size: 0.6875rem;
   text-transform: uppercase;
   letter-spacing: 0.1em;
   color: var(--accent);
   margin: 0;
}

.spec {
   margin: 0;
   border-top: 1px solid var(--rule);
}

.spec__row {
   display: grid;
   grid-template-columns: minmax(8rem, 12rem) minmax(0, 1fr);
   gap: var(--sp-2) var(--sp-4);
   padding: var(--sp-3) 0;
   border-bottom: 1px solid var(--rule-soft);
}

.spec dt {
   font-family: var(--font-mono);
   font-size: var(--text-xs);
   text-transform: uppercase;
   letter-spacing: 0.11em;
   color: var(--ink-2);
   padding-top: 0.2em;
}

.spec dd {
   margin: 0;
   max-width: 62ch;
}

.gigs {
   list-style: none;
   margin: 0;
   padding: 0;
   border-top: 1px solid var(--rule);
}

.gig {
   display: grid;
   grid-template-columns: 8rem minmax(0, 1fr);
   gap: var(--sp-4);
   padding: var(--sp-4) 0;
   border-bottom: 1px solid var(--rule-soft);
}

.gig__year {
   display: block;
   font-family: var(--font-display);
   font-size: 1.9rem;
   font-weight: 700;
   line-height: 1;
}

.gig__date {
   display: block;
   margin-top: 0.2rem;
   font-family: var(--font-mono);
   font-size: var(--text-xs);
   color: var(--ink-2);
}

.gig__venue {
   font-size: var(--text-xl);
   font-weight: 700;
   text-transform: uppercase;
   margin: 0;
}

.gig__role {
   margin: 0.25rem 0 0;
   font-family: var(--font-mono);
   font-size: var(--text-xs);
   text-transform: uppercase;
   letter-spacing: 0.1em;
   color: var(--ink-2);
}

.gig__role--key {
   color: var(--accent);
}

.gig__talks {
   list-style: none;
   margin: var(--sp-3) 0 0;
   padding: 0;
}

.gig__talks li {
   position: relative;
   padding-left: 1.1rem;
   margin-bottom: 0.3rem;
   color: var(--ink-2);
   max-width: 62ch;
}

.gig__talks li::before {
   content: '';
   position: absolute;
   left: 0;
   top: 0.72em;
   width: 0.55rem;
   height: 1px;
   background: var(--accent);
}

.gig__talks a {
   color: var(--ink);
   text-decoration-color: var(--accent);
   text-underline-offset: 3px;
}

.gig__talks a:hover {
   color: var(--accent);
}

.close {
   padding: var(--sp-5);
   border-top: 1px solid var(--rule);
   background: var(--paper-2);
}

.close__title {
   font-size: clamp(1.8rem, 3.4vw, 2.6rem);
   font-weight: 800;
   text-transform: uppercase;
   line-height: 0.92;
   margin-bottom: var(--sp-3);
   max-width: 22ch;
}

.close__body {
   color: var(--ink-2);
   max-width: 56ch;
   margin-bottom: var(--sp-4);
}

/* --------------------------------------------------------------------------
   9b. Policy prose

   The review policy is the site's only long-form page: each section is a
   .policy block with a .policy__title, and this styles the flowing body
   inside one. Body runs at full --ink for legibility over a page this long,
   not the --ink-2 used for short supporting copy elsewhere. Bullets reuse the
   accent-dash marker from .gig__talks so the list idiom stays consistent
   site-wide, and links match .prose (accent, ink on hover).
   -------------------------------------------------------------------------- */

/* Section headings match the strong display titles used elsewhere
   (.topic__title, .gig__venue, .proj__name) rather than the quiet mono
   .band__title eyebrow — a policy section is a content header, not a band
   category label, so it needs that visual weight to read as a site page. */
.policy__title {
   font-family: var(--font-display);
   font-size: var(--text-xl);
   font-weight: 700;
   text-transform: uppercase;
   line-height: 1.05;
   margin-bottom: var(--sp-3);
   max-width: 28ch;
}

.policy {
   max-width: 68ch;
}

.policy p {
   margin: 0 0 var(--sp-3);
}

.policy > :last-child {
   margin-bottom: 0;
}

.policy strong {
   font-weight: 700;
   color: var(--ink);
}

.policy ul {
   list-style: none;
   margin: var(--sp-3) 0;
   padding: 0;
}

.policy li {
   position: relative;
   padding-left: 1.1rem;
   margin-bottom: 0.5rem;
   max-width: 66ch;
}

.policy li:last-child {
   margin-bottom: 0;
}

.policy li::before {
   content: '';
   position: absolute;
   left: 0;
   top: 0.72em;
   width: 0.55rem;
   height: 1px;
   background: var(--accent);
}

.policy a {
   color: var(--accent);
   text-underline-offset: 3px;
}

.policy a:hover {
   color: var(--ink);
}

/* --------------------------------------------------------------------------
   10. Reveal
   -------------------------------------------------------------------------- */

.js .rv {
   opacity: 0;
   transform: translateY(14px);
}

.js .rv.in {
   opacity: 1;
   transform: none;
   transition: opacity 0.5s var(--ease), transform 0.5s var(--ease);
}

/* --------------------------------------------------------------------------
   11. Responsive
   -------------------------------------------------------------------------- */

@media (max-width: 940px) {
   .shell {
      grid-template-columns: 1fr;
   }

   .rail {
      position: static;
      height: auto;
      overflow: visible;
      display: block;
      padding-block: var(--sp-3);
      border-bottom: 1px solid var(--rule);
   }

   /* Identity and the menu button on one row, so the first screen on a
      phone is the page itself and not a wall of links. */
   .rail__bar {
      display: flex;
      align-items: center;
      gap: var(--sp-2);
   }

   /* Tighter than desktop: at the default gaps the bar's minimum width
      overflowed a 360px screen by a pixel. */
   .rail__ident {
      align-items: center;
      gap: var(--sp-2);
   }

   .rail__portrait {
      width: 56px;
      height: 56px;
   }

   .rail__name {
      font-size: 1.9rem;
   }

   .rail__menu {
      display: grid;
      grid-template-columns: repeat(2, minmax(0, 1fr));
      grid-template-areas:
         'role  role'
         'spans spans'
         'pages links'
         'foot  foot';
      column-gap: var(--sp-4);
      row-gap: var(--sp-4);
      align-items: start;
      margin-top: var(--sp-4);
   }

   /* Collapsed only when JS is running to reopen it. With scripting off the
      menu stays expanded, exactly as it was before the toggle existed. */
   .js .rail:not(.is-open) .rail__menu {
      display: none;
   }

   .js .rail__toggle {
      display: inline-flex;
      align-items: center;
      gap: 0.55rem;
      margin-left: auto;
      min-height: 44px;
      padding: 0 0.9rem;
      font-family: var(--font-mono);
      font-size: var(--text-xs);
      font-weight: 500;
      text-transform: uppercase;
      letter-spacing: 0.1em;
      color: var(--accent);
      background: var(--paper);
      border: 1px solid var(--rule);
      cursor: pointer;
      transition: border-color 0.14s var(--ease);
   }

   /* Hover sticks after a tap on touch screens, so mice only. */
   @media (hover: hover) {
      .rail__toggle:hover {
         border-color: var(--accent);
      }
   }

   /* Two bars that cross into an X when open. */
   .rail__burger {
      position: relative;
      width: 16px;
      height: 10px;
   }

   .rail__burger::before,
   .rail__burger::after {
      content: '';
      position: absolute;
      left: 0;
      right: 0;
      height: 2px;
      background: currentColor;
      transition: transform 0.18s var(--ease);
   }

   .rail__burger::before {
      top: 0;
   }

   .rail__burger::after {
      bottom: 0;
   }

   .rail.is-open .rail__burger::before {
      transform: translateY(4px) rotate(45deg);
   }

   .rail.is-open .rail__burger::after {
      transform: translateY(-4px) rotate(-45deg);
   }

   /* Without an explicit area this auto-places after every named one, which
      dropped the role line below the address. */
   .rail__role {
      grid-area: role;
   }

   .rail__spans {
      grid-area: spans;
   }

   .rail__pages {
      grid-area: pages;
   }

   .rail__links {
      grid-area: links;
   }

   .rail__foot {
      grid-area: foot;
      margin-top: 0;
   }

   .work {
      border-left: 0;
   }
}

/* Below this the mobile bar's minimum width no longer fits a 320px screen. */
@media (max-width: 380px) {
   .rail {
      padding-inline: var(--sp-3);
   }

   .rail__mark,
   .rail__home {
      width: 36px;
      height: 36px;
   }

   .rail__portrait {
      width: 44px;
      height: 44px;
   }

   .rail__name {
      font-size: 1.6rem;
   }

   .js .rail__toggle {
      padding: 0 0.7rem;
   }
}

@media (max-width: 640px) {
   :root {
      --sp-5: 1.25rem;
      --sp-6: 2.25rem;
   }

   .rail__link {
      flex-wrap: wrap;
      row-gap: 0;
      padding: 0.3rem 0;
   }

   .shot {
      aspect-ratio: 4 / 3;
   }

   .doing__row,
   .spec__row,
   .gig {
      grid-template-columns: 1fr;
      gap: 0.35rem;
   }

   /* Photo drops below the text rather than squeezing beside it. */
   .proj {
      grid-template-columns: 1fr;
      gap: var(--sp-3);
   }

   .proj__shot {
      grid-column: 1;
      max-width: 420px;
   }

   .gig__date {
      display: inline;
      margin-left: 0.6rem;
   }
}

/* --------------------------------------------------------------------------
   12. Touch targets

   Text links sit at 14-20px tall, which is fine with a mouse and fiddly with
   a thumb. Vertical padding on an inline element grows its hit box without
   changing the line box, so these get bigger to touch without the layout
   moving. The dense talk list also gets extra row spacing, otherwise the
   enlarged targets would overlap each other and cause mis-taps.
   -------------------------------------------------------------------------- */

@media (pointer: coarse) {
   .band__more,
   .rail__foot a {
      display: inline-block;
      padding-block: 0.7rem;
   }

   .doing a,
   .prose a,
   .policy a,
   .proj__meta a {
      padding-block: 0.55rem;
   }

   .gig__talks a {
      padding-block: 0.45rem;
   }

   .gig__talks li {
      margin-bottom: 0.8rem;
   }
}

/* --------------------------------------------------------------------------
   13. Print

   .rv targets the visitor never scrolled to are still at opacity 0 and would
   print blank. The Menu button does nothing on paper; at narrow print widths
   the collapsed rail prints as just the name bar, which is all a printout
   needs.
   -------------------------------------------------------------------------- */

@media print {
   .js .rv {
      opacity: 1;
      transform: none;
   }

   .js .rail__toggle {
      display: none;
   }
}

/* --------------------------------------------------------------------------
   14. Reduced motion
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
   html {
      scroll-behavior: auto;
   }

   *,
   *::before,
   *::after {
      animation-duration: 0.01ms !important;
      transition-duration: 0.01ms !important;
   }

   .js .rv {
      opacity: 1;
      transform: none;
   }
}
