/* Bryce Boggs — portfolio
   Two faces, like a printed table of contents:
   - Inter Bold, uppercase, tight: titles and names (.display)
   - IBM Plex Mono: everything else. Uppercase and tracked for labels/meta (.mono),
     sentence case for running text (body). */

:root {
  /* Pure white page: most of the work is drawn or rendered on white, so images sit flush with no
     visible box, and it gives the highest contrast. Glare is softened in the ink, not the page.
     Contrast on white: ink 18.9:1, mute 4.7:1 (AA for small text). --faint is for rules only. */
  --paper: #ffffff;
  --ink: #141414;
  --mute: #737373;
  --faint: #e4e4e4;
  --sans: 'Inter', 'Helvetica Neue', Helvetica, Arial, sans-serif;
  --mono: 'IBM Plex Mono', ui-monospace, Menlo, Consolas, monospace;
  /* Type scale */
  --t1: clamp(30px, 3.4vw, 48px);    /* project titles */
  --t2: clamp(22px, 2.2vw, 30px);    /* headings, project names, statements */
  --t3: clamp(20px, 1.9vw, 26px);    /* contact line */
  --t4: clamp(13.5px, 1.05vw, 15px); /* running text (mono) */
  --t5: clamp(11.5px, .9vw, 13px);   /* labels and meta (mono, uppercase) */
  --gutter: clamp(16px, 2vw, 28px);
  --ease: cubic-bezier(.2, .7, .1, 1);
}

/* Dark theme (About page). Soft near-black rather than pure black; contrast on #111:
   ink 16:1, mute 7.1:1. */
html.theme-dark {
  --paper: #111111;
  --ink: #ededed;
  --mute: #a3a3a3;
  --faint: #2f2f2f;
  color-scheme: dark;
}

*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; background: var(--paper); }
body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font: 400 var(--t4)/1.6 var(--mono);
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}
img { display: block; max-width: 100%; height: auto; }
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; text-transform: inherit; letter-spacing: inherit; background: none; border: 0; padding: 0; cursor: pointer; }
::selection { background: var(--ink); color: var(--paper); }
:focus-visible { outline: 1px solid var(--ink); outline-offset: 3px; }

.mono {
  font-family: var(--mono); font-weight: 400; font-size: var(--t5); line-height: 1.5;
  text-transform: uppercase; letter-spacing: .06em;
}
.mono b, .mono strong, b { font-weight: 600; }
a[href^='mailto:'] { text-transform: none; letter-spacing: .02em; }
.muted { color: var(--mute); }

/* Titles and names: Inter Bold, uppercase, set tight. */
.site-header, .intro-name, .sheet h1, .entry .t, .note h2, .pager strong,
.cv-block > h2, .about .lede, .contact-line {
  font-family: var(--sans); font-weight: 700; letter-spacing: -0.005em;
}
.site-header, .intro-name, .sheet h1, .entry .t, .note h2, .pager strong,
.cv-block > h2 { text-transform: uppercase; line-height: 1.05; }

/* ---------- Header ---------- */
.site-header {
  position: sticky; top: 0; z-index: 20;
  display: flex; justify-content: space-between; align-items: baseline; gap: 1em;
  padding: 14px var(--gutter) 12px;
  background: var(--paper);
  font-size: clamp(14px, 1.1vw, 16px);
}
.site-header nav { display: flex; gap: 1.6em; }
/* Hover underline draws in from the left; on leave it carries on out to the right. */
.site-header a {
  padding-bottom: .12em;
  background: linear-gradient(currentColor, currentColor) right bottom / 0 1px no-repeat;
  transition: background-size .45s cubic-bezier(.22, .61, .36, 1);
}
.site-header a:hover, .site-header a:focus-visible {
  font-style: italic;
  background-position: left bottom; background-size: 100% 1px;
}
.site-header a[aria-current] { font-style: italic; }
.site-header::after {
  content: ''; position: absolute; left: var(--gutter); right: var(--gutter); bottom: 0;
  border-bottom: 1px solid var(--ink);
}

