/* ============================================================
   Hannah Norford — Portfolio
   Design: an image-led index. The home page is nothing but the
   work, spaced with real air between pieces — no titles, no
   labels, just the pictures. A project's name only appears once
   you open it, set small, quiet, and close to the image it
   belongs to. One serif voice (Newsreader) throughout, black on
   white, no accent colour.
   ============================================================ */

:root {
  --paper: #ffffff;
  --ink: #000000;
  --ink-soft: #555550;
  --ink-faint: #93938d;
  --line: #dcdcd7;

  --font: "Noto Serif", "Iowan Old Style", Georgia, serif;

  --u: calc(100vw / 375); /* mobile unit: 1px at the 375px reference, scaling with the screen */
  --nav-h: 56px;
  --edge: 20px; /* a constant ~5mm frame around every page, at every size */
  --figma-edge: 3px; /* the Figma frame's own margin is already baked into each item's x/y%, so this just needs to be a hairline, not a second frame */
  --max: 1440px;

  /* the nav sits at the same spot on every page: the midpoint between
     picture 1's bottom edge (4.37%) and picture 2's top edge (5.57%) on
     the home canvas — i.e. 4.97% of it, which scales with width as
     (100vw - 2*edge) * 6281/1117; 0.27946 is that ratio (5.6222) times
     4.97%. Other pages reuse this same value so the bar never jumps
     between pages, and add --nav-gap-clearance to their top padding so
     content still starts clear of it. */
  --nav-gap-top: calc(var(--figma-edge) + (100vw - 2 * var(--figma-edge)) * 0.27946);
  --nav-gap-clearance: calc(var(--nav-gap-top) + var(--nav-h) / 2);

  /* the same top offset picture 1 sits at on the home canvas (its own
     top:0.18%, scaled the same way as --nav-gap-top: 0.18% of
     6281/1117 = 0.01012 of the canvas width) — for the About page hero,
     which starts flush with this instead of clearing the nav, since nav
     overlays picture 1 there too rather than sitting below it. */
  --img1-top: calc(var(--figma-edge) + (100vw - 2 * var(--figma-edge)) * 0.01012);
}

/* ---------- reset ---------- */
* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
/* the hover-only tooltips (.work-item__title, .work-item__award-note)
   lay out full-width with white-space:nowrap even while invisible,
   which can push the document a little wider than the viewport —
   clip that instead of letting the page slide sideways to reveal it. */
html, body { overflow-x: hidden; }
body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font);
  font-weight: 500;
  font-size: 15px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}
img { max-width: 100%; display: block; }
a { color: inherit; text-decoration: none; }
h1, h2, h3, p, ul { margin: 0; }
ul { padding: 0; list-style: none; }
button { font: inherit; color: inherit; background: none; border: 0; padding: 0; cursor: pointer; }

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

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

.skip-link {
  position: absolute;
  left: var(--edge);
  top: -60px;
  background: var(--ink);
  color: var(--paper);
  padding: 10px 16px;
  font-size: 13px;
  transition: top .2s ease;
  z-index: 200;
}
.skip-link:focus-visible { top: 12px; }

/* ---------- layout shell ---------- */
.page {
  min-height: 100vh;
  padding-bottom: calc(var(--nav-h) + 24px);
}
/* the About page manages its own bottom spacing (--img1-top, matching
   its own top border) so the CV grid can sit flush against the true
   bottom of the viewport instead of leaving this generic gap after it */
.page--about { padding-bottom: 0; }

.container {
  max-width: var(--max);
  margin: 0 auto;
  padding: 0 var(--edge);
}

/* ---------- site nav: fixed, sitting at the exact gap between the first
   two pictures on the home canvas — same distance from both — and staying
   there (fixed) as the page scrolls, on every page, so the bar never
   jumps between pages. Other pages' top padding uses --nav-gap-clearance
   to stay clear of it at every width. ---------- */
/* Item positions are percentages of the 1117px Figma frame width, matching
   the exact x-position of each nav item (Hannah Norford, Work, About,
   Contact, Instagram, LinkedIn) in Frame 2. */
.site-nav {
  position: fixed;
  left: 0; right: 0;
  top: var(--nav-gap-top);
  transform: translateY(-50%);
  z-index: 100;
  height: var(--nav-h);
  padding: 0 var(--figma-edge);
  box-sizing: border-box;
  background: none;
  border: none;
  font-family: var(--font);
  pointer-events: none;
}
/* Only the actual links/button re-enable pointer-events — not
   .site-nav__inner itself, which spans the full nav-bar rectangle at
   every width and would otherwise swallow hover/clicks on whatever sits
   beneath its empty space (e.g. work-list-page rows the nav happens to
   overlap at narrower viewports). */
.site-nav__inner {
  position: relative;
  width: 100%;
  height: 100%;
}

.site-nav__home,
.site-nav__toggle,
.site-nav__links a,
.site-nav__social a {
  pointer-events: auto;
}

/* nav items are positioned by left/right % of the same 1117px Figma
   frame width as everything else — so the text itself needs to scale
   at that same rate, or a fixed 12px stops shrinking with those %
   anchors below ~1117px and the nav items run into each other (most
   visibly LinkedIn/Instagram on the right edge). Floored at 9.8px,
   matching the ~914px transition point used across the rest of the
   site, below which it holds at the floor (stacked mobile takes over
   before that anyway, see the 850px media query). Capped at 12px —
   its original fixed size — so it stops growing past the 1117px
   reference width instead of ballooning on a wide/fullscreen monitor. */
.site-nav__home,
.site-nav__social a {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  white-space: nowrap;
  font-size: clamp(9.8px, calc(100vw * 12 / 1117), 12px);
}
/* Desktop keeps About at its original, individually-positioned spot
   (nth-child left:%% below) rather than the flex space-between layout —
   that behavior is mobile-only now (see the media query). */
.site-nav__links a {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  white-space: nowrap;
  font-size: clamp(9.8px, calc(100vw * 12 / 1117), 12px);
}
.site-nav__links a:nth-child(1) { left: 16.74%; }
.site-nav__links a:nth-child(2) { left: 24.62%; }
.site-nav__links a:nth-child(3) { left: 32.86%; }

/* Instagram/LinkedIn are each pinned by their own right%, matching the
   right edge of a picture below (Instagram → the right-column pictures at
   67.14%+32.05%=99.19%, so right:0.81%; LinkedIn → Studio Shorimoma at
   58.64%+32.50%=91.14%, so right:8.86%) — a percentage anchor rather than
   a fixed gap, since a fixed px gap between two % anchored elements drifts
   out of alignment as the canvas scales. */

.site-nav a[aria-current="page"] { font-style: oblique 8deg; }
.site-nav__home,
.site-nav__links a,
.site-nav__social a { transition: font-style .15s ease; }
.site-nav__links a:hover,
.site-nav__social a:hover,
.site-nav__home:hover { font-style: oblique 8deg; }

.site-nav__toggle { display: none; }

/* ---------- work list (home): exact proportional reproduction of Figma Frame 2 ---------- */
/* Frame 2 is 1117 x 6281px; every item below is positioned as a percentage
   of that frame, so the whole composition scales together and keeps its
   exact relative positions, sizes and overlaps at any width. */
.work-frame {
  width: 100%;
  padding: var(--figma-edge);
  box-sizing: border-box;
}
.work-list {
  position: relative;
  width: 100%;
  aspect-ratio: 1117 / 6281;
}

.work-item {
  position: absolute;
  display: block;
}
.work-item__frame {
  display: block;
  width: 100%;
  height: 100%;
  overflow: hidden;
}
.work-item img,
.work-item video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: opacity .3s ease;
}
/* video's own intrinsic aspect ratio can win out over a percentage
   height against an aspect-ratio-derived auto parent (a gap plain img
   doesn't hit), inflating the tile — pin it to the tile's box directly
   instead of relying on that percentage resolving correctly. */
.work-item video {
  position: absolute;
  inset: 0;
}
.work-item:hover img,
.work-item:hover video { opacity: .82; }

.work-item__title {
  position: absolute;
  left: 0;
  top: 100%;
  margin-top: 6.5px;
  font-size: 12px;
  color: var(--ink);
  opacity: 0;
  white-space: nowrap;
  transition: opacity .25s ease;
}
/* the nxt work-item carries two wordings ("For" only dropped on the
   mobile Figma export's tighter title) — desktop shows the full title
   by default, and the media query below swaps which one is visible */
.work-item__title--mobile-only { display: none; }
.work-item:hover .work-item__title,
.work-item:focus-visible .work-item__title {
  opacity: 1;
}

.work-item__award-note {
  position: absolute;
  left: 100%;
  top: -4px;
  margin-left: 6.5px;
  font-size: 12px;
  line-height: 1.5;
  color: var(--ink);
  opacity: 0;
  white-space: nowrap;
  transition: opacity .25s ease;
}
.work-item:hover .work-item__award-note,
.work-item:focus-visible .work-item__award-note {
  opacity: 1;
}

.work-item__mobile-img { display: none !important; }
.work-item__mobile-frame { display: none; }

.site-footer {
  position: relative;
  margin-top: -40px;
  padding-top: 24px;
  padding-right: var(--figma-edge);
  padding-bottom: 10px;
  /* matches .site-nav__home's own left, so "Hannah Norford" lines up
     with the logo above it */
  padding-left: calc(0.90% + 3.346px);
  font-size: 11px;
  line-height: 13px;
}
.site-footer p:last-child { margin: 0; }
/* the year's right edge lines up with "Contact"'s right edge (same
   reference width the nav itself uses), rather than the page's own
   outer edge like the rest of the frame's right-anchored elements */
.site-footer__year {
  position: absolute;
  top: 24px;
  right: 61.106%;
}

/* below ~700px the proportional canvas gets too cramped to use; fall back
   to a simple full-width stacked column instead */
@media (max-width: 850px) { /* stacked mobile fallback: only below phone width now — everything from here up to desktop scales fluidly instead, see .project-meta-row__col etc. */
  :root { --nav-h: calc(56 * var(--u)); --edge: calc(20 * var(--u)); --figma-edge: calc(3 * var(--u)); }
  /* the calc()-based nav position assumes the desktop canvas's fixed
     6281/1117 proportions, which don't hold once the layout below
     switches to a stacked column — every page (including home, which
     now has its own curated mobile image stack rather than a reflowed
     desktop grid) falls back to a plain top bar instead, since a
     mid-screen fixed nav would overlap whatever content scrolls under
     it either way. */
  .site-nav { top: calc(var(--nav-h) / 2 - calc(10 * var(--u))); }
  /* every non-home page's top padding follows suit, since it was only
     ever sized to clear that plain top bar, not the home page's formula */
  .project-hero { padding-top: calc(var(--nav-h) + calc(24 * var(--u))); }
  .work-item__title { font-size: min(10.3px, calc(10.3 * var(--u))); margin-top: calc(5 * var(--u)); }
  .work-item__title--typography-size { font-size: min(10.2px, calc(10.2 * var(--u))); }
  .work-item__title--desktop-only { display: none; }
  .work-item__title--mobile-only { display: inline; }
  .work-item__award-note { font-size: min(10px, calc(10 * var(--u))); margin-left: calc(5 * var(--u)); }
  .contact { padding-top: calc(var(--nav-h) + calc(36 * var(--u))); }
  /* .work-frame needs no top padding of its own — the mobile Figma
     frame's first item is already placed low enough (y=134 of 5565) to
     clear the plain top-bar nav on its own. */
  /* mobile home page: its own curated stack (own picks, own order,
     own crops — not a reflow of the desktop grid), positioned the same
     way the desktop canvas is — absolute percentages of a fixed-ratio
     frame (here the 390x5565 mobile Figma frame) — via a parallel set
     of --m* custom properties set per item, so the desktop inline
     left/top/width/height survive untouched above 700px. */
  /* the 3px figma-edge hairline is dropped on the sides here — the
     --mx/--mw values below are rescaled so each item's own percentage
     already bakes in a 10px edge margin against the full viewport. */
  /* trims 20px off the page's overall bottom length, mobile only —
     desktop keeps its original spacing untouched */
  .work-frame { padding-left: 0; padding-right: 0; margin-bottom: calc(-20 * var(--u)); }
  .work-list { aspect-ratio: 390 / 5565; }
  .work-item {
    position: absolute !important;
    left: var(--mx) !important;
    top: var(--my) !important;
    width: var(--mw) !important;
    height: var(--mh) !important;
  }
  .work-item--has-mobile-alt > img,
  .work-item--has-mobile-alt > .work-item__frame { display: none; }
  .work-item__mobile-frame {
    display: block !important;
    width: 100%;
    height: 100%;
    overflow: hidden;
  }
  .work-item__mobile-img {
    display: block !important;
    width: 100%;
    height: 100%;
    object-fit: cover;
  }
  /* a slight zoom-in for mobile-alt images whose crop otherwise looks
     too loose at the tile's own aspect ratio — scoped per-image via
     this modifier rather than changing every mobile-alt image */
  .work-item__mobile-img--zoom {
    max-width: none !important;
    width: 142% !important;
    height: 142% !important;
    position: relative;
    left: -21%;
    top: calc(-21% - calc(45 * var(--u)));
  }
}