/* ---------- Footer ---------- */
.site-footer {
  display: grid; grid-template-columns: 1fr auto 1fr; gap: 1em; align-items: baseline;
  margin: 18vh var(--gutter) 0; padding: 10px 0 18px;
  border-top: 1px solid var(--ink);
}
.site-footer > :nth-child(2) { text-align: center; }
.site-footer > :last-child { text-align: right; }
.site-footer a:hover { color: var(--mute); }

/* ---------- Home ---------- */
/* One screen: images either side of a centre spine, index at the top of the spine. */
.page-home { height: 100vh; height: 100svh; overflow: hidden; display: flex; flex-direction: column; }
.home { position: relative; flex: 1; min-height: 0; display: flex; flex-direction: column; }
.stage-home {
  --g: var(--gutter);
  position: relative; flex: 1; min-height: 0; overflow: hidden;
  margin-top: 14px; touch-action: pan-x;
}
.slot {
  position: absolute; left: 0; top: 0; display: block;
  will-change: transform, width;
}
.stage-home.ready .slot { transition: transform 1s var(--ease), width 1s var(--ease), opacity .7s var(--ease); }
.slot img { width: 100%; height: auto; }

/* Cropped images (see `crop` in data.js): the wrapper shows only part of the image. */
.crop { display: block; position: relative; overflow: hidden; width: 100%; }
.crop > img { position: absolute; max-width: none; height: auto; }
.slot.past { cursor: pointer; }
.slot.past:hover { opacity: .8 !important; }
.slot.current { z-index: 2; }

.home-foot {
  display: grid; grid-template-columns: 1fr auto 1fr; gap: 1em; align-items: baseline;
  margin: 0; padding: 8px var(--gutter) 12px;
}
.home-foot > :nth-child(2) { text-align: center; }
.home-foot > :last-child { text-align: right; }
.home-foot a:hover { color: var(--mute); }

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

/* ---------- Projects: catalogue ---------- */
.catalogue-page {
  display: grid; grid-template-columns: repeat(12, 1fr); gap: 0 var(--gutter);
  padding: 22px var(--gutter) 0; position: relative; z-index: 1;
}
.filters { grid-column: 1 / span 4; align-self: start; position: sticky; top: 80px; }
.filters > button { padding: 0; }
.filters > button:hover { color: var(--mute); }
.filters ul { list-style: none; margin: 10px 0 0; padding: 0; }
.filters li button { padding: 1px 0; color: var(--mute); }
.filters li button:hover { color: var(--ink); }
.filters li button[aria-pressed='true'] { color: var(--ink); font-style: italic; }

#catalogue { grid-column: 5 / -1; display: grid; grid-template-columns: 1fr 1fr; gap: 0 var(--gutter); align-items: start; }
.cat-col h2 { margin: 0 0 14px; padding-bottom: 10px; border-bottom: 1px solid var(--faint); font-size: var(--t5); font-weight: 400; color: var(--mute); }
/* Each entry reads like a row in a table of contents: number, name, then mono details. */
.entry {
  display: grid; grid-template-columns: 4.5ch 1fr; gap: 0 1ch;
  padding: 14px 0 26px; border-bottom: 1px solid var(--faint);
}
.entry .no { font-weight: 600; padding-top: .35em; }
.entry .lines { display: flex; flex-direction: column; }
.entry .t { font-size: var(--t2); margin-bottom: 10px; }
.entry .t em { font-style: italic; }
.entry .lines > span:not(.t) {
  font-family: var(--mono); font-size: var(--t5); line-height: 1.6;
  text-transform: uppercase; letter-spacing: .06em;
}
.entry .cat { color: var(--mute); }
.entry:hover .t, .entry:focus-visible .t { font-style: italic; }

/* Images scattered behind the list while a project is hovered */
/* Hover images are shown in one treatment: black and white, multiplied onto the paper so white
   backgrounds disappear. Full colour returns on the project page. */