/* ---------- project detail ---------- */
.project-hero {
  padding: calc(var(--nav-gap-clearance) + 24px) var(--edge) 0;
}
.project-hero img { width: 100%; height: auto; }

.project-caption {
  padding: 14px var(--edge) 44px;
  max-width: 60ch;
}
.project-caption h1 {
  font-weight: 500;
  font-size: clamp(17px, 1.8vw, 22px);
  line-height: 1.3;
}
.project-caption__subtitle {
  font-style: italic;
  color: var(--ink-soft);
  font-size: 14px;
  margin-top: 4px;
}
.project-caption__award {
  display: block;
  font-weight: 600;
  font-size: 13px;
  margin-top: 8px;
}

.project-gallery {
  display: flex;
  flex-direction: column;
  gap: clamp(28px, 5vh, 72px);
  padding: 0 var(--edge) 48px;
}
.project-gallery img { width: 100%; height: auto; }

/* A Figma export of this page showed its own nav sitting flush at the
   top (not in the home canvas's picture-gap spot), with a compact
   meta/description row directly under it — so this page type gets its
   own plain top-bar nav position instead of the site-wide one, and the
   gallery frame's own top padding is sized to that same bar instead of
   --nav-gap-clearance. */
body.project-page .site-nav { top: calc(var(--figma-edge) + var(--nav-h) / 2); }
@media (max-width: 850px) { /* stacked mobile fallback: phone-only, see note above */
  body.project-page .site-nav { top: calc(var(--nav-h) / 2 - calc(10 * var(--u))); }
}
/* the gallery frame below already ends in its own figma-edge bottom
   border, matching its top — so drop the site-wide .page bottom
   clearance (meant for pages with a pager row) here instead of
   stacking a second, larger gap underneath it. */
body.project-page .page,
body.is-home .page { padding-bottom: 0; }

/* ---------- project gallery grid (2-column staggered, plus a meta row) ----------
   Matches a Figma export 1:1: a 1117-wide canvas (same reference frame
   as the home grid) at its own native aspect ratio (1117/2186), with
   the meta row and each image placed by the exact %s that export's own
   pixel rects work out to — left/width against the frame's width,
   top/height against its height, the same convention the home canvas
   uses. */
.project-gallery-grid-frame {
  padding: 12px var(--figma-edge);
}
.project-gallery-grid {
  position: relative;
  width: 100%;
  aspect-ratio: 1117 / 2186;
}
.project-meta-row__col {
  position: absolute;
  /* scales with the canvas (1117px is the Figma reference width this
     whole grid's %-based positions are measured against) so the text
     keeps its exact desktop line breaks at every width, Readymag-style,
     instead of reflowing below 700px like it used to. Matches the site
     nav's own clamp (9.8px to 12px) so subpage text reads at the same
     size as the nav rather than a point smaller. Below ~914px the text
     is pinned at the 9.8px floor and wraps on its own like it used to
     past 700px, since going any smaller stops being legible on a phone
     (stacked mobile takes over before that anyway, see the 850px media
     query). Capped at 12px so it stops growing past the 1117px
     reference width instead of ballooning on a wide/fullscreen monitor.
     Unitless line-height keeps the same leading ratio at any size. */
  font-size: min(12px, calc(100vw * 12 / 1117));
  line-height: 1.182;
}
.project-meta-row__col p { margin: 0; }
@media (min-width: 851px) { .project-body-text .project-meta-row__col p { white-space: nowrap; } }
.project-gallery-grid__item {
  position: absolute;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* desktop has no on-page title (the meta row carries that context
   instead) — only the mobile Figma export shows one, above the intro
   text, so it's hidden by default and switched on below 700px. */
.project-mobile-title { display: none; }
/* the opposite of .project-body-text__break below: a handful of line
   breaks are only wanted in the mobile paragraph's own wrap, not the
   desktop column's, so this stays off until the mobile override. */
.project-mobile-break { display: none; }
/* same on/off logic as .project-mobile-break, but for a hyphen character
   that must stay inline with its word instead of forcing its own line */
.project-mobile-hyphen { display: none; }
/* lets a paragraph carry two separate wordings — a desktop version and a
   mobile version — instead of one shared text reflowed at each width */
.project-desktop-text { display: block !important; }
.project-mobile-text { display: none !important; }
/* same pair, inline — for swapping just a word/phrase inline rather than
   a whole paragraph */
.project-desktop-text-inline { display: inline !important; }
.project-mobile-text-inline { display: none !important; }

@media (max-width: 850px) { /* stacked mobile fallback: phone-only, see note above */
  .project-desktop-text { display: none !important; }
  .project-mobile-text { display: inline !important; }
  .project-desktop-text-inline { display: none !important; }
  .project-mobile-text-inline { display: inline !important; }
  /* the desktop version starts flush with the nav's own top border since
     the fixed nav overlays it there by design (matching the Figma
     export); once the layout below stacks into a single column that
     same starting point would run the first line of text straight under
     the nav instead, so give it normal clearance here instead. */
  .project-gallery-grid-frame { padding-top: calc(var(--nav-h) + calc(24 * var(--u))); padding-left: var(--edge); padding-right: var(--edge); padding-bottom: var(--edge); }
  .project-mobile-title {
    display: block;
    /* 23px gap from the nav's own bottom edge to the title's own top —
       +13px here since the nav itself currently renders 10px higher
       than --nav-h alone accounts for.
       10px: matches the frame override below and the pictures' own edge,
       so title/text and pictures all share one flush left/right inset. */
    margin: calc(var(--nav-h) + calc(13 * var(--u))) calc(10 * var(--u)) 0;
    /* 13px matches the export's own title-glyph width (~165px) far more
       closely than 14px (~176px) does; scales with the viewport from
       that same 375px reference like the rest of the mobile type. */
    font-size: min(14px, calc((100vw - calc(20 * var(--u))) * 14 / 355));
    font-weight: 600;
  }
  /* the title itself now clears the nav, so the frame right after it
     only needs the small gap beneath the title, not a second full
     nav-clearance on top of that — 10px left/right so text and pictures
     share the same edge as the navbar. */
  .project-mobile-title + .project-gallery-grid-frame { padding-top: calc(25 * var(--u)); padding-left: calc(10 * var(--u)); padding-right: calc(10 * var(--u)); }
  /* !important needed: the desktop inline aspect-ratio (for its
     percentage-based absolute positioning) otherwise beats this reset,
     forcing the grid to the Figma frame's tall portrait ratio and
     leaving a huge empty gap below the stacked mobile content */
  /* also resets the desktop-only negative margin-bottom script.js applies
     (crops trailing whitespace below the last image on the wide
     Figma-canvas layout) — irrelevant and actively harmful once the grid
     becomes a stacked flex column here, where it has no excess canvas
     space to crop and would instead overlap real stacked content. */
  .project-gallery-grid { aspect-ratio: auto !important; display: flex; flex-direction: column; gap: calc(24 * var(--u)); margin-bottom: 0 !important; }
  .project-meta-row__col {
    position: static;
    width: auto !important;
    margin-bottom: calc(16 * var(--u));
  }
  /* the two text columns read as one flowing paragraph on the mobile
     export rather than two visually separate blocks, and the Book/
     Awards and Credits columns stay side by side instead of stacking. */
  /* no gap: the export renders this as one continuous flowing
     paragraph, not two visually separate blocks */
  /* the two desktop columns read as one truly continuous paragraph on
     mobile — inline, not stacked blocks, so the wrap runs straight
     through the boundary between them with no forced line start */
  .project-body-text .project-meta-row__col,
  .project-body-text .project-meta-row__col p { display: inline; }
  /* min-height:0 overrides the implicit min-height:auto flex items get
     by default, which otherwise floors this at its content's natural
     (font-default) line height and silently ignores a smaller
     line-height override on the text inside */
  /* every line box gets an invisible "strut" sized to its block
     container's OWN line-height — since that was still inheriting
     body's default (22.5px) here, it was silently flooring every line
     regardless of the smaller line-height set on the text itself below */
  .project-body-text { font-weight: 500; min-height: 0; line-height: min(15.5px, calc(15.5 * var(--u))); }
  /* the desktop copy's hard breaks (and the "every-" hyphen they force)
     are tuned to its own narrow 24%-wide column — at mobile's full
     width they'd chop lines short, so let the browser wrap naturally */
  .project-body-text__break { display: none; }
  .project-mobile-break { display: block; }
  .project-mobile-hyphen { display: inline; }
  /* shaves ~14px off this phrase so it fits the 351px column on one
     line as requested; desktop's own (unspaced) column isn't affected
     since this class has no effect outside this media query */
  .mobile-tight { letter-spacing: -0.017em; }
  /* !important: overrides the inner span's own desktop-tuned -0.01em
     inline style, which otherwise wins over a class at any width */
  .mobile-tight-1 { letter-spacing: -0.04em !important; }
  .mobile-tight-2 { letter-spacing: -0.03em !important; }
  .mobile-tight-3 { letter-spacing: -0.032em !important; }
  .mobile-tight-4 { letter-spacing: -0.027em !important; }
  .mobile-tight-5 { letter-spacing: -0.017em !important; }
  .mobile-tight-6 { letter-spacing: -0.03em !important; }
  .mobile-tight-7 { letter-spacing: -0.02em !important; }
  .mobile-tight-8 { letter-spacing: -0.036em !important; }
  .mobile-tight-9 { letter-spacing: -0.015em !important; }
  .mobile-tight-10 { letter-spacing: -0.025em !important; }
  .mobile-tight-11 { letter-spacing: -0.031em !important; }
  .mobile-tight-12 { letter-spacing: -0.022em !important; }
  .mobile-tight-13 { letter-spacing: -0.01em !important; }
  .mobile-tight-14 { letter-spacing: -0.017em !important; }
  .mobile-tight-16 { letter-spacing: -0.023em !important; }
  .mobile-tight-17 { letter-spacing: -0.005em !important; }
  .mobile-tight-18 { letter-spacing: -0.012em !important; }
  .mobile-tight-19 { letter-spacing: -0.007em !important; }
  .mobile-tight-20 { letter-spacing: -0.013em !important; }
  .mobile-tight-21 { letter-spacing: -0.099em !important; }
  .mobile-tight-22 { letter-spacing: -0.082em !important; }
  .mobile-tight-23 { letter-spacing: -0.079em !important; }
  .mobile-tight-24 { letter-spacing: -0.088em !important; }
  .mobile-tight-25 { letter-spacing: -0.061em !important; }
  .mobile-tight-26 { letter-spacing: -0.03em !important; }
  .mobile-tight-27 { letter-spacing: -0.059em !important; }
  .mobile-tight-28 { letter-spacing: -0.045em !important; }
  .mobile-tight-29 { letter-spacing: -0.058em !important; }
  .mobile-tight-30 { letter-spacing: -0.077em !important; }
  .mobile-tight-31 { letter-spacing: -0.07em !important; }
  .mobile-tight-32 { letter-spacing: -0.071em !important; }
  .mobile-tight-33 { letter-spacing: -0.068em !important; }
  .mobile-tight-34 { letter-spacing: -0.072em !important; }
  /* mobile-only +1% tracking for two specific lines, no !important
     needed since there's no competing inline style on the same element */
  .mobile-loose-1 { letter-spacing: 0.01em; }
  .mobile-loose-2 { letter-spacing: 0.005em; }
  /* !important: overrides a competing mobile-tight-N class or inline
     desktop letter-spacing on the same element, unlike mobile-loose-1/2 */
  .mobile-loose-3 { letter-spacing: 0.005em !important; }
  .mobile-loose-4 { letter-spacing: 0.002em !important; }
  /* zeroes out a desktop-tuned inline letter-spacing at mobile only */
  .mobile-normal { letter-spacing: normal !important; }
  /* blanket override: letter-spacing is 0 everywhere on mobile now,
     regardless of any inline style or mobile-tight-N/mobile-loose-N
     class on the element — wins over both since it's !important and
     comes later in the cascade than those class rules above. This
     will reflow some of the hand-tuned line breaks above 375px's own
     breakpoints, since those relied on this tracking to fit. */
  .project-mobile-text, .project-mobile-text *,
  .project-body-text, .project-body-text * {
    letter-spacing: normal !important;
  }
  /* nudges the laptop crop up 15px within its frame, mobile only —
     desktop keeps its own inline top:-3% untouched */
  .nxt-laptop-crop { width: 112% !important; height: auto !important; aspect-ratio: 4096 / 2731; object-fit: fill !important; left: calc(-3.7% - calc(20 * var(--u))) !important; top: calc(-17.3% + calc(5 * var(--u))) !important; }
  /* the desktop-tuned +42px phone-centering offset is way too much at
     mobile's much narrower frame width and exposes the frame's left
     edge — fall back to the plain -8% crop, which already covers the
     frame fine at mobile's own proportions */
  .nxt-phone-crop { left: -8% !important; }
  /* centers the bowls horizontally within the frame at mobile's zoom level */
  .foto10-crop { left: -27.7% !important; transform: none !important; }
  /* crops out the plain tablecloth strip at the bottom of this photo,
     mobile only, so less blank space sits against the next picture */
  .foto11-crop { width: 135% !important; height: 135% !important; left: -17.5% !important; top: -33% !important; }
  /* zoomed all the way back out, mobile only — the minimum scale that
     still covers the frame with no gaps, matching desktop's own crop */
  .pictoright-crop { height: 100% !important; top: 0 !important; transform: scale(1.17); transform-origin: 50% 60%; }
  .pain-posters { aspect-ratio: 446.1 / 303; object-fit: cover; }
  .foto7-crop { width: 100% !important; height: 100% !important; object-position: 50% 95% !important; left: 0 !important; top: 0 !important; }
  /* .project-meta-row__col's own base rule (font-size:11px) directly
     matches these children and would otherwise win over the wrapper's
     font-size by inheritance rules — this descendant selector has
     higher specificity, so it's the one that actually reaches the text */
  .project-body-text .project-meta-row__col {
    margin-bottom: 0;
    /* shrinks below the 375px mobile Figma reference width the same way
       the desktop copy now grows above its own reference — capped at
       13px (its original fixed size) so it never exceeds that past
       375px, matching every breakpoint tuned this session exactly from
       375px up through the 850px handoff to the fluid desktop rule. */
    font-size: min(14px, calc((100vw - calc(20 * var(--u))) * 14 / 355));
    line-height: min(17px, calc((100vw - calc(20 * var(--u))) * 17 / 355));
    white-space: nowrap;
  }
  /* the second column always starts its own line, so a wider phone can never
     pull its first words onto the end of the first column's last line */
  .project-body-text .project-meta-row__col + .project-meta-row__col::before { content: ""; display: block; }
  /* "more" sits left-aligned directly under the paragraph on mobile,
     not right-aligned like its desktop position — !important since it
     also carries an inline text-align:right for that desktop position */
  /* the export's own glyph rects put only ~7px between the paragraph
     and "more" (not the grid's usual 24px gap) — pull it up to match */
  .project-body-text + .project-meta-row__col { text-align: left !important; margin-top: calc(-23 * var(--u)); margin-bottom: calc(6 * var(--u)); font-size: min(14px, calc((100vw - calc(20 * var(--u))) * 14 / 355)); line-height: min(23px, calc((100vw - calc(20 * var(--u))) * 23 / 355)); }
  /* the "more" link's own 16px margin-bottom plus the grid's 24px gap
     already lands on the export's ~40px gap above this row; adding
     another 16px below reaches that same ~40px before the first image. */
  .project-meta-row__pair { display: flex; gap: calc(16 * var(--u)); margin-bottom: calc(11 * var(--u)); }
  .project-meta-row__pair .project-meta-row__col { margin-bottom: 0; flex: 1; min-width: 0; font-size: min(14px, calc((100vw - calc(20 * var(--u))) * 14 / 355)); line-height: 1.192; }
  /* the second column's left edge is pinned to line up with "Work" in the
     mobile nav (left:49.886% of site-nav__inner, itself inset 10px from
     the viewport) at every width, by giving the first column a fixed
     (non-flex) width equal to that target position minus the 16px gap
     and this frame's own 10px left inset, instead of splitting the pair
     50/50 via flex:1 */
  .project-meta-row__pair .project-meta-row__col:first-child { flex: 0 0 calc(49.886vw - calc(25.977 * var(--u))); }
  /* "Book" sits directly above the award list, no extra gap, unlike
     the desktop-tuned 8px */
  .project-meta-row__pair .project-meta-row__col:first-child p + p { margin-top: 0 !important; }
  /* 30px between images: the grid's usual 24px gap plus 6px extra.
     Pictures no longer need their own edge-extension — the frame itself
     is now 10px inset (matching the navbar's edge), same as the title
     and body text, so pictures line up flush at their plain 100% width. */
  .project-mobile-title ~ .project-gallery-grid-frame .project-gallery-grid__item {
    margin-bottom: calc(6 * var(--u));
  }
  /* 100px total from the last picture to the end of the page (this
     margin plus the grid's 24px flex gap, the footer row's own height,
     and the frame's 10px bottom padding). Deliberately NOT :last-of-type:
     that matches per tag name among ALL siblings (not just gallery
     items), so a one-off tag like <picture> among <img> siblings can
     wrongly count as "last of its type" even mid-gallery — this class
     is applied explicitly to the true last item instead. */
  .project-mobile-title ~ .project-gallery-grid-frame .project-gallery-grid__item--last { margin-bottom: calc(53.5 * var(--u)); }
  /* the closing "Hannah Norford / 2026" row sits only 10px above the
     end of the page — override its own margin-bottom and the frame's
     usual bottom padding, which would otherwise total ~36px here */
  .project-mobile-title ~ .project-gallery-grid-frame { padding-bottom: calc(10 * var(--u)); }
  /* "Hannah Norford" and "2026" sit on one line, flush to opposite
     edges, instead of the desktop-only absolute left/right positions */
  .project-footer-pair { display: flex; justify-content: space-between; margin-bottom: 0; }
  .project-footer-pair .project-meta-row__col { margin-bottom: 0; }
  .project-gallery-grid__item {
    position: static !important;
    width: 100% !important;
    height: auto !important;
  }
  /* cropped-picture frames: iOS Safari won't resolve the picture's percentage
     height/top against a height that only comes from aspect-ratio, so the crop
     came out different on a real phone. Absolutely positioned children resolve
     those percentages against the frame's final box in every browser, and give
     the same geometry (the frame is already the picture's top-left origin). */
  div.project-gallery-grid__item[style*="aspect-ratio"] { position: relative !important; left: auto !important; top: auto !important; }
  div.project-gallery-grid__item[style*="aspect-ratio"] > img,
  div.project-gallery-grid__item[style*="aspect-ratio"] > video { position: absolute !important; }
  /* Alone/Together cover: picture shifted 10px to the right inside its frame */
  .alleine-cover-crop { left: calc(-4% + 5 * var(--u)) !important; }
  /* portrait photos on this page are pinned to a fixed 441px height at
     mobile — must come after the general item reset above (line ~537),
     since equal-specificity !important rules resolve by source order */
  .foto-portrait { height: calc(441 * var(--u)) !important; }
}
/* The hand-placed mobile line breaks/hyphens above are tuned for the
   ~355px text column of the 375px-wide mobile Figma reference. Past
   ~480px (comfortably wider than any real phone) that same column is
   much wider, so those exact breaks would leave large ragged gaps
   instead of reading as intentional — past this point, up through the
   700px mobile/desktop cutoff, let the browser wrap the text naturally
   instead, same as it already does above 700px. */
@media (min-width: 99999px) and (max-width: 1px) { /* disabled */
  .project-mobile-break { display: none; }
  .project-mobile-hyphen { display: none; }
}

.project-pager {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 16px;
  padding: 24px var(--edge) 56px;
  border-top: 1px solid var(--line);
  font-size: 13px;
  flex-wrap: wrap;
}
.project-pager a { transition: font-style .15s ease; }
.project-pager a:hover { font-style: oblique 8deg; }
.project-pager__all { color: var(--ink-faint); }

/* ---------- work list page (text list, not the home grid) ----------
   Matched to readymag.com/designs/6315390/work/'s own list 1:1: same
   column x-positions (measured at its 1200px reference width, so
   expressed here as a % of width like the About page's readymag match),
   same tight row rhythm, same 12px serif type — and it starts flush at
   the top (--img1-top, same as the About hero row) rather than clearing
   the nav like a normal content page, so nav overlays it exactly as it
   overlays readymag's own list. No horizontal edge padding, so these
   percentages map straight to the viewport like readymag's own do. */
.work-list-page {
  position: relative;
  padding: var(--img1-top) 0 48px;
  max-width: var(--max);
  margin: 0 auto;
  font-weight: 400;
}
.work-list-page__row {
  display: grid;
  grid-template-columns: 50.49% 16.6% 1fr;
  font-size: 13px;
  line-height: 18px;
  transition: font-style .15s ease;
}
.work-list-page__title { grid-column: 2; }
.work-list-page__title--tight { line-height: 14px; }
.work-list-page__title--tight ~ .work-list-page__year { line-height: 14px; }
.work-list-page__year { grid-column: 3; }
@media (hover: hover) { .work-list-page__row:hover { font-style: oblique 8deg; } }
.work-list-page__row--header { font-weight: 600; margin-bottom: 12px; }
@media (hover: hover) { .work-list-page__row--header:hover { font-style: normal; } }

.work-list-page__preview {
  position: absolute;
  left: var(--edge);
  top: var(--img1-top);
  width: clamp(160px, 16vw, 240px);
  aspect-ratio: 3 / 4;
  overflow: hidden;
  visibility: hidden;
  background: #fff;
  pointer-events: none;
  z-index: 1;
  contain: paint;
}
.work-list-page__preview.is-visible { visibility: visible; }
.work-list-page__preview-img {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: none;
}
.work-list-page__preview-img.is-active { display: block; }

@media (max-width: 850px) { /* stacked mobile fallback: phone-only, see note above */
  /* tighter edge margins matching the nav's own 10px inset */
  .work-list-page { padding-top: calc(var(--nav-h) + calc(33 * var(--u))); padding-left: calc(10 * var(--u)); padding-right: calc(10 * var(--u)); }
  .work-list-page__row {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    font-size: min(12px, calc(12 * var(--u)));
    line-height: min(17px, calc(17 * var(--u)));
  }
  .work-list-page__title { padding-right: calc(10 * var(--u)); }
  .work-list-page__year {
    display: block;
    flex: 0 0 auto;
    white-space: nowrap;
  }
  .work-list-page__row--header { margin-bottom: calc(14 * var(--u)); }
  /* on touch there's no hover, so rows are tapped instead (see script.js)
     and the preview sits fixed in the bottom-left corner of the screen
     rather than inline next to the list. */
  .work-list-page__preview {
    position: fixed;
    left: calc(10 * var(--u));
    top: auto;
    bottom: calc(10 * var(--u));
    width: clamp(calc(110 * var(--u)), 32vw, calc(150 * var(--u)));
  }
}

/* ---------- about ----------
   Structure matches readymag.com/designs/6315390/about/ — the row of
   text/photo/contact and, below it, a 3-column CV grid (Experience /
   Education / Clients there; Awards / Education / empty here, since
   Hannah has no clients list to put in that third slot rather than
   inventing one) — with Hannah's own content and, per her request, this
   page's own rhythm rather than a literal copy of the reference's: the
   hero row starts at the exact top offset the first home-page picture
   starts at (--img1-top, so nav overlays it exactly like it overlays
   that picture, rather than being pushed below the nav like every other
   page), the two paragraphs run one after another in the same column
   instead of the reference's split second row, the CV grid sits flush
   against the bottom of the page (mirroring --img1-top as the bottom
   border) instead of at the reference's fixed offset below the hero
   row, and CV entry type is that reference's own sizes (10.5px, weight
   400, pure black) rather than the rest of this site's — an intentional
   one-off match, not a change to the site's own type scale. */
.about-rm {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  padding: var(--img1-top) 0 var(--img1-top);
  max-width: var(--max);
  margin: 0 auto;
  font-weight: 400;
}
/* the bio's mobile line breaks are tuned to a width the desktop breaks
   don't match — hidden by default, shown only at mobile (mirrors the
   project-mobile-break/project-mobile-hyphen pattern used on work pages) */
.about-rm__mobile-break { display: none; }
.about-rm__mobile-hyphen { display: none; }

/* row1's height now follows the text column (normal flow), since at
   18px with these forced line breaks it's the tallest thing here —
   taller than the fixed-size photo — rather than the fixed height that
   matched just the photo before. Photo/contact stay absolutely
   positioned at top:0, safely inside whatever height that ends up
   being. */
.about-rm__row1 {
  position: relative;
  width: 100%;
  flex: 0 0 auto;
}
.about-rm__text {
  margin-left: 0.98%;
  width: 48.51%;
  /* same fluid-with-a-floor approach as .project-meta-row__col: scales
     against this section's own 1440px max-width (--max) so the bio's
     line breaks stay put down to ~1080px (1440*12/16), then holds at a
     12px floor below that (stacked mobile takes over before that
     anyway, see the 850px media query). Capped at 16px, its original
     fixed size, so it stops growing past 1440px on a wide monitor. */
  font-size: min(16px, calc(100vw * 16 / 1440));
  line-height: 1.17;
}
.about-rm__text p { margin: 0 0 16px; }
/* desktop About: everything scales together with the window width, in both
   directions — type, offsets and the gaps between blocks all derive from
   the same 1440px reference, so the layout (and its line breaks) never
   changes shape at any size. */
@media (min-width: 851px) {
  .about-rm { max-width: none; }
  .about-rm__text { font-size: min(16px, calc(100vw * 16 / 1440)); }
  .about-rm__text p { white-space: nowrap; margin-bottom: calc(100vw * 16 / 1440); }
  .about-rm__text p:last-child { margin-bottom: 0; }
  .about-rm__text [style*="--dls"] { letter-spacing: var(--dls); }
  .about-rm .about-rm__photo { left: calc(50.17% + 100vw * 125 / 1440 - 1px); width: calc(100vw * 226 / 1440 + 2px); }
  .about-rm .about-rm__contact { left: calc(83.59% + 100vw * 51 / 1440); }
  .about-rm .about-rm__cv { margin-top: calc(100vw * 349 / 1440); }
  .about-rm .about-rm__col h2, .about-rm .about-rm__entry p { white-space: nowrap; font-size: min(13px, calc(100vw * 13 / 1117)); }
}
.about-rm__text p:last-child { margin-bottom: 0; }
.about-rm__photo {
  position: absolute;
  left: calc(50.17% + 125px);
  top: 0;
  width: clamp(120px, 18.83vw, 226px);
  aspect-ratio: 226 / 321;
  overflow: hidden;
}
.about-rm__photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.about-rm__contact {
  position: absolute;
  left: calc(83.59% + 51px);
  top: 0;
  display: flex;
  flex-direction: column;
  font-size: 13px;
  line-height: 1.222;
  white-space: nowrap;
}
.about-rm__contact a { transition: font-style .15s ease; }
.about-rm__contact a:hover { font-style: oblique 8deg; }

.about-rm__cv {
  margin-top: auto;
  display: grid;
  grid-template-columns: 16.14% 33%;
  gap: 0;
  margin-left: 0.98%;
}
.about-rm__col h2 {
  font-weight: 600;
  font-size: 13px;
  line-height: 1.222;
  margin: 0 0 2.15% 0;
}
.about-rm__entry { margin-bottom: 2.15%; }
.about-rm__entry p a { transition: font-style .15s ease; }
.about-rm__entry p a:hover { font-style: oblique 8deg; }
.about-rm__entry p {
  font-size: 13px;
  line-height: 1.222;
  margin: 0;
}

@media (max-width: 850px) { /* stacked mobile fallback: phone-only, see note above */
  /* matches the mobile Figma export: compact 12px body copy (not the
     desktop 16px), a full-width portrait instead of a small fixed box,
     and tighter 12px edge margins matching the nav's own inset. */
  .about-rm {
    display: block;
    min-height: auto;
    padding-top: calc(var(--nav-h) + calc(33 * var(--u)));
    padding-left: calc(12 * var(--u));
    padding-right: calc(12 * var(--u));
    /* desktop intentionally uses the lighter 400 weight here, but at
       mobile it read as a different typeface next to the project pages
       (which use the site's normal 500 weight) — match it, mobile only */
    font-weight: 500;
  }
  .about-rm__row1 { position: static; height: auto; }
  .about-rm__text, .about-rm__photo, .about-rm__contact {
    position: static;
    width: auto;
    margin-left: 0;
  }
  /* same fluid-from-375px-reference approach as the project pages'
     mobile body copy — grows with the viewport instead of sitting flat
     at 12px all the way to the 850px handoff. */
  .about-rm__text { font-size: min(13px, calc((100vw - calc(24 * var(--u))) * 13 / 351)); line-height: 1.35; }
  .about-rm__text p { white-space: nowrap; }
  .about-rm__text span[style*="0.01em"] { letter-spacing: inherit !important; }
  .about-rm__text p [style*="--ls"] { letter-spacing: var(--ls); }  /* the desktop copy's hard breaks are tuned to its own narrow 48.5%-wide
     column — at mobile's full width they'd just chop lines short, so let
     the browser wrap the paragraph naturally instead. */
  .about-rm__break { display: none; }
  .about-rm__mobile-break { display: inline; }
  .about-rm__mobile-hyphen { display: inline; }
  .about-rm__photo { margin-top: calc(34 * var(--u)); width: calc(100% + 3px); margin-left: -1.5px; height: auto; aspect-ratio: 367 / 433; }
  .about-rm__contact { margin-top: calc(42 * var(--u)); white-space: normal; font-size: min(13px, calc(100vw * 13 / 375)); }
  .about-rm__cv { margin-top: calc(44 * var(--u)); grid-template-columns: 1fr; margin-left: 0; }
  .about-rm__col h2, .about-rm__entry p { font-size: min(13px, calc(100vw * 13 / 375)); }
  .about-rm__col { margin-bottom: calc(24 * var(--u)); }
  .about-rm__entry { margin-bottom: calc(16 * var(--u)); }
}

/* ---------- contact ---------- */
.contact {
  min-height: calc(100vh - var(--nav-h));
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: calc(var(--nav-gap-clearance) + 36px) var(--edge) 48px;
}
.contact h1 {
  font-weight: 500;
  font-size: clamp(24px, 4vw, 40px);
  margin-bottom: 24px;
}
.contact__list {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.contact__list a {
  font-weight: 500;
  font-size: clamp(20px, 4vw, 34px);
  line-height: 1.2;
  display: inline-block;
}
.contact__list a { transition: font-style .15s ease; }
.contact__list a:hover { font-style: oblique 8deg; }
.contact__note {
  margin-top: 24px;
  max-width: 46ch;
  font-style: italic;
  color: var(--ink-soft);
  font-size: 14px;
}

/* ---------- mobile nav ---------- */
.site-nav__menu { display: contents; }

@media (max-width: 850px) { /* stacked mobile fallback: phone-only, see note above */
  /* Hannah Norford and Contact anchor the bar's two edges with a
     matching 12px gap; Work/About keep their exact-percentage spacing
     from the 375px-wide mobile Figma reference. */
  .site-nav { padding: calc(1 * var(--u)) calc(10 * var(--u)); }
  .site-nav__inner {
    position: relative;
    display: flex;
    align-items: center;
  }
  .site-nav__home {
    position: static;
    transform: none;
    white-space: nowrap;
  }
  .site-nav__toggle { display: none; }
  /* mobile-only: About stays exactly centered between Work and Contact
     at any mobile width via flex space-between, instead of desktop's
     individually-positioned nth-child left:%% values above. */
  .site-nav__links {
    position: absolute;
    top: 50%;
    left: 49.886%;
    right: 0;
    transform: translateY(-50%);
    display: flex;
    justify-content: space-between;
    align-items: center;
  }
  /* the flex layout above spaces these out rather than pinning them by
     %, so unlike the absolute-positioned desktop/tablet nav there's no
     overlap risk here — matches the rest of the mobile type: scales
     below the 375px reference, capped at its original 12px from there
     up through the 850px handoff. */
  .site-nav__home,
  .site-nav__social a,
  .site-nav__links a {
    font-size: min(13px, calc(100vw * 13 / 375));
  }
  .site-nav__links a {
    position: static !important;
    left: auto !important;
    top: auto !important;
    transform: none !important;
    white-space: nowrap;
  }
  .site-nav__social { display: none; }
  .site-footer__year { right: calc(10 * var(--u)); }
  .site-footer { padding-left: calc(10 * var(--u)); }
}

/* ---------- desktop: every page scales as one piece with the window width ----------
   Type and the fixed-px offsets next to it follow the same reference width as
   the %-positioned layout they sit in (1117px for the Figma-frame pages, 1440px
   for the About / work-list / contact pages), in both directions, with no
   floors or caps — so spacing, line breaks and proportions stay identical at
   any size. Mobile (<=850px) keeps its own stacked rules above. */
@media (min-width: 851px) {
  .work-item__title { margin-top: calc(100vw * 6.5 / 1117); }
  .work-item__award-note { top: calc(100vw * -4 / 1117); margin-left: calc(100vw * 6.5 / 1117); }
  .site-footer { margin-top: calc(100vw * -40 / 1117); padding-top: calc(100vw * 24 / 1117); padding-bottom: calc(100vw * 10 / 1117); }
  .site-footer__year { top: calc(100vw * 24 / 1117); }

  .work-list-page { max-width: none; }
  .work-list-page__row--header { margin-bottom: calc(100vw * 12 / 1440); }
  .work-list-page__preview { width: 16vw; }

  .contact h1 { margin-bottom: calc(100vw * 24 / 1440); }
  .contact__list { gap: calc(100vw * 4 / 1440); }
  .contact__note { margin-top: calc(100vw * 24 / 1440); }
}

/* cropped-picture frames, every width: place the oversized picture absolutely inside
   its frame (the frame is itself positioned, so it is the origin). Safari resolves the
   picture's percentage height/left against a frame whose height only comes from
   aspect-ratio / a percentage unreliably when the picture is just position:relative,
   which showed these crops stretched and shifted on desktop Safari too. */
div.project-gallery-grid__item[style*="aspect-ratio"] > img,
div.project-gallery-grid__item[style*="aspect-ratio"] > video { position: absolute !important; }

/* hairline picture outlines: a border thinner than one physical pixel gets
   dropped on some edges by iPhone Safari (the bottom one went missing), so on
   high-density screens use exactly one device pixel instead */
@media (-webkit-min-device-pixel-ratio: 3), (min-resolution: 3dppx) {
  .has-outline { border-width: 0.4px !important; }
}
@media (-webkit-min-device-pixel-ratio: 2) and (-webkit-max-device-pixel-ratio: 2.99), (min-resolution: 2dppx) and (max-resolution: 2.99dppx) {
  .has-outline { border-width: 0.5px !important; }
}

/* mobile: draw the hairline outline as an outer ring instead of a border —
   iPhone Safari drops the thin bottom border on the last picture, a shadow
   ring is painted with the same geometry on every side */
@media (max-width: 850px) {
  .has-outline { border: 0 !important; box-shadow: 0 0 0 var(--hair, 0.5px) #000; }
}

/* mobile: cropped pictures are wider than their frames (the frame clips them).
   iPhone Safari can still let that clipped overflow be scrolled into view
   sideways — showing stray picture edges/lines next to the page — so clip it
   hard at every level. */
@media (max-width: 850px) {
  html, body { overflow-x: clip; max-width: 100%; }
  /* not on .project-gallery-grid itself: its edge is exactly where the left/right
     outline ring of full-width outlined pictures sits, and clipping there cuts it off */
  .page, main, .project-gallery-grid-frame { overflow-x: clip; }
  /* the cropped-picture frames clip their own oversized pictures; deliberately no
     `contain: paint` here — it threw off the crop position inside the frame on iPhone Safari */
  div.project-gallery-grid__item[style*="aspect-ratio"] { overflow: hidden !important; }
}

/* Safari's Live Text can read the lettering inside photos (poster text etc.) and
   lay it over the picture as selectable text; after a back-navigation it can end
   up painted visibly on the page. Photos aren't meant to be selected anyway. */
img, video {
  -webkit-user-select: none;
  user-select: none;
  -webkit-user-drag: none;
  -webkit-touch-callout: none;
}