.scatter { position: fixed; inset: 0; z-index: 0; pointer-events: none; mix-blend-mode: multiply; }
.scatter img { filter: grayscale(1) contrast(1.08); }
.scatter img.knockout { filter: url(#knockout) grayscale(1); }
.scatter ~ .site-footer { position: relative; z-index: 1; }
.scatter img { position: absolute; height: auto; opacity: 0; transition: opacity .45s var(--ease); }
.scatter.on img { opacity: 1; }
[hidden] { display: none !important; }

/* ---------- Project page ---------- */
.project {
  display: grid; grid-template-columns: minmax(0, 1fr) clamp(300px, 30vw, 460px);
  gap: var(--gutter); padding: 18px var(--gutter) 0; align-items: start;
}
.plates { display: grid; gap: clamp(40px, 7vw, 110px); }
.plate { margin: 0; scroll-margin-top: 90px; }
.plate img { width: 100%; background: var(--faint); }
.plate.narrow img { width: auto; max-width: 100%; max-height: 86vh; }
.plate figcaption { display: flex; gap: 1.2em; margin-top: 8px; }

.sheet { position: sticky; top: 80px; max-height: calc(100vh - 96px); overflow: auto; padding-bottom: 20px; scrollbar-width: none; }
.sheet::-webkit-scrollbar { display: none; }
.sheet .no { margin: 0 0 10px; }
.sheet h1 { margin: 0; font-size: var(--t1); line-height: 1; }
.sheet .sub { margin: 8px 0 0; font-family: var(--mono); font-size: var(--t5); text-transform: uppercase; letter-spacing: .06em; }
.facts { display: grid; grid-template-columns: minmax(11ch, max-content) 1fr; gap: 2px 1em; margin: 22px 0 0; padding-top: 10px; border-top: 1px solid var(--ink); }
.facts dt { color: var(--mute); }
.facts dd { margin: 0; }
.sheet .text { margin-top: 22px; }
.sheet .text p { margin: 0 0 .8em; }
.plates-index { list-style: none; margin: 24px 0 0; padding: 10px 0 0; border-top: 1px solid var(--ink); }
.plates-index a { display: grid; grid-template-columns: 4ch 1fr; gap: .6em; padding: 1px 0; color: var(--mute); transition: color .2s; }
.plates-index a:hover, .plates-index a.on { color: var(--ink); }
.plates-index a.on span:last-child { font-style: italic; }

.pager { display: flex; justify-content: space-between; gap: 1em; margin: 14vh var(--gutter) 0; padding-top: 10px; border-top: 1px solid var(--ink); }
.pager a { display: block; max-width: 48%; }
.pager a:last-child { text-align: right; margin-left: auto; }
.pager strong { display: block; margin-top: 6px; font-size: var(--t2); letter-spacing: -0.005em; }
.pager a:hover strong { font-style: italic; }

/* Drawings whose white background is filtered out (see `knockout` in data.js) */
/* clip-path trims the outermost pixels, which the filter can render as a hairline when scaled. */
img.knockout { filter: url(#knockout); clip-path: inset(1%); background: none !important; }
.frame:has(> img.knockout) { background: none; }

/* Text between images */
.note { max-width: 64ch; padding-top: 12px; border-top: 1px solid var(--ink); }
.note h2 { margin: 0 0 18px; font-size: var(--t2); }
.note p { margin: 0 0 1em; }

/* Stop-motion sequence scrubber */
.sequence { margin: 0; }
.sequence .stage { position: relative; aspect-ratio: 3 / 2; background: var(--faint); overflow: hidden; }
.sequence .stage img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; opacity: 0; }
.sequence .stage img.on { opacity: 1; }

/* ---------- About ---------- */
.about { display: grid; grid-template-columns: repeat(12, 1fr); gap: 0 var(--gutter); padding: 0 var(--gutter); }
.about .lede {
  grid-column: 1 / span 8; margin: 9vh 0 11vh;
  font-size: var(--t2); line-height: 1.15;
}
.about .portrait { grid-column: 10 / span 3; grid-row: 1; align-self: start; margin: 9vh 0 11vh; }
.about .portrait img { width: 100%; aspect-ratio: 1; object-fit: cover; object-position: 50% 40%; background: var(--faint); }
.about .lede { grid-row: 1; }
.cv-block { grid-column: 1 / -1; display: grid; grid-template-columns: subgrid; margin-bottom: 9vh; }
.cv-block > h2 {
  grid-column: 1 / span 5; margin: 0 0 6px; padding-bottom: 6px; border-bottom: 1px solid var(--ink);
  font-size: var(--t2);
}
.cv-row { grid-column: 1 / -1; display: grid; grid-template-columns: subgrid; padding: 12px 0; border-bottom: 1px solid var(--faint); }
.cv-row:last-child { border-bottom: 0; }
.cv-row .when { grid-column: 1 / span 2; }
.cv-row .what { grid-column: 3 / span 6; }
.cv-row .what p { margin: 6px 0 0; }
.cv-row .aside { grid-column: 9 / span 4; }
/* Expandable detail under an experience entry */
.cv-more { margin-top: 14px; border-bottom: 1px solid var(--faint); }
.cv-more details { border-top: 1px solid var(--faint); }
.cv-more summary {
  display: flex; justify-content: space-between; gap: 1em;
  padding: 7px 0; cursor: pointer; list-style: none;
}
.cv-more summary::-webkit-details-marker { display: none; }
.cv-more summary::after { content: '+'; }
.cv-more details[open] summary::after { content: '−'; }
.cv-more summary:hover { color: var(--mute); }
.cv-more .body { padding: 0 0 12px; }
.cv-more .body p { margin: 0 0 .6em; }

.work-list { list-style: none; margin: 8px 0 0; padding: 0; }
.work-list li { padding: 3px 0; border-top: 1px dotted var(--faint); outline-offset: 2px; }
.work-list .row { display: grid; grid-template-columns: 1fr auto; gap: 1em; }
/* Hovering (or focusing / tapping) a project slides its description open beneath it. */
.work-list .desc {
  display: grid; grid-template-rows: 0fr; opacity: 0;
  transition: grid-template-rows .45s var(--ease), opacity .3s ease;
}
.work-list .desc > div { overflow: hidden; }
.work-list .desc p {
  margin: 0; padding: 6px 0 10px; max-width: 62ch;
  font-size: var(--t4); line-height: 1.6; text-transform: none; letter-spacing: 0;
}
.work-list .desc p + p { padding-top: 0; }
.work-list .desc p.meta { font-size: var(--t5); text-transform: uppercase; letter-spacing: .06em; color: var(--mute); }
.work-list li:hover .desc, .work-list li:focus-within .desc { grid-template-rows: 1fr; opacity: 1; }
.work-list li:hover .row > :first-child, .work-list li:focus-within .row > :first-child { font-style: italic; }
.work-list li:first-child { border-top: 0; }
.skills { grid-column: 3 / -1; display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--gutter); padding-top: 12px; }
.skills h3 { margin: 0 0 4px; font-size: inherit; color: var(--mute); }
.skills ul { list-style: none; margin: 0; padding: 0; }
.contact-line { grid-column: 3 / -1; padding-top: 12px; font-size: var(--t3); line-height: 1.3; }
.contact-line a { border-bottom: 1px solid currentColor; }
.contact-line a:hover { font-style: italic; }

/* ---------- Responsive ---------- */
@media (max-width: 900px) {
  .filters { grid-column: 1 / -1; position: static; margin-bottom: 26px; }
  #catalogue { grid-column: 1 / -1; }
  .project { grid-template-columns: 1fr; }
  .sheet { position: static; max-height: none; order: -1; }
  .plates-index { display: none; }
  .cv-row .when { grid-column: 1 / span 3; }
  .cv-row .what { grid-column: 4 / -1; }
  .cv-row .aside { grid-column: 4 / -1; margin-top: 6px; }
  .skills, .contact-line { grid-column: 1 / -1; }
  .about .lede { grid-column: 1 / -1; }
  .about .portrait { grid-column: 1 / -1; grid-row: auto; width: min(60%, 340px); margin: 0 0 9vh; }
}
@media (max-width: 640px) {
  #catalogue { grid-template-columns: 1fr; }
  .cat-col + .cat-col { margin-top: 18px; }
  /* Phones: email left, LinkedIn right; the title is dropped to keep one line. */
  .site-footer, .home-foot { grid-template-columns: 1fr auto; }
  .site-footer > :nth-child(2), .home-foot > :nth-child(2) { display: none; }
  .cv-block > h2 { grid-column: 1 / -1; }
  .cv-row .when, .cv-row .what, .cv-row .aside { grid-column: 1 / -1; }
  .skills { grid-template-columns: 1fr 1fr; }
}

/* ---------- Page transitions ----------
   Between pages the content fades out and the next page fades in while the header holds still.
   Browsers with cross-document view transitions do this natively; others get the JS fallback
   in app.js (html.pt-fallback). */
@view-transition { navigation: auto; }
:root { --pt-ease: cubic-bezier(.22, .61, .36, 1); }
.site-header { view-transition-name: site-header; }
::view-transition-group(site-header) { animation-duration: .6s; animation-timing-function: var(--pt-ease); }
::view-transition-old(site-header), ::view-transition-new(site-header) { animation-duration: .6s; }
/* The old page lifts away and dissolves; the new one fades in a beat later, settling upward. */
::view-transition-old(root) { animation: pt-out .5s var(--pt-ease) both; }
::view-transition-new(root) { animation: pt-in .8s var(--pt-ease) .3s both; }
@keyframes pt-out { to { opacity: 0; transform: translateY(-10px); } }
@keyframes pt-in { from { opacity: 0; transform: translateY(14px); } }

/* Between light and dark pages (type "theme", set in app.js): the old page stays put while the
   new one fades in over it, so the colours blend straight from one to the other, no flash. */
html:active-view-transition-type(theme)::view-transition-old(root) { animation: pt-hold 1s linear both; }
html:active-view-transition-type(theme)::view-transition-new(root) { animation: pt-theme-in 1s var(--pt-ease) both; }
html:active-view-transition-type(theme)::view-transition-old(site-header) { animation: pt-hold 1s linear both; }
html:active-view-transition-type(theme)::view-transition-new(site-header) { animation: pt-theme-in 1s var(--pt-ease) both; }
@keyframes pt-hold { to { opacity: 1; } }
@keyframes pt-theme-in { from { opacity: 0; } }
html.pt-to-theme body, html.pt-to-theme .site-header { transition: background-color .45s var(--pt-ease), color .45s var(--pt-ease); }

html.pt-fallback body > :not(.site-header) { animation: pt-in .8s var(--pt-ease) both; }
html.pt-leaving body > :not(.site-header) { opacity: 0; transform: translateY(-10px); transition: opacity .45s var(--pt-ease), transform .45s var(--pt-ease); }

/* Staggered entrance: each page's main pieces arrive one after another (--i set in app.js). */
.enter { animation: rise .9s var(--pt-ease) both; animation-delay: calc(var(--enter-base, .35s) + var(--i, 0) * 70ms); }

/* Opening title (homepage, once per visit) */
.intro {
  position: fixed; inset: 0; z-index: 100;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 1.2em;
  background: var(--paper); text-align: center; padding: var(--gutter);
  transition: opacity .9s var(--pt-ease), visibility 0s linear .9s;
}
/* The name floats above the cover so it can fly to the header while the cover fades.
   Its line-height and tracking match the header exactly, so scaling it down lands it flush. */
.intro-name {
  position: fixed; z-index: 101; margin: 0; white-space: nowrap;
  font-size: clamp(40px, 6vw, 96px); line-height: 1.05; letter-spacing: -0.005em;
  transform-origin: 0 0;
  animation: rise 1.1s var(--pt-ease) .15s both;
}
.intro-name.fly { transition: transform 1.15s cubic-bezier(.65, 0, .35, 1); }
.intro-name-slot { height: calc(clamp(40px, 6vw, 96px) * 1.05); width: 100%; }
.intro-role { animation: rise 1.1s var(--pt-ease) .45s both; }
.intro.out { opacity: 0; visibility: hidden; transition: opacity .9s var(--pt-ease) .15s, visibility 0s linear 1.05s; }
.intro.out .intro-role { opacity: 0; transition: opacity .3s ease; }
/* Hide the header name while its stand-in is on screen. */
.intro-on .site-header .brand { visibility: hidden; }@keyframes rise { from { opacity: 0; transform: translateY(16px); } }

/* Images fade in once loaded instead of popping in. */
img.pending { opacity: 0; }
img.loaded { transition: opacity .7s var(--pt-ease); }

@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*) { animation: none !important; }
  *, *::before, *::after { animation: none !important; transition: none !important; }
}
