/* ===========================================================================
   Small enhancements layered on top of the original theme.
   Loaded after theme.css so these win.
   =========================================================================== */
@import url("https://fonts.googleapis.com/css2?family=Sometype+Mono:wght@400;500;600;700&display=swap");

/* Site-wide font: Sometype Mono (replaces the theme's Fira Mono). */
:root {
  --bs-body-font-family: "Sometype Mono", monospace;
  --bs-font-monospace: "Sometype Mono", monospace;

  /* ---- Type scale (single source of truth) --------------------------------
     Every font-size on the site maps to one of these tokens. Don't hand-write
     raw font-size values — pick the nearest token so sizes never drift again.
     Defined here in custom.css (always loaded on the frontend); app.css uses
     them too via var(). See docs/DESIGN-NOTES.md → "Type scale". */
  --fs-xs: 0.625rem;    /* ~10px  — tiny caption: size guide, care list, edition meta */
  --fs-sm: 0.72rem;     /* ~11.5px — labels & meta: breadcrumb, mobile nav, cart variant */
  --fs-base: 0.75rem;   /* 12px   — body copy (canonical; matches the John Godfrey page) */
  --fs-md: 0.875rem;    /* 14px   — headings & nav */
  --fs-lg: 1rem;        /* 16px   — price / emphasis */
  /* --fs-xl (1.15rem/~18px) RETIRED 2026-07: only 2 uses and just 2px off --fs-lg,
     so it never earned a step on the scale. Both uses (the product title and a
     mobile menu link) now use --fs-lg. Don't reintroduce it. */
  --fs-2xl: 1.4rem;     /* ~22px  — cart panel title */
  --fs-3xl: 2rem;       /* 32px   — large section heading · shop title · 404 code */

  /* ---- Tracking (letter-spacing) ------------------------------------------
     ONE value for every tracked label. Small uppercase labels had drifted across
     nine values (0.02–0.1em) for what is really a single role — 0.04 vs 0.05 vs
     0.06 is imperceptible. Titles, filters and body copy stay `normal`; only
     small uppercase labels get this. Don't hand-write tracking values. */
  --track-label: 0.06em;

  /* ---- Line-height --------------------------------------------------------
     Three roles instead of the ten values that had accumulated. */
  --lh-body: 1.9;    /* body copy and most labels */
  --lh-tight: 1.35;  /* captions / multi-line titles in cards */
  --lh-flat: 1;      /* headings and single-line UI text */
}

/* Subtle film-grain texture over the whole page — fits the movie-poster studio.
   Grayscale fractal noise generated inline (no asset), fixed to the viewport so
   it reads like real grain rather than scrolling with the page, and
   non-interactive so it never blocks clicks. Multiply = fine dark specks on the
   paper; barely-there opacity keeps it a texture, not a filter. */
body.wp-theme-johngodfrey::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 9998;
  pointer-events: none;
  opacity: 0.35;
  mix-blend-mode: multiply;
  filter: contrast(1.1); /* punch up the grain's light/dark specks by 10% */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='fg'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.82' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='200' height='200' filter='url(%23fg)'/%3E%3C/svg%3E");
  background-size: 200px 200px;
}
body,
body * {
  font-family: "Sometype Mono", monospace !important;
}
/* Form controls don't inherit font-weight from the page, so browser-default 400
   leaked into a few buttons (the cart's × and −/+) while everything else is 500. */
button,
input,
select,
textarea {
  font-weight: inherit;
}

/* Tiled paper texture behind every page. Currently ambientCG Paper001 (CC0),
   with a 10% white wash over it. Other options in assets/: ambientcg-paper003,
   istockphoto-1469387639. Swap the url() + background-size to change. */
body {
  background-color: #f5f4ef;
  background-image:
    linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0.05)),
    url("/assets/ambientcg-paper001.webp");
  background-repeat: repeat;
  background-size: auto, 638px auto;
  background-attachment: fixed;
}

/* Honeypot — an off-screen bot-bait field. Kept in the layout (not display:none,
   which some bots skip) but out of sight and out of the tab order. Real users never
   see or fill it; a filled value flags the submission as spam server-side. */
.hp-field {
  position: absolute !important;
  left: -9999px !important;
  top: auto;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* Logo grain — same film-grain fill as the display titles (SHOP / JOHN GODFREY).
   The logo is normally a black SVG drawn as a background-image; here it becomes a
   MASK instead, so the "paint" behind it can be the noise-over-black rather than a
   flat fill. Tile stays 180px (as in the titles) so the specks are the same physical
   size, not scaled down to the 50px mark.

   Desktop only, and only the masthead logo: below 67.5rem the logo is already masked
   with a JS-flipped background-COLOUR (white over dark artwork), and the float-nav
   logo does the same — a background-image would paint over that flip and make the
   logo unreadable on dark posters. */
@media (min-width: 67.5rem) {
  .site-header .site-branding .johngodfrey-logo {
    background-color: #000;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='tg'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3CfeComponentTransfer%3E%3CfeFuncR type='linear' slope='4' intercept='-2.5'/%3E%3CfeFuncG type='linear' slope='4' intercept='-2.5'/%3E%3CfeFuncB type='linear' slope='4' intercept='-2.5'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23tg)' opacity='0.9'/%3E%3C/svg%3E");
    background-size: 120px 120px;
    background-repeat: repeat;
    -webkit-mask-image: url('data:image/svg+xml;utf8,<svg fill="white" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 512 512"><path d="M256,11C120.9,11,11,121.3,11,256s109.9,244.5,245,244.5v.5c135.2,0,245-109.9,245-245S391.1,11,256,11ZM256,466.5c-116.2,0-210.5-94.3-210.5-210.5S139.8,45.5,256,45.5s210.5,94.3,210.5,210.5-94.3,210.5-210.5,210.5Z"/><path d="M410.6,259.4l1-1h-124.5c-6.8,0-12.2,4.4-12.2,10.7s38.4,12.2,38.4,26.7v35.5c0,9.2-7.3,16.5-16.5,16.5h-46.7c-24.3,0-48.1-24.8-53-52-2.4-14.6-38.4-19.9-38.4-26.7s5.3-10.7,12.2-10.7h16c5.3,0,9.2-4.4,9.2-9.2v-37.9c0-31.1,29.7-52.5,56.9-52.5h30.6c15.1,0,32.6-1,32.6,30.6v3.4c0,15.6,12.6,28.2,28.2,28.2h10.2c15.6,0,29.7-12.6,29.7-28.2v-3.4c0-36.9-9.7-56.4-84.6-56.4h-84.1c-57.9,0-90.9,46.2-90.9,118.1v.4c-1.2,3.9-4.2,6.9-8.3,6.9h-16c-6.8,0-12.2,4.4-12.2,10.7s32.2,12.2,37.1,25.5c3.3,48.1,36.1,85.4,102.9,85.4h60.8c36.9,0,76.3-6.3,94.3-22.9v-60.3c0-14.6,39.4-20.4,39.4-26.7s-5.3-10.7-12.2-10.7Z"/></svg>');
    mask-image: url('data:image/svg+xml;utf8,<svg fill="white" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 512 512"><path d="M256,11C120.9,11,11,121.3,11,256s109.9,244.5,245,244.5v.5c135.2,0,245-109.9,245-245S391.1,11,256,11ZM256,466.5c-116.2,0-210.5-94.3-210.5-210.5S139.8,45.5,256,45.5s210.5,94.3,210.5,210.5-94.3,210.5-210.5,210.5Z"/><path d="M410.6,259.4l1-1h-124.5c-6.8,0-12.2,4.4-12.2,10.7s38.4,12.2,38.4,26.7v35.5c0,9.2-7.3,16.5-16.5,16.5h-46.7c-24.3,0-48.1-24.8-53-52-2.4-14.6-38.4-19.9-38.4-26.7s5.3-10.7,12.2-10.7h16c5.3,0,9.2-4.4,9.2-9.2v-37.9c0-31.1,29.7-52.5,56.9-52.5h30.6c15.1,0,32.6-1,32.6,30.6v3.4c0,15.6,12.6,28.2,28.2,28.2h10.2c15.6,0,29.7-12.6,29.7-28.2v-3.4c0-36.9-9.7-56.4-84.6-56.4h-84.1c-57.9,0-90.9,46.2-90.9,118.1v.4c-1.2,3.9-4.2,6.9-8.3,6.9h-16c-6.8,0-12.2,4.4-12.2,10.7s32.2,12.2,37.1,25.5c3.3,48.1,36.1,85.4,102.9,85.4h60.8c36.9,0,76.3-6.3,94.3-22.9v-60.3c0-14.6,39.4-20.4,39.4-26.7s-5.3-10.7-12.2-10.7Z"/></svg>');
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-position: center; mask-position: center;
  -webkit-mask-size: contain; mask-size: contain;
  }
}

/* Contact 8-ball GIF: its white background flickers in/out (animated-GIF frame
   disposal). Multiply-blend it onto the paper so white always drops out and the
   texture shows through consistently. */
.johngodfrey-contact-image-block .image-wrapper {
  mix-blend-mode: multiply;
  /* Let the ball bleed to the browser edge and shake freely — clip ONLY at the
     viewport (html already has overflow-x: clip), not at the wrapper box. the base sheet's
     overflow:hidden otherwise cuts the ball at the wrapper's edge mid-shake (visible
     in Firefox as a hard edge that isn't the browser edge). */
  overflow: visible;
}

/* After the 8-ball's initial shake, give it a subtle, endless hover — a gentle bob
   with a touch of sway. It's CHAINED onto the existing shake (magic8ballShake, 1.3s,
   defined in base.css): shake once, then the float starts at 1.3s and loops. Same
   selector as the base sheet so this wins on source order (custom.css loads last); the float
   is the LAST animation in the list, so it takes over the transform once the shake
   ends (its 0% frame matches the shake's resting end state for a seamless handoff).
   The animation stays on the <img>, NOT the multiply-blended .image-wrapper (a transform
   there would spawn a stacking context and break the white drop-out). */
/* A smooth figure-8 (∞) wander — x = 10·sinθ, y = -8·sin2θ, rotate = sinθ — sampled
   densely at 24 points (every 15° of θ) so the `linear` interpolation (below) hugs
   the curve tightly and the direction changes at each vertex are tiny: same path as
   before, just smoother travel. Runs `linear` so it flows through the points instead
   of easing to a stop at each one. Starts/ends at 0,0 to hand off cleanly from the
   shake. */
@keyframes eightball-float {
  0%   { transform: translate3d(0, 0, 0) rotate(0deg); }
  4%   { transform: translate3d(2.6px, -4px, 0) rotate(0.26deg); }
  8%   { transform: translate3d(5px, -6.9px, 0) rotate(0.5deg); }
  13%  { transform: translate3d(7.1px, -8px, 0) rotate(0.71deg); }
  17%  { transform: translate3d(8.7px, -6.9px, 0) rotate(0.87deg); }
  21%  { transform: translate3d(9.7px, -4px, 0) rotate(0.97deg); }
  25%  { transform: translate3d(10px, 0, 0) rotate(1deg); }
  29%  { transform: translate3d(9.7px, 4px, 0) rotate(0.97deg); }
  33%  { transform: translate3d(8.7px, 6.9px, 0) rotate(0.87deg); }
  38%  { transform: translate3d(7.1px, 8px, 0) rotate(0.71deg); }
  42%  { transform: translate3d(5px, 6.9px, 0) rotate(0.5deg); }
  46%  { transform: translate3d(2.6px, 4px, 0) rotate(0.26deg); }
  50%  { transform: translate3d(0, 0, 0) rotate(0deg); }
  54%  { transform: translate3d(-2.6px, -4px, 0) rotate(-0.26deg); }
  58%  { transform: translate3d(-5px, -6.9px, 0) rotate(-0.5deg); }
  63%  { transform: translate3d(-7.1px, -8px, 0) rotate(-0.71deg); }
  67%  { transform: translate3d(-8.7px, -6.9px, 0) rotate(-0.87deg); }
  71%  { transform: translate3d(-9.7px, -4px, 0) rotate(-0.97deg); }
  75%  { transform: translate3d(-10px, 0, 0) rotate(-1deg); }
  79%  { transform: translate3d(-9.7px, 4px, 0) rotate(-0.97deg); }
  83%  { transform: translate3d(-8.7px, 6.9px, 0) rotate(-0.87deg); }
  88%  { transform: translate3d(-7.1px, 8px, 0) rotate(-0.71deg); }
  92%  { transform: translate3d(-5px, 6.9px, 0) rotate(-0.5deg); }
  96%  { transform: translate3d(-2.6px, 4px, 0) rotate(-0.26deg); }
  100% { transform: translate3d(0, 0, 0) rotate(0deg); }
}

/* Override base.css's magic8ballShake: VIOLENT, fast, and thrown along the
   bottom-left ↔ top-right diagonal. Y is negated relative to X so each swing goes to
   bottom-left (-x,+y) then top-right (+x,-y). Big opening amplitude (~±36px / ±6.5deg)
   packed into the first ~15%, then a hard decay to rest. Same name → this later
   definition (custom.css loads last) wins. Speed set by the animation duration below. */
@keyframes magic8ballShake {
  0%   { transform: translate(0, 0) rotate(0deg); }
  3%   { transform: translate(-36px, 32px) rotate(-6.5deg); }
  6%   { transform: translate(34px, -30px) rotate(6deg); }
  9%   { transform: translate(-31px, 27px) rotate(-5.4deg); }
  12%  { transform: translate(27px, -24px) rotate(4.6deg); }
  15%  { transform: translate(-23px, 20px) rotate(-3.8deg); }
  19%  { transform: translate(18px, -16px) rotate(3deg); }
  24%  { transform: translate(-14px, 12px) rotate(-2.3deg); }
  30%  { transform: translate(10px, -9px) rotate(1.7deg); }
  38%  { transform: translate(-7px, 6px) rotate(-1.2deg); }
  47%  { transform: translate(5px, -4px) rotate(0.8deg); }
  58%  { transform: translate(-3px, 2.5px) rotate(-0.5deg); }
  71%  { transform: translate(1.8px, -1.5px) rotate(0.3deg); }
  85%  { transform: translate(-1px, 0.8px) rotate(-0.15deg); }
  100% { transform: translate(0, 0) rotate(0deg); }
}
.johngodfrey-contact-image-block.animate .image-wrapper img,
.johngodfrey-contact-image-block.animate .image-wrapper picture {
  /* Shake fill is left at the default `none` (not `both`): it ends on the resting
     frame — identical to the base state — so once it finishes the float is the only
     animation touching `transform`, avoiding a fill-vs-running override tug-of-war. */
  animation:
    magic8ballShake 1.6s ease-in-out,
    eightball-float 7s linear 1.6s infinite;
}
@media (prefers-reduced-motion: reduce) {
  .johngodfrey-contact-image-block.animate .image-wrapper img,
  .johngodfrey-contact-image-block.animate .image-wrapper picture { animation: none; }
}

/* Contact page: widen the text/form column 25% into the gap toward the 8-ball.
   The columns and the (right-aligned, column-scaled) 8-ball are left untouched —
   the widget simply overflows its column into the empty gap. Desktop only, where
   that gap exists; on smaller screens the layout stacks so 100% stays. */
@media (min-width: 992px) {
  .contact-row .widget-area { width: 125%; }
}

/* Very subtle lift on portfolio (slider) + edition items. drop-shadow (not
   box-shadow) so it follows the actual art — letterboxed/contained or
   transparent images get a shadow that hugs the image, not its box. */
.slider-item .image-wrapper img {
  filter: drop-shadow(0 3px 3px rgba(0, 0, 0, 0.24));
}

/* Poster load placeholder: a two-tone gradient in the poster's OWN prominent
   colours (--ph-c1/--ph-c2, set per item from its stored palette; a neutral pair
   when a piece has none) fills the reserved frame until the artwork paints, so
   fast horizontal scrolling never flashes an empty box. A slight dark wash keeps
   it reading as a dim stand-in rather than a finished design element. Poster
   galleries only - their art is opaque and full-bleed, so it covers the gradient
   on load; art/home galleries (.gallery--art) can hold transparent or contained
   pieces where a backing box would show, so they keep a plain frame. */
.gallery-page:not(.gallery--art) .slider-item .image-wrapper {
  background-image:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='pg'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.45' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23pg)'/%3E%3C/svg%3E"),
    linear-gradient(rgba(0, 0, 0, 0.22), rgba(0, 0, 0, 0.22)),
    linear-gradient(135deg, var(--ph-c1, #e9e7e0), var(--ph-c2, #d5d2c9));
  /* A distinct film grain over the two-tone gradient (overlay blend punches the
     light/dark specks) so the loading box reads like a grainy poster, not a flat
     swatch. */
  background-size: 160px 160px, cover, cover;
  background-repeat: repeat, no-repeat, no-repeat;
  background-blend-mode: overlay, normal, normal;
}
/* Don't flash the alt text over the placeholder before the pixels paint (the alt
   attribute stays for a11y/SEO; this only affects the not-yet-loaded state). */
.gallery-page:not(.gallery--art) .slider-item .image-wrapper img {
  color: transparent;
  font-size: 0;
}
/* Editions shadow: grainy instead of a smooth gradient — an SVG filter (defined in
   app.blade.php) rebuilds the drop shadow and speckles it with fractalNoise, to
   match the site's film-grain look. */
/* Editions shadow: grainy instead of a smooth gradient — an SVG filter (defined in
   app.blade.php) rebuilds the drop shadow and speckles it with fractalNoise, to
   match the site's film-grain look. */
.edition-card .shop-card__media img {
  filter: url(#fx-shadow-grain);
}

/* Editions cards get 10% more breathing room than the shop's
   clamp(2rem, 3.5vw, 4rem). Chained as .shop-card.edition-card because the base
   .shop-card rule sits LATER in this file at the same single-class specificity and
   would otherwise win on source order. */
.shop-card.edition-card {
  /* Modest padding (~10% over the shop base) — the floating image above provides
     the roominess now, so the earlier inflated padding is no longer needed. */
  padding: clamp(2.2rem, 3.85vw, 4.4rem);
}

/* Editions card = a roughly-square card with the caption pinned to the bottom and
   the artwork centred in the space above it. It's a flex column (the base .shop-card
   already is): the media box grows to fill everything above the caption, so the
   caption sits at the bottom; the image is capped by BOTH its width (--ed-img-w, the
   side air) and the available height (so a tall poster scales down to fit), then
   centred. `align-items: stretch` overrides the base `center` so the media box and
   full-width caption span the card. Scoped to .edition-card; shop grid is untouched. */
.shop-card.edition-card {
  /* Somewhat square, slightly tall so the tallest caption fits inside the aspect
     (otherwise a long caption pushes that card taller and the row stops aligning). */
  aspect-ratio: var(--ed-card-ratio, 4 / 5);
  align-items: stretch;
}
.edition-card .shop-card__media {
  aspect-ratio: auto;         /* drop the shop's 1/1 square */
  flex: 1 1 auto;             /* fill the space above the caption (base is position:relative) */
  min-height: 0;
}
.edition-card .shop-card__media img {
  --ed-img-w: 64%;           /* how much air sits beside the art (knob) */
  /* Absolute so the image is CONSTRAINED to the media box and can't push the card
     past its aspect ratio — a taller poster scales down within the box instead of
     stretching the card. inset:0 + margin:auto centres it both ways. */
  position: absolute;
  inset: 0;
  margin: auto;
  width: auto;
  height: auto;
  max-width: var(--ed-img-w);
  max-height: 100%;
  object-fit: contain;
}
.edition-card .edition-card__foot {
  flex: 0 0 auto;            /* natural height, pinned at the card bottom */
}

/* Per-piece opt-out — the CMS "No drop shadow" toggle on a Piece adds .no-shadow.
   For art that already carries its own shadow, or a cut-out object, the site
   shadow reads as a double. Kills it everywhere that piece renders: gallery
   slider, Editions card, and the lightbox. Each selector is specific enough to
   beat its base rule above (the lightbox base is an id-on-id selector). */
.slider-item.no-shadow .image-wrapper img,
.edition-card.no-shadow .shop-card__media img,
#posterModal .poster-modal-layout.no-shadow .image-wrapper img,
#posterModal .poster-modal-layout.no-shadow #posterImageElement {
  filter: none;
}

/* Editions "Available" button: sits dimmed 10% at rest (still fully clickable),
   then inverts to white-on-black on hover/focus. */
.edition-card .shop-card__price.is-available {
  opacity: 0.9;
  transition: background-color 120ms ease, color 120ms ease, border-color 120ms ease, opacity 120ms ease;
}
.edition-card .shop-card__price.is-available:hover,
.edition-card .shop-card__price.is-available:focus-visible {
  opacity: 1;
  background-color: #000;
  color: #fff;
  border-color: #000;
}

/* Shop: clean product grid (Field Notes style) in the site's aesthetic.
   Product photos have white backgrounds — multiply-blend the media box onto the
   paper so they sit on the texture like the rest of the site. */
.shop-listing {
  /* Top-align the heading to the same spot as the John Godfrey / text pages
     (offset 0 from .site-main). See docs/DESIGN-NOTES.md → "Text page start". */
  padding: 0 0 clamp(40px, 6vw, 80px);
}
/* Field Notes-style grid: larger cells with dividing lines between them (borders
   on each cell + top/left on the container = a full grid), lots of padding,
   centered captions. Cells are transparent so the paper shows through. */
.shop-grid {
  display: grid;
  grid-template-columns: 1fr; /* mobile: single column */
  gap: 0;
}
.shop-card {
  position: relative;
  /* Paper background so the media's multiply still has paper to blend against
     once the card gets its own stacking context on hover (below). */
  background: #f5f4ef;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  color: #000;
  text-decoration: none;
  padding: clamp(2rem, 3.5vw, 4rem);
  /* No dividing lines on mobile (single column) — added at >=768px below. */
}
/* Lift the hovered card so its enlarged image grows over its neighbours. The
   card's paper background keeps the multiply working inside this new context. */
.shop-card:hover {
  z-index: 2;
}
@media (min-width: 768px) {
  .shop-grid {
    grid-template-columns: repeat(3, 1fr);
  }
  .shop-card {
    border-bottom: 1.5px dotted #000; /* interior lines only, dotted black */
    border-right: 1.5px dotted #000;
  }
  .shop-card:nth-child(3n) {
    border-right: 0; /* no outer right edge */
  }
  .shop-card:nth-last-child(-n + 3) {
    border-bottom: 0; /* no outer bottom edge (multiples of 3 = full last row) */
  }
}
.shop-card__media {
  position: relative;
  width: 100%;
  aspect-ratio: 1 / 1;
  overflow: visible; /* let the image grow past its bounds on hover */
  margin-bottom: 1.4rem;
  mix-blend-mode: multiply; /* drop the white product background onto the paper */
}
/* Absolutely positioned so the image can't stretch the square media box — this
   keeps every card's image (and the text below it) aligned across a row. */
.shop-card__media img {
  position: absolute;
  inset: 0;
  margin: auto;
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  transition: transform 0.35s cubic-bezier(0.2, 0.7, 0.2, 1);
}
.shop-card:hover .shop-card__media img {
  transform: scale(1.0315);
}

/* Mobile (single column): greatly scale up the thumbnails on BOTH the shop and
   editions grids, and give the two the same footprint so they match. Off the
   grid's row alignment matters here (one column), so drop the fixed media aspect
   ratios and let each image flow at the full card width at its natural height.
   Matched side padding on both card types = matched image width. Reverts to the
   desktop aspect-locked layout at >=768px (the grid rule below). */
@media (max-width: 767.98px) {
  .shop-grid .shop-card,
  .shop-grid .shop-card.edition-card {
    padding: 1.25rem; /* far less air than the old clamp(2rem, ..) - bigger image, tighter stack */
    aspect-ratio: auto;        /* editions: drop the 4/5 card cap */
  }
  .shop-grid .shop-card__media {
    aspect-ratio: auto;        /* drop the shop 1/1 square */
    width: 100%;
    height: auto;
    min-height: 0;
  }
  .shop-grid .shop-card__media img,
  .shop-grid .edition-card .shop-card__media img {
    position: static;          /* flow, so the media box takes the image's height */
    width: 100%;
    height: auto;
    max-width: 100%;           /* overrides editions' --ed-img-w 64% cap */
    max-height: none;
    margin: 0;                 /* was auto (centred in the fixed box); not needed now */
  }
}

/* Tablet: drop the fixed media aspect ratios and flow each thumbnail at the
   column's full width (as the phone layout does), so both the shop and editions
   grids show large images instead of a small square (shop) or a squeezed sliver
   (editions, whose tall caption ate the height inside its 4/5 card). */
@media (min-width: 768px) and (max-width: 67.5rem) {
  .shop-grid .shop-card {
    aspect-ratio: auto; /* also drops the editions 4/5 card cap */
  }
  .shop-grid .shop-card__media {
    aspect-ratio: auto; /* drop the shop 1/1 square */
    width: 100%;
    height: auto;
    min-height: 0;
    /* The media box stretches to the row's height (equal-height cards); centre the
       image in it so images of different heights sit on a shared centre line across
       the row instead of all clinging to the top. */
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .shop-grid .shop-card__media img {
    position: static;
    width: 100%;
    height: auto;
    max-width: 100%;
    max-height: none;
    margin: 0;
  }
  /* Align each card's sections across the grid row with subgrid: the media row
     sizes to the tallest image (shorter ones centre in it) and the caption rows
     start on a shared line, so every row's text tops line up regardless of how
     long each caption is. Editions cards are media + foot (2 rows); shop cards
     are media + title + price (3 rows). */
  .shop-grid {
    grid-auto-rows: auto;
  }
  .editions-listing .shop-grid .edition-card {
    grid-row: span 2;
    display: grid;
    grid-template-rows: subgrid;
    grid-template-columns: minmax(0, 1fr);
    row-gap: 0.5rem;
  }
  .shop-listing:not(.editions-listing) .shop-grid .shop-card {
    grid-row: span 2;
    display: grid;
    grid-template-rows: subgrid;
    grid-template-columns: minmax(0, 1fr);
    row-gap: 0.5rem;
  }
  .shop-grid .shop-card > * {
    align-self: stretch;
    min-width: 0;
  }
}

/* Desktop (>=1080px): size the editions image exactly like a shop image. The base
   editions rules float the art in a tall 4/5 card capped at 64% width, so a poster
   lands ~25% narrower than the same poster on the Shop grid. Here we give editions
   the shop's box instead — matching padding, a 1/1 square media, image contained with
   no width cap — so a poster renders at the identical width in both grids (a portrait
   fills the square's height; the caption sits below, as on the Shop). The tablet/mobile
   ranges above already flow the image full-width, so they're untouched. */
@media (min-width: 67.5rem) {
  .shop-grid .shop-card.edition-card {
    aspect-ratio: auto;                 /* drop the fixed 4/5 card cap */
    padding: clamp(2rem, 3.5vw, 4rem);  /* same air as the shop, so the box width matches */
  }
  .shop-grid .edition-card .shop-card__media {
    aspect-ratio: 1 / 1;                /* the shop's square box (side = card content width) */
    flex: 0 0 auto;                     /* natural square height (base grows to fill the tall card) */
    min-height: 0;
  }
  .shop-grid .edition-card .shop-card__media img {
    max-width: 100%;                    /* drop the --ed-img-w 64% cap; stays contained + centred */
  }
}

/* Shop caption: title with the price on the line right beneath it (not aligned
   to other cards' prices - the price rides with its title). */
.shop-card__foot {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}
.shop-card__title {
  font-size: var(--fs-base);
  line-height: var(--lh-tight);
  /* Always underlined, thick — matching the editions titles. The global thick-
     underline rule only targets a/h1–h6, and this title is a <span> on the shop
     grid (an <a> on editions), so it needs the underline set explicitly here. */
  text-decoration: underline;
  text-decoration-thickness: 2px;
  text-underline-offset: 4px;
}
.shop-card__price {
  margin-top: 0.3rem;
  font-size: var(--fs-base);
  color: #000;
}

/* Edition card: meta lines beneath the title (matches shop caption sizing). */
.edition-card__meta {
  display: flex;
  flex-direction: column;
  gap: 0.12rem;
  margin-top: 0.35rem;
  font-size: var(--fs-sm);
  line-height: var(--lh-tight);
  color: #000;
}

/* Portfolio slider captions: tighten the line spacing between the title and the
   project-type line to about half, and make that description smaller + all caps. */
.image-description .title {
  line-height: var(--lh-tight);
}
.image-description .category {
  line-height: var(--lh-tight);
  font-size: var(--fs-xs); /* ~10px, down from 12px */
  font-weight: 500; /* up from 400 */
  text-transform: uppercase;
}
/* Mobile: pull each piece's caption up closer to its image. Below 576px the
   desktop slider's 1.5rem caption gap doesn't apply, so the base sheet's looser
   2rem gap is left showing - tighten it here. */
@media (max-width: 575.98px) {
  .slider-container .slider-items .slider-item .image-description {
    padding-top: 0.75rem;
  }
}

/* "BUY" badge on for-sale posters/art: an outlined circle to the right of the
   caption, linking to a store. Inherits the site font (same as the title). */
.slider-item .image-description { position: relative; }

/* Title + buy label share one row: the label is right-aligned to the image above and
   sits on the title's own line (baseline-aligned, so it stays on line 1 even when a
   long title wraps). Full chain because the base sheet sets `.image-description span
   { display: block }` at higher specificity, which would otherwise stop this flexing. */
.slider-container .slider-items .slider-item .image-description .desc-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.9rem;
}

/* "PRINT" / "EDITION" + page glyph — a quiet text label, not a filled badge. */
.buy-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  flex: 0 0 auto;
  color: #000;
  text-decoration: none;
  text-transform: uppercase;
  font-size: var(--fs-xs);
  font-weight: 500;
  letter-spacing: var(--track-label);
  line-height: var(--lh-tight);
  white-space: nowrap;
}
.buy-badge svg {
  /* 10% up on the original 0.95em. Sits to the RIGHT of the word in gallery view. */
  width: 1.045em;
  height: 1.045em;
  flex: 0 0 auto;
  /* Nudge the glyph onto the text's optical centre (inline-flex aligns to baseline). */
  transform: translateY(0.08em);
}
.buy-badge:hover {
  text-decoration: underline;
  text-decoration-thickness: 2px;
  text-underline-offset: 3px;
}

/* ---- Skip-to-content link (WCAG 2.4.1) -----------------------------------
   Off-screen until it receives keyboard focus, then it drops into the top-left
   as a solid black chip. Never shown to mouse users. */
/* Selector is .site a.skip-link so it outranks the site-wide `.site a { color:
   inherit }` rule — otherwise the chip renders black-on-black and is invisible. */
.skip-link,
.site a.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 10000;
  padding: 0.7rem 1.1rem;
  background: #000;
  color: #fff;
  text-decoration: none;
  font-size: var(--fs-sm);
  text-transform: uppercase;
  letter-spacing: normal;
}
/* Must match the base rule's specificity + :focus, or the off-screen `left`
   above wins and the chip never appears. */
.skip-link:focus,
.site a.skip-link:focus {
  left: 0.75rem;
  top: 0.75rem;
}

/* ---- Keyboard focus ring (WCAG 2.4.7) -----------------------------------
   A guaranteed, visible focus indicator on every interactive element. Bootstrap
   zeroes the outline on its own components (relying on a box-shadow ring) and
   the site's custom controls fall back to inconsistent UA defaults — this makes
   keyboard focus obvious everywhere. :focus-visible only, so mouse clicks stay
   ring-free. Uses the house control-outline line: 1.5px solid #000. */
.site :is(a, button, input, textarea, select, summary, [tabindex]):focus-visible {
  outline: 1.5px solid #000;
  outline-offset: 2px;
}

/* ---- One underline style, site-wide -------------------------------------
   Every underline on the site is THICK: 2px thickness, 4px offset. This single
   rule (with !important) overrides the assorted thin per-link underlines so there
   is exactly one underline look everywhere — headings, nav, body links, footer,
   checkout, consent. There is intentionally no "thin" underline. See
   docs/DESIGN-NOTES.md → "Underline".
   `.footer-legal button` (the "Cookie settings" link-button) is included because a
   control that reads as a link must use the one underline too — the base rule only
   reaches <a> + headings, so link-styled <button>s need adding here explicitly. */
.site :is(a, h1, h2, h3, h4, h5, h6),
.site .footer-legal button {
  text-decoration-thickness: 2px !important;
  text-underline-offset: 4px !important;
}

/* Pin the nav to the top and vertically center page content in the space below. */
@media (min-width: 67.5rem) {
  #page {
    min-height: 100vh;
    display: flex;
    flex-direction: column;
  }
  .site-header {
    height: auto !important;
    justify-content: flex-start !important;
    padding-top: 2.75rem !important;
    padding-bottom: 1.25rem !important;
  }
  /* Nav sits lower (padding above), but keep the G poking above the top edge
     for the partially-cut-off look — lift the logo independently of the nav. */
  .site-header .site-branding {
    position: relative;
    top: -1.5rem;
  }
  /* Lift the nav by the same amount so its text centers on the logo's centre
     (content is bottom-aligned, so the shorter nav otherwise sits lower). */
  .site-header .menu-wrapper .site-navigation {
    position: relative;
    top: -1.5rem;
  }
  /* Top-align content on every non-gallery page so the heading/text starts at the
     same height regardless of how much content the page has — clicking between the
     John Godfrey, Commissions, Contact, Editions, Shop pages keeps a consistent
     start position (matching the editions page). Gallery pages keep their vertically
     centred slider via the higher-specificity #page.gallery-page rule below. */
  #primary.site-main {
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    justify-content: flex-start;
  }
  /* Center each poster+description within its slide. The theme's
     .image-description{margin-bottom:auto} top-aligns it; a matching auto top
     margin on the image balances it so the group sits centered. */
  .slider-container .slider-items .slider-item .image-wrapper {
    margin-top: auto;
  }
  /* The slider is sized to the tallest poster and top-aligns shorter ones
     (flex-start), so they sit high. Center items so every poster centers on the
     same midline (between the nav and the footer). */
  .slider-container .slider-items {
    align-items: center;
  }
  /* Bring the caption 25% closer to the poster (the base sheet sets 2rem at high
     specificity, so match it). */
  .slider-container .slider-items .slider-item .image-description {
    padding-top: 1.5rem;
  }
}

/* Art galleries (artwork, commissions): don't force every piece to the full slider
   height. Portraits stand tall; everything else (squares AND wider pieces like the
   3/2 "Are You Still Watching?") shares ONE middle height so they read as a set —
   height is capped per orientation, width follows each piece's --aspect-ratio. The
   generous max-width keeps a 3/2 from being clamped shorter than a 5/4 square, so
   e.g. "Are You Still Watching?" matches "Nepobaby". Desktop slider only. */
@media (min-width: 576px) {
  /* Pieces are different heights, so pin the caption directly under each image (like
     the posters pages) and centre the image+caption group on the slider's midline.
     The image-wrapper shrinks to the image; the caption follows immediately below at
     the same gap the posters use. */
  .gallery--art .slider-container .slider-items .slider-item {
    justify-content: center;
  }
  .gallery--art .slider-container .slider-items .slider-item .image-wrapper {
    flex: 0 0 auto;
    height: auto;          /* shrink to the image so the caption sits right beneath it */
    margin: 0;
    display: flex;
    justify-content: center;
  }
  .gallery--art .slider-container .slider-items .slider-item .image-wrapper picture {
    height: auto;
    width: auto;
  }
  .gallery--art .slider-container .slider-items .slider-item .image-description {
    margin: 0;             /* cancel the theme's auto margin so the group centres cleanly;
                              keep the shared padding-top (matches the posters' gap) */
  }
  /* Three height tiers. Portrait tallest (74vh), landscape shortest (46vh), and the
     square/near-square in the middle at exactly halfway ((74+46)/2 = 60vh). Each is
     also capped by viewport WIDTH so a wide piece never grows past the screen and
     overlaps its neighbour on a tall/narrow viewport (widths stay ≤80vw). */
  .gallery--art .slider-container .slider-items .slider-item .image-wrapper img {
    height: auto;
    width: auto;
    max-height: min(64vh, 64vw);   /* square: taller than landscape (which is now 550px) */
    max-width: 80vw;
  }
  .gallery--art .slider-container .slider-items .slider-item.portrait .image-wrapper img {
    /* Portrait is the tallest tier, so on a short window it's the only one that could
       push its caption off-screen. The calc keeps image + caption + header/footer
       within the viewport (≈12rem of chrome on desktop); on a normal window 74vh wins. */
    max-height: min(74vh, calc(100dvh - 13rem), 92vw);
    max-width: 80vw;
  }
  .gallery--art .slider-container .slider-items .slider-item.is-landscape .image-wrapper img {
    /* Landscape (wide pieces): fill the ~550px tier (capped by 62vh on short windows
       and 53vw on narrow ones). This only fills correctly because the responsive
       `sizes` hint is widened for landscape pieces (see ResponsiveImage) — otherwise
       the img lays out at ~600px wide and never reaches the cap. max-height (not a
       forced height) keeps ultra-wide pieces proportionally shorter with no letterbox. */
    max-height: min(550px, 62vh, 53vw);
    max-width: 80vw;
  }
}
@media (min-width: 576px) and (max-width: 67.49rem) {
  /* Tablet header is taller, so give the portrait more chrome allowance on short windows. */
  .gallery--art .slider-container .slider-items .slider-item.portrait .image-wrapper img {
    max-height: min(74vh, calc(100dvh - 17rem), 92vw);
  }
}

/* Gallery pages are a single-screen experience — header + horizontal slider + footer,
   no vertical scroll. Instead of guessing a fixed slider height that must exactly
   equal viewport − header − footer (which spills a few px and shows a stray scrollbar
   whenever those heights differ from the guess), pin the whole page to the viewport as
   a flex column: the footer stays pinned at the bottom and the slider area flexes to
   fill and clips any leftover, so a vertical scrollbar can never appear. Mobile ≤575
   is a vertical, page-scrolling column, so it's excluded. */
@media (min-width: 576px) {
  #page.gallery-page {
    height: 100dvh;
    min-height: 0;          /* neutralise the desktop #page min-height:100vh */
    display: flex;
    flex-direction: column;
    overflow: hidden;       /* the single line that guarantees no page scrollbar */
  }
  #page.gallery-page #primary.site-main {
    flex: 1 1 auto;
    min-height: 0;          /* fill the space between header + footer, absorb any excess */
    overflow: hidden;
    display: flex;
    flex-direction: column;
    justify-content: safe center;
  }
  /* Size the slider to roughly that space so the art sits comfortably centred within
     it (the flex layout above is what actually prevents the scrollbar). */
  .slider-container .slider-items,
  .slider-container .slider-items .slider-item {
    height: calc(100dvh - 9.5rem);
  }
  /* ...but that calc is a guess at the header+footer height, and when it guesses
     low the slider ends up TALLER than #primary — whose overflow:hidden then
     shears the bottom off the captions/BUY badges. Chain a real height down from
     main instead, so the slider fills exactly the space it's given at any header
     or footer size. Higher specificity so it wins over the calc above. */
  #page.gallery-page #primary.site-main > article.page,
  #page.gallery-page #primary.site-main .entry-content,
  #page.gallery-page #primary.site-main .johngodfrey-poster-block,
  #page.gallery-page #primary.site-main .slider-container {
    height: 100%;
    min-height: 0;
  }
  #page.gallery-page .slider-container .slider-items,
  #page.gallery-page .slider-container .slider-items .slider-item {
    height: 100%;
  }
}
@media (min-width: 576px) and (max-width: 67.49rem) {
  /* Tablet header is taller (lifted logo + more padding), so the slider gets less. */
  .slider-container .slider-items,
  .slider-container .slider-items .slider-item {
    height: calc(100dvh - 13.5rem);
  }
}

/* Nav underline matches the highlighted page title (e.g. "EDITIONS"): 2px thick,
   4px offset, black. */
.site-header .menu-wrapper .site-navigation .menu-item a span {
  text-decoration-thickness: 2px !important;
  text-underline-offset: 4px !important;
}

/* The mailing-menu is a new list; the theme's reset targets the other menus by id,
   so it doesn't reach this one — null the marker AND the default ul margin (the 16px
   bottom margin was pushing it above the row's centre line). */
.site-header .menu-wrapper .site-navigation .mailing-menu { list-style: none; margin: 0; }
.site-header .menu-wrapper .site-navigation .mailing-menu .menu-item { list-style: none; }

/* Shop nav link: display font, a bit larger, but on the row's line box so its
   centre stays aligned with the rest of the items despite the bigger size. The
   text lives in the inner span, so style that too (the theme sets its font). */
.site-header .menu-wrapper .site-navigation .shop-link,
.site-header .menu-wrapper .site-navigation .shop-link span {
  font-family: "Boldonse", "Sometype Mono", monospace !important;
  text-transform: uppercase;
  font-size: var(--fs-lg) !important;
  line-height: var(--lh-body);
}

/* …and it carries the film grain, like the display titles and the logo. Same noise
   tile at the same 180px, so the specks match the rest of the ink.

   Two things differ from `.display-title`:
   - No cropping to fight, and no compensating negative margins. An INLINE box takes its
     background from the font metrics, not from `line-height`, so the paint box already
     contains the ink (measured at 16px: ink ascends 19px against a 24px font ascent).
     `padding-block` is only headroom in case the size token grows or someone gives this
     `display: inline-block` later — on an inline box it costs nothing, since vertical
     padding expands the paint box without reflowing the line.
   - Only `-webkit-text-fill-color` goes transparent; `color` stays black. On /shop the
     active 2px underline sits on this same span and paints in `color` — blanking that
     would take the underline with it. */
@supports ((-webkit-background-clip: text) or (background-clip: text)) {
  .site-header .menu-wrapper .site-navigation .shop-link span {
    padding-block: 0.75em;
    background-color: #000;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='tg'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3CfeComponentTransfer%3E%3CfeFuncR type='linear' slope='4' intercept='-2.5'/%3E%3CfeFuncG type='linear' slope='4' intercept='-2.5'/%3E%3CfeFuncB type='linear' slope='4' intercept='-2.5'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23tg)' opacity='0.9'/%3E%3C/svg%3E");
    background-size: 120px 120px;
    -webkit-background-clip: text;
    background-clip: text;
    -webkit-text-fill-color: transparent;
  }
}

/* Desktop main nav: pack items tightly and put a " / " between each. */
@media (min-width: 67.5rem) {
  .site-header .menu-wrapper .site-navigation .main-menu {
    justify-content: flex-start !important;
  }
  /* The theme gives the social menu auto margins on BOTH sides, centering it in
     the leftover space (bumped left). Push it flush to the right edge instead,
     symmetric with the logo's left margin. */
  .site-header .menu-wrapper .site-navigation .social-menu.social-menu {
    margin-left: auto;
    margin-right: 0;
  }
  /* Mailing List floats centered in the space between the left (main) and right
     (social) menus. Equal auto-margins on it and the social menu split the free
     space evenly, landing it at the midpoint of the gap. */
  .site-header .menu-wrapper .site-navigation .mailing-menu {
    margin-left: auto;
    margin-right: 0;
    align-self: center;   /* match the main/social menus (nav centres them; this one wasn't) */
  }
  .site-header .menu-wrapper .site-navigation .mailing-menu .menu-item {
    display: flex;
    align-items: center;
  }
  /* Identical treatment to every other nav link — same padding (so it aligns and has
     the same hover target), size, weight and case. The underline (2px / 4px offset,
     black, hover-only) comes from the shared .menu-item a span rule. */
  .site-header .menu-wrapper .site-navigation .mailing-menu .menu-item a {
    padding: 12px 0.45rem !important;
    font-size: var(--fs-md);
    font-weight: 500;
    text-transform: uppercase;
    text-decoration: none;   /* the anchor was underlined at rest (theme resets the others by id) */
  }
  .site-header .menu-wrapper .site-navigation .mailing-menu .menu-item a:hover span {
    text-decoration: underline;   /* hover only, on the span, like the rest of the nav */
  }
  .site-header .menu-wrapper .site-navigation .main-menu .menu-item {
    display: flex;
    align-items: center;
  }
  .site-header .menu-wrapper .site-navigation .main-menu .menu-item a {
    padding-left: 0.45rem !important;
    padding-right: 0.45rem !important;
  }
  .site-header .menu-wrapper .site-navigation .main-menu .menu-item:not(:last-child)::after {
    content: "/";
    opacity: 1;   /* separator: solid black, per "all text black" */
    font-weight: 700;
  }
  /* Top-right menu (Contact / Instagram / Mailing List / Cart) — same treatment. */
  .site-header .menu-wrapper .site-navigation .social-menu .menu-item {
    display: flex;
    align-items: center;
  }
  .site-header .menu-wrapper .site-navigation .social-menu .menu-item a {
    padding-left: 0.45rem !important;
    padding-right: 0.45rem !important;
  }
  .site-header .menu-wrapper .site-navigation .social-menu .menu-item:not(:last-child)::after {
    content: "/";
    opacity: 1;   /* separator: solid black, per "all text black" */
    font-weight: 700;
  }
}

/* Movie Posters section: the horizontal secondary sub-nav (All / Alternative /
   Official) sits just under the main nav and stays put across those pages so
   you can switch between them. The theme positions it absolutely; make sure it
   shows horizontally on desktop regardless of the mobile .show toggling. */
@media (min-width: 768px) {
  .site-header .menu-wrapper .site-navigation .secondary-menu {
    display: flex !important;
    flex-direction: row !important;
    flex-wrap: nowrap !important;
    position: absolute !important;
    bottom: -2rem;
    left: 0.45rem; /* align opening "[" under HOME's text */
    margin: 0 !important;
    gap: 0;
    /* Size to content so the absolute box isn't squeezed by its containing block
       at narrow desktop widths (which would wrap the items). */
    width: max-content;
  }
  .site-header .menu-wrapper .site-navigation .secondary-menu .menu-item a {
    font-size: var(--fs-sm) !important; /* ~11px, smaller than the 14px main nav */
    padding-left: 0 !important;
    padding-right: 0 !important;
    white-space: nowrap;
  }
  /* One [ … / … ] bracket pair around the two options, not one per item. */
  .site-header .menu-wrapper .site-navigation .secondary-menu .menu-item a::before,
  .site-header .menu-wrapper .site-navigation .secondary-menu .menu-item a::after {
    content: none !important;
  }
  .site-header .menu-wrapper .site-navigation .secondary-menu .menu-item:nth-child(2) a::before {
    content: "[" !important;
    margin-right: 0.4em;
  }
  .site-header .menu-wrapper .site-navigation .secondary-menu .menu-item:nth-child(2) a::after {
    content: "/" !important;
    margin: 0 0.4em;
    font-weight: 700;
  }
  .site-header .menu-wrapper .site-navigation .secondary-menu .menu-item:last-child a::after {
    content: "]" !important;
    margin-left: 0.4em;
  }
}

/* Mobile menu (open overlay): left-align the items under the X, and keep the
   logo visible beside the X instead of hiding it behind the overlay. */
@media screen and (max-width: 67.5rem) {
  .site-header .menu-wrapper .site-navigation.open {
    /* Compact top-left box (like the desktop scroll menu) instead of a full-screen
       overlay — anchored to the corner, sized to its content, with the logo +
       hamburger sitting in its top-left. */
    inset: auto;
    top: 0;
    left: 0;
    width: -moz-max-content;
    width: max-content;
    max-width: 82vw;
    height: auto;
    max-height: 100dvh;
    overflow-y: auto;
    min-width: 12rem;
    align-items: flex-start;
    justify-content: flex-start;
    /* Top padding clears the logo/hamburger; left aligns items under the logo. */
    padding: 5.75rem 1.6rem 1.3rem;
    background: #fff;
    border-right: 1px solid rgba(0, 0, 0, 0.1);
    border-bottom: 1px solid rgba(0, 0, 0, 0.1);
    box-shadow: 0 8px 17px rgba(0, 0, 0, 0.12);
  }
  .site-header .menu-wrapper .site-navigation.open .main-menu,
  .site-header .menu-wrapper .site-navigation.open .mailing-menu,
  .site-header .menu-wrapper .site-navigation.open .social-menu,
  .site-header .menu-wrapper .site-navigation.open .secondary-menu {
    align-items: flex-start;
    text-align: left;
    width: auto;
    /* The theme centres these lists with auto side margins, which override
       align-items — zero them so the items sit at the left edge. */
    margin-left: 0;
    margin-right: 0;
  }
  .site-header .menu-wrapper .site-navigation.open .menu-item,
  .site-header .menu-wrapper .site-navigation.open .menu-item a {
    text-align: left;
  }
  /* Mailing List: the theme styles it as a small, indented, mixed-case button.
     In the stacked menu it should read like every other item — same 18px
     uppercase Sometype Mono, flush left at 0, same 16px vertical rhythm, no
     rest-state underline (the desktop reset lives in a min-width query). */
  .site-header .menu-wrapper .site-navigation.open .mailing-menu,
  .site-header .menu-wrapper .site-navigation.open .mailing-menu .menu-item {
    margin: 0;
    padding: 0;
  }
  .site-header .menu-wrapper .site-navigation.open .mailing-menu .menu-item a {
    display: block;
    padding: 16px 0;
    font-family: "Sometype Mono", monospace;
    font-size: var(--fs-lg);
    font-weight: 500;
    line-height: var(--lh-body);
    text-transform: uppercase;
    text-decoration: none;
  }
  .site-header .menu-wrapper .site-navigation.open .mailing-menu .menu-item a span {
    text-decoration: none;
  }
  /* Reveal the logo (the theme hides it with opacity:0 on mobile) and raise it
     above the overlay so it shows next to the X while the menu is open. */
  .site-header.open .menu-wrapper .site-branding {
    opacity: 1;
    z-index: 96;
  }
  /* Pin the cart to the header's top-right, always visible without opening the
     menu. It lives in the top bar (.mobile-cart) — NOT inside the nav overlay,
     which has opacity:0 when closed and would hide any descendant. Aligns with
     the logo; symmetric right inset to the toggle's left inset. */
  .site-header .mobile-cart {
    align-items: center;
    /* Bootstrap's .row > * forces width:100% (it's a row child); shrink to fit. */
    width: max-content;
    position: fixed;
    /* The mobile header is 13vh tall and the logo centres at half that; match it
       so the cart aligns with the logo at every mobile width. */
    top: 6.5vh;
    transform: translateY(-50%);
    right: 1rem;
    z-index: 96; /* above the overlay (90) and toggle (95) */
    color: #000;
    text-decoration: none;
    font-size: var(--fs-base);
  }
  /* Only show it once the cart holds something (base rule keeps it hidden). */
  html.cart-has-items .site-header .mobile-cart {
    display: inline-flex;
  }
}
/* On small phones the toggle drops its left margin, so match the items to it. */
@media screen and (max-width: 575.98px) {
  .site-header .menu-wrapper .site-navigation.open {
    padding-left: 1rem;
  }
  /* The theme paints the fixed mobile header white; keep the header box itself
     transparent - the scrim below provides the paper. */
  .site-header {
    background-color: transparent;
  }
  /* Scroll scrim (modelled on Chargefield's header fade): a paper wash behind the
     fixed header so page content scrolls UNDER it and fades out instead of touching
     the top of the browser. Opaque paper at the top hides content at the very top,
     then it tapers to transparent - and extends past the header's bottom edge, so
     the fade is soft with no hard bar. Same paper (colour + texture) as the body,
     masked to a top-down fade; the tile aligns because both anchor at the viewport
     top-left. Sits behind the header's own content (logo/menu/Shop). */
  .site-header::before {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    top: 0;
    /* Pull the bottom edge up so the teeth sit just under the G logo circle.
       Halved the logo-to-teeth gap (was 12.5px) to sit the teeth ~3px under it. */
    bottom: 17.5px;
    z-index: 0;
    pointer-events: none;
    background-color: rgb(245, 244, 239);
    background-image:
      linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0.05)),
      url("/assets/ambientcg-paper001.webp");
    background-size: auto, 638px auto;
    /* No gradient: the paper is fully opaque with a zig-zag (pinking-shears)
       bottom edge. Two mask layers, unioned by the default mask-composite: a
       solid block for everything above the teeth, plus a row of small downward
       triangles (an SVG tile) repeated across the very bottom. The tile is 1px
       taller than the solid block's cut-off so the two overlap - otherwise a
       sub-pixel seam leaks a hairline of the image between them. Paper-on-paper
       it's invisible - the zig-zag only shows where the header overlaps a poster
       or text as it scrolls under. Teeth are 25% smaller than the original
       10.3×7.2 tile (→ 7.725×5.4; solid cut-off 5.4-1=4.4). */
    -webkit-mask-image:
      linear-gradient(#000, #000),
      url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='7.725' height='5.4'><path d='M0 0H7.725L3.8625 5.4Z' fill='black'/></svg>");
    mask-image:
      linear-gradient(#000, #000),
      url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='7.725' height='5.4'><path d='M0 0H7.725L3.8625 5.4Z' fill='black'/></svg>");
    -webkit-mask-size: 100% calc(100% - 4.4px), 7.725px 5.4px;
    mask-size: 100% calc(100% - 4.4px), 7.725px 5.4px;
    -webkit-mask-position: top, left bottom;
    mask-position: top, left bottom;
    -webkit-mask-repeat: no-repeat, repeat-x;
    mask-repeat: no-repeat, repeat-x;
  }
  /* Keep the header's own content above the scrim. */
  .site-header > * {
    position: relative;
    z-index: 1;
  }

  /* Headroom slides the header with `transform` + `will-change: transform`,
     which promotes it to an isolated compositing layer — so the logo's blend
     can only mix with the (transparent) header and just renders white. Re-do the
     show/hide with `top` instead, so nothing isolates and the invert reaches the
     page scrolling behind it. */
  .site-header.headroom {
    will-change: auto;
    transform: none;
    transition: top 200ms linear;
  }
  .site-header.headroom--pinned {
    transform: none;
    top: 0;
  }
  /* Keep the header (logo + menu) persistent — don't slide it away on scroll. */
  .site-header.headroom--unpinned:not(.open) {
    transform: none;
    top: 0;
  }
}

/* Sub-desktop (hamburger shown): lift the logo AND the toggle together so the
   logo keeps the same "poking above the top" position it has in the desktop nav —
   no downward jump when the nav collapses. Covers the tablet range (static header)
   AND the phone range (fixed header); phone has half the top padding, so it gets a
   smaller lift below to poke above by the same visual amount. */
@media screen and (min-width: 36rem) and (max-width: 67.5rem) {
  /* Small lift for the logo/hamburger. Eased from the old -1.5rem: once the header
     was trimmed (below) that lift poked the logo to the very top edge, where a
     device status bar clipped it - keep clear of the top. */
  .site-header .site-branding,
  .site-header .menu-toggle {
    position: relative;
    top: -0.5rem;
  }
  /* Tablet header used the theme's fixed 13vh height + tall 2rem/2rem padding,
     leaving a big dead band below the logo that pushed page content way down. Let
     it shrink to its content (as the desktop header does) and trim the bottom
     padding, so content sits up near the header. */
  .site-header {
    height: auto !important;
    padding-top: 1.5rem !important;
    padding-bottom: 0.5rem !important;
  }
}
/* Tablet: on the scrolling sections (non-gallery pages) pin the nav and give it
   the same paper scroll-scrim + zig-zag teeth the phone header has, so content
   dissolves under the nav as it scrolls instead of sliding over it. Header bg is
   already transparent here; the ::before supplies the paper, its teeth sitting
   just below the logo, and the nav's own controls sit above it. */
@media screen and (min-width: 36rem) and (max-width: 67.5rem) {
  #page:not(.gallery-page) .site-header {
    position: sticky;
    top: 0;
  }
  /* The float-nav is the scrolled-away header's stand-in (its logo/toggle flip
     white over artwork). With the header now pinned here it would just overlay a
     second, white logo on the sticky one - so drop it on these pages. */
  #page:not(.gallery-page) .float-nav {
    display: none;
  }
  #page:not(.gallery-page) .site-header::before {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    top: 0;
    /* Apex ~3.5px up from the header's bottom edge - the same logo-to-teeth gap
       (~6px) and tooth depth (~6px) the phone header has. The Email Alerts circle
       would poke into these, so the listing title is nudged down on tablet (below)
       to clear them. */
    bottom: 3.5px;
    z-index: 0;
    pointer-events: none;
    background-color: rgb(245, 244, 239);
    background-image:
      linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0.05)),
      url("/assets/ambientcg-paper001.webp");
    background-size: auto, 638px auto;
    -webkit-mask-image:
      linear-gradient(#000, #000),
      url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='10.3' height='7.2'><path d='M0 0H10.3L5.15 7.2Z' fill='black'/></svg>");
    mask-image:
      linear-gradient(#000, #000),
      url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='10.3' height='7.2'><path d='M0 0H10.3L5.15 7.2Z' fill='black'/></svg>");
    -webkit-mask-size: 100% calc(100% - 6.2px), 10.3px 7.2px;
    mask-size: 100% calc(100% - 6.2px), 10.3px 7.2px;
    -webkit-mask-position: top, left bottom;
    mask-position: top, left bottom;
    -webkit-mask-repeat: no-repeat, repeat-x;
    mask-repeat: no-repeat, repeat-x;
    /* Give the masked scrim its own compositing layer. A sticky element with a
       masked pseudo doesn't invalidate cleanly on scroll, so the paper (and the
       masked logo/toggle below) left ghost trails of the content passing under
       it - a dedicated layer forces a clean repaint each frame. */
    transform: translateZ(0);
  }
  /* Same layer promotion for the mask-painted logo and hamburger (leaf elements
     with no fixed descendants, so this is safe - unlike promoting the header
     itself, which would trap the fixed nav overlay). */
  #page:not(.gallery-page) .site-header .johngodfrey-logo,
  #page:not(.gallery-page) .site-header .menu-toggle {
    transform: translateZ(0);
  }
  #page:not(.gallery-page) .site-header > * {
    position: relative;
    z-index: 1;
  }
  /* When the menu opens, the header is lifted above the white panel (so the logo
     and X stay visible) - which drags the teeth scrim over the panel's top too.
     Clip the scrim out of the panel's column (~208px wide box on the left) so the
     white reads cleanly there; the rest of the header keeps its teeth. */
  #page:not(.gallery-page) .site-header.menu-open::before {
    -webkit-clip-path: inset(0 0 0 228px);
    clip-path: inset(0 0 0 228px);
  }
  /* Nudge the listing title (and its Email Alerts circle) down a few px so the
     circle clears the teeth apex now that the teeth sit lower for the gap. */
  .shop-listing .listing-header {
    margin-top: 0.6rem;
  }
}
/* Desktop: on vertical (non-gallery) pages, keep the full menu pinned at the top
   while scrolling and give it the same paper scroll-scrim + zig-zag teeth as the
   phone/tablet headers, so content dissolves under the nav. The float-nav (compact
   scrolled bar) is dropped here since the full header now stays. The teeth apex
   sits below the menu row and above where the Email Alerts circle rests, so the
   scrim never bites it. Galleries are excluded (they don't scroll vertically). */
@media screen and (min-width: 67.5rem) {
  #page:not(.gallery-page) .site-header {
    position: sticky;
    top: 0;
    /* Above the page content so the scrim (and nav) cover what scrolls under. */
    z-index: 20;
  }
  #page:not(.gallery-page) .float-nav {
    display: none;
  }
  #page:not(.gallery-page) .site-header::before {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    top: 0;
    bottom: 28px; /* teeth apex ~90px: below the menu row, above the alerts circle */
    z-index: 0;
    pointer-events: none;
    background-color: rgb(245, 244, 239);
    background-image:
      linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0.05)),
      url("/assets/ambientcg-paper001.webp");
    background-size: auto, 638px auto;
    -webkit-mask-image:
      linear-gradient(#000, #000),
      url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='10.3' height='7.2'><path d='M0 0H10.3L5.15 7.2Z' fill='black'/></svg>");
    mask-image:
      linear-gradient(#000, #000),
      url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='10.3' height='7.2'><path d='M0 0H10.3L5.15 7.2Z' fill='black'/></svg>");
    -webkit-mask-size: 100% calc(100% - 6.2px), 10.3px 7.2px;
    mask-size: 100% calc(100% - 6.2px), 10.3px 7.2px;
    -webkit-mask-position: top, left bottom;
    mask-position: top, left bottom;
    -webkit-mask-repeat: no-repeat, repeat-x;
    mask-repeat: no-repeat, repeat-x;
    /* Own compositing layer so the sticky masked scrim repaints cleanly on scroll. */
    transform: translateZ(0);
  }
  /* Same layer promotion for the mask-painted logo, so it doesn't ghost on scroll. */
  #page:not(.gallery-page) .site-header .johngodfrey-logo {
    transform: translateZ(0);
  }
  #page:not(.gallery-page) .site-header > * {
    position: relative;
    z-index: 1;
  }
}
@media screen and (max-width: 35.99rem) {
  .site-header .site-branding,
  .site-header .menu-toggle {
    position: relative;
    top: -0.7rem; /* eased from -1.4rem: the logo poked to ~5px off the top and got
                     clipped by the status bar / rounded corners on real phones */
  }
}
/* Fixed top clearance for the mobile header. Android Chrome sits the page close
   to the status bar and reports no safe-area inset, so push the header down a set
   amount and grow the padding that clears it to match (env() would be device-only
   but is 0 there). --hdr-top-pad is the value added to both. */
@media screen and (max-width: 575.98px) {
  :root {
    --hdr-top-pad: 1.75rem;
  }
  #masthead.site-header {
    padding-top: calc(1rem + var(--hdr-top-pad));
  }
  #primary.site-main {
    /* 13vh ≈ the header height; the extra --hdr-top-pad is the visible gap to the
       first poster. Halved (÷2) to tighten that gap without shrinking the header
       (the header keeps the full --hdr-top-pad in its own padding above). */
    padding-top: calc(13vh + var(--hdr-top-pad) / 2);
  }
  /* Text (non-gallery) pages use a STICKY header — matching the tablet (min-width
     36rem) and desktop (min-width 67.5rem) rules above; galleries stay fixed.
     Phone previously had no such rule, so About/Contact fell back to the theme's
     position:fixed, which on real devices let the header's top tuck under the
     browser URL bar / status bar as you scrolled ("half the menu hidden"). Sticky
     sits in normal flow, so it can't clip under the browser chrome the same way. */
  #page:not(.gallery-page) .site-header {
    position: sticky;
    top: 0;
    z-index: 20;
  }
  #page:not(.gallery-page) .float-nav {
    display: none;
  }
  /* A sticky header takes its own space in flow, so it no longer needs the
     fixed-header spacer (13vh) — keep only the small logo-to-content gap. */
  #page:not(.gallery-page) #primary.site-main {
    padding-top: calc(var(--hdr-top-pad) / 2);
  }
  /* Match the galleries' 16px gutter site-wide on phones so every page title
     (and its content) lines up in the same place. Galleries already sit at 16px
     via the slider-item padding; this brings Shop/Editions/About/Contact/legal
     (which live inside .page-content) in from 32px to match. */
  #primary .page-content {
    padding-left: 1rem;
    padding-right: 1rem;
  }
  .site-footer .container-fluid {
    padding-left: 1rem;
    padding-right: 1rem;
  }
  /* Contact leads with a hero image on phones, pushing its title far down the
     page. Lead with the title/form instead (image below) so the "Contact" title
     starts at the top like every other page. */
  .contact-row > .sidebar {
    order: 0;
  }
  .contact-row > .page-content {
    order: 1;
  }
  /* About/Contact titles sit ~8px lower than the gallery/shop titles (which get
     an extra pull-up); nudge them up so every page title lands on the same line. */
  .johngodfrey-about-block .biography,
  .contact-row > .sidebar {
    margin-top: -0.6rem;
  }
}
/* Keep the logo's raised z-index applied at ALL times (not just when the menu is
   open). Otherwise opening the menu sets it for the first time, which promotes
   the logo to a new stacking layer — Firefox re-snaps a freshly-promoted element
   to the pixel grid, so the logo (and the hamburger beside it) appear to shift a
   hair down-right. A constant z-index means nothing changes on open. */
@media screen and (max-width: 67.5rem) {
  .site-header .site-branding { z-index: 96; }
  /* When the mobile menu is open, lift the WHOLE header above the menu panel
     (.mobile-menu is z-index 90). The per-icon z-index (toggle 95 / logo 96)
     can't do this on its own — it's trapped inside .site-header's own stacking
     context (z-index: 10), so the opaque white panel paints over the header and
     the logo + hamburger vanish behind it (reads as "the icons turned white").
     Above the panel, they stay visible and the sampler flips them black against
     the white — the same look as the desktop float-nav bar. */
  .site-header.menu-open { z-index: 95; }
  /* That z-index keeps the (transparent) header bar above the menu panel so the
     logo + hamburger stay visible - but its full-width box would then swallow
     taps on the menu items beneath it (e.g. the first item, "John Godfrey",
     which sits under the bar). Drop the bar's own pointer-events while open so
     those taps reach the menu, and re-enable only its own controls. */
  .site-header.menu-open { pointer-events: none; }
  .site-header.menu-open .menu-toggle,
  .site-header.menu-open .site-branding,
  .site-header.menu-open .header-actions-mobile {
    pointer-events: auto;
  }
}

/* Mobile logo + hamburger: each is carved to its shape with a mask and painted
   solid black via background-color (works on a fixed element in every browser).
   They used to flip black↔white against whatever scrolled under them, but the
   fixed header now carries a paper scroll-scrim that covers the artwork behind
   the top bar, so they simply stay black on paper (see SiteHeader - the flip is
   gone; the float-nav on desktop/tablet still flips). */
@media screen and (max-width: 67.5rem) {
  .site-header .johngodfrey-logo {
    background-image: none;
    background-color: #000;
    transition: background-color 120ms linear;
    -webkit-mask-image: url('data:image/svg+xml;utf8,<svg fill="white" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 512 512"><path d="M256,11C120.9,11,11,121.3,11,256s109.9,244.5,245,244.5v.5c135.2,0,245-109.9,245-245S391.1,11,256,11ZM256,466.5c-116.2,0-210.5-94.3-210.5-210.5S139.8,45.5,256,45.5s210.5,94.3,210.5,210.5-94.3,210.5-210.5,210.5Z"/><path d="M410.6,259.4l1-1h-124.5c-6.8,0-12.2,4.4-12.2,10.7s38.4,12.2,38.4,26.7v35.5c0,9.2-7.3,16.5-16.5,16.5h-46.7c-24.3,0-48.1-24.8-53-52-2.4-14.6-38.4-19.9-38.4-26.7s5.3-10.7,12.2-10.7h16c5.3,0,9.2-4.4,9.2-9.2v-37.9c0-31.1,29.7-52.5,56.9-52.5h30.6c15.1,0,32.6-1,32.6,30.6v3.4c0,15.6,12.6,28.2,28.2,28.2h10.2c15.6,0,29.7-12.6,29.7-28.2v-3.4c0-36.9-9.7-56.4-84.6-56.4h-84.1c-57.9,0-90.9,46.2-90.9,118.1v.4c-1.2,3.9-4.2,6.9-8.3,6.9h-16c-6.8,0-12.2,4.4-12.2,10.7s32.2,12.2,37.1,25.5c3.3,48.1,36.1,85.4,102.9,85.4h60.8c36.9,0,76.3-6.3,94.3-22.9v-60.3c0-14.6,39.4-20.4,39.4-26.7s-5.3-10.7-12.2-10.7Z"/></svg>');
    mask-image: url('data:image/svg+xml;utf8,<svg fill="white" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 512 512"><path d="M256,11C120.9,11,11,121.3,11,256s109.9,244.5,245,244.5v.5c135.2,0,245-109.9,245-245S391.1,11,256,11ZM256,466.5c-116.2,0-210.5-94.3-210.5-210.5S139.8,45.5,256,45.5s210.5,94.3,210.5,210.5-94.3,210.5-210.5,210.5Z"/><path d="M410.6,259.4l1-1h-124.5c-6.8,0-12.2,4.4-12.2,10.7s38.4,12.2,38.4,26.7v35.5c0,9.2-7.3,16.5-16.5,16.5h-46.7c-24.3,0-48.1-24.8-53-52-2.4-14.6-38.4-19.9-38.4-26.7s5.3-10.7,12.2-10.7h16c5.3,0,9.2-4.4,9.2-9.2v-37.9c0-31.1,29.7-52.5,56.9-52.5h30.6c15.1,0,32.6-1,32.6,30.6v3.4c0,15.6,12.6,28.2,28.2,28.2h10.2c15.6,0,29.7-12.6,29.7-28.2v-3.4c0-36.9-9.7-56.4-84.6-56.4h-84.1c-57.9,0-90.9,46.2-90.9,118.1v.4c-1.2,3.9-4.2,6.9-8.3,6.9h-16c-6.8,0-12.2,4.4-12.2,10.7s32.2,12.2,37.1,25.5c3.3,48.1,36.1,85.4,102.9,85.4h60.8c36.9,0,76.3-6.3,94.3-22.9v-60.3c0-14.6,39.4-20.4,39.4-26.7s-5.3-10.7-12.2-10.7Z"/></svg>');
    -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
    -webkit-mask-position: center; mask-position: center;
    -webkit-mask-size: contain; mask-size: contain;
  }
  /* Hamburger: draw the whole icon by masking the toggle and painting it solid —
     the same single-element approach as the logo. Hide the theme's painted bars
     so only the masked fill shows. */
  .site-header .menu-text,
  .site-header .menu-text::before,
  .site-header .menu-text::after {
    background-color: transparent;
  }
  .site-header .menu-toggle {
    /* Center the toggle with the logo (theme's 30px top margin dropped it low). */
    margin-top: 0;
    align-self: center;
    background-color: #000;
    transition: background-color 120ms linear;
    -webkit-mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30"><rect x="0" y="4.5" width="30" height="3" rx="1.5"/><rect x="0" y="13.5" width="30" height="3" rx="1.5"/><rect x="0" y="22.5" width="30" height="3" rx="1.5"/></svg>');
    mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30"><rect x="0" y="4.5" width="30" height="3" rx="1.5"/><rect x="0" y="13.5" width="30" height="3" rx="1.5"/><rect x="0" y="22.5" width="30" height="3" rx="1.5"/></svg>');
    -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
    -webkit-mask-position: center; mask-position: center;
    -webkit-mask-size: contain; mask-size: contain;
  }
  /* Open state: swap the three bars for an X. */
  .site-header .menu-toggle[aria-expanded="true"] {
    -webkit-mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30"><path d="M6 6 L24 24 M24 6 L6 24" stroke="black" stroke-width="3" stroke-linecap="round" fill="none"/></svg>');
    mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30"><path d="M6 6 L24 24 M24 6 L6 24" stroke="black" stroke-width="3" stroke-linecap="round" fill="none"/></svg>');
  }
  /* Top-bar Shop / Cart: invert against whatever scrolls behind them, like the
     logo and hamburger — legible on both the light paper and dark posters.
     backdrop-filter (used for the logo/toggle) can't carve glyph shapes, so use
     white text under mix-blend-mode: difference, which inverts pixel-for-pixel
     against the backdrop. The phone header is transparent and un-transformed
     (see below), so the blend reaches the page content behind it. */
}

/* About page, phone: two theme quirks to correct.
   1) The columns pad only ~1rem against the row's -2rem pull, so the content
      lands at a ~16px gutter - narrower than the site's 32px (footer, other
      pages). Pad them to 2rem so About lines up with everything else.
   2) The theme orders the photo FIRST on mobile; move it to the bottom. */
@media (max-width: 575.98px) {
  .johngodfrey-about-block .biography,
  .johngodfrey-about-block .accomplishments,
  .johngodfrey-about-block .john-image {
    padding-left: 2rem;
    padding-right: 2rem;
  }
  .johngodfrey-about-block .john-image {
    order: 4; /* after biography (2) and accomplishments (3) */
  }
}

/* ===========================================================================
   Legal pages, footer, consent banner
   =========================================================================== */

/* ---- Legal pages (left-aligned to Home, as wide as the nav, all black) ---- */
.legal {
  max-width: 615px; /* about as wide as the nav, John Godfrey → Shop */
  margin: 0;
  /* Top-align to the same start spot as the John Godfrey / text pages (offset 0). */
  padding: 0 0 clamp(40px, 6vw, 80px);
  color: #000;
}
@media (min-width: 67.5rem) {
  /* Inset to line the left edge up under the first nav link: logo (3.125rem) +
     branding gap (0.75rem) + the nav link's own padding (0.45rem). */
  .legal { margin-left: 4.325rem; }
}
/* Headings mirror the John Godfrey page: 14px uppercase with the site's thick
   underline (2px, 4px offset), NOT a thin one. See docs/DESIGN-NOTES.md. */
.legal h1,
.legal-section h2 {
  font-size: var(--fs-md);
  font-weight: 500;
  text-transform: uppercase;
  text-decoration: underline;
  text-decoration-thickness: 2px;
  text-underline-offset: 4px;
  letter-spacing: normal;
  color: #000;
}
.legal h1 { margin: 0 0 0.7rem; }
.legal-section h2 { margin: 0 0 0.6rem; text-underline-offset: 6px; }
.legal-updated { font-size: var(--fs-sm); color: #000; margin: 0 0 1.6rem; text-transform: uppercase; letter-spacing: var(--track-label); }
/* Body copy matches the John Godfrey page: 0.75rem / line-height 1.9. */
.legal-intro { font-size: var(--fs-base); line-height: var(--lh-body); color: #000; margin: 0 0 0.5rem; }
.legal-section { margin-top: 1.8rem; }
.legal-section p, .legal-section li { font-size: var(--fs-base); line-height: var(--lh-body); color: #000; }
.legal-section p { margin: 0 0 0.7rem; }
.legal-section ul { margin: 0 0 0.7rem; padding-left: 1.15rem; }
.legal-section li { margin-bottom: 0.3rem; }
.legal-section a { text-decoration: underline; text-underline-offset: 0.15rem; color: #000; }
.legal-foot { margin-top: 2.4rem; padding-top: 1.2rem; border-top: 1.5px dotted #000; font-size: var(--fs-base); color: #000; }
.legal-foot a { color: inherit; text-decoration: underline; }

/* ---- Footer -------------------------------------------------------------- */
.site-footer__inner {
  display: flex;
  flex-wrap: wrap;
  gap: 12px 24px;
  align-items: center;
  justify-content: space-between;
  padding: 1.2rem 0;
}
.footer-social {
  font-size: var(--fs-sm);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: #000;
  margin-right: auto;
}
.footer-social:hover { text-decoration: underline; text-underline-offset: 0.15rem; }
.footer-legal { display: flex; flex-wrap: wrap; gap: 18px; }
/* Real links (Privacy/Terms) and the "Cookie settings" link-button read identically:
   underlined at rest with the ONE site underline (thickness/offset from the global
   rule below). Set explicitly so it doesn't depend on the <a> UA default — which a
   <button> never gets. */
.footer-legal a,
.footer-legal button {
  font-size: var(--fs-sm);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: #000;
  text-decoration: underline;
}
/* Strip the button chrome so the <button> looks like its sibling links. */
.footer-legal button {
  font-family: inherit;
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
}

/* Phone: Instagram and the legal links are two separate flex groups, so the
   legal group breaks to its own line and Instagram is stranded alone up top.
   Flow all five as one run of inline text instead - each label stays whole
   (nowrap) and the run wraps to more lines only as it needs to, reading as a
   single continuous line rather than being cut off after Instagram. */
@media (max-width: 35.99rem) {
  .site-footer__inner {
    display: block;
    line-height: 2.2; /* row spacing when the run wraps */
    padding: 1.2rem 0;
  }
  .footer-legal {
    display: inline; /* let its links join the same inline flow as Instagram */
  }
  .site-footer__inner .footer-social,
  .footer-legal a,
  .footer-legal button {
    display: inline;
    white-space: nowrap; /* keep each label intact; wrap between labels */
    margin: 0 0.9rem 0 0; /* even spacing between labels */
  }
}

/* The old cookie banner (.consent-banner/.consent-accept/…) was removed:
   the React opt-in banner (.consent, in app.css) replaces it and follows the
   current no-shadow / invert-on-hover conventions. */

/* ===========================================================================
   Form field lines: solid, not the theme's dotted underline
   =========================================================================== */
.wpcf7-form-control-wrap input[type="text"],
.wpcf7-form-control-wrap input[type="email"],
.wpcf7-form-control-wrap textarea,
form .wpcf7-form-control-wrap input[type="text"],
form .wpcf7-form-control-wrap input[type="email"],
form .wpcf7-form-control-wrap textarea {
  background-image: none !important;
  border-bottom: 2px solid #000 !important;
}
.wpcf7-form-control-wrap input[type="text"]:focus,
.wpcf7-form-control-wrap input[type="email"]:focus,
.wpcf7-form-control-wrap textarea:focus,
form .wpcf7-form-control-wrap input[type="text"]:focus,
form .wpcf7-form-control-wrap input[type="email"]:focus,
form .wpcf7-form-control-wrap textarea:focus {
  background-image: none !important;
  border-bottom: 3px solid #000 !important;
}

/* ===========================================================================
   Newsletter (mailing list) modal: site-wide heading norm + stamp edge
   =========================================================================== */
.modal .modal-title {
  font-size: var(--fs-md);
  font-weight: 500;
  text-transform: uppercase;
  text-decoration: underline;
  text-underline-offset: 0.18em;
  letter-spacing: normal;
  color: #000;
}

/* Mailing-list modal: two columns — a full-bleed vertical poster on the left, the
   signup panel on the right. Clean sharp-edged rectangle (no scallop, no rounding);
   the 1.5px dotted rule (same as the shop grid dividers / legal footer) defines the
   white card against the paper-grey overlay wash. !important beats the theme's own
   .modal-content border. */
#newsletterForm .modal-dialog {
  max-width: 880px;
  width: calc(100% - 2rem);
  margin: 1.75rem auto;
}
#newsletterForm .modal-content {
  background: #fff;
  border: 0 !important;      /* was a 1.5px dotted rule — now pinking teeth (below) */
  border-radius: 0 !important;
  flex-direction: row;      /* image | panel */
  padding: 0;               /* poster is full-bleed; the panel carries its padding */
  overflow: hidden;         /* clip the poster to the toothed rectangle */
  /* Pinking-shears teeth on the TOP and BOTTOM edges (the site's zig-zag motif),
     replacing the dotted border. Three mask layers unioned: a solid block for the
     middle, a row of upward triangles at the top, and downward triangles at the
     bottom. Each tile is 1px taller than the block's cut-off so they overlap and no
     hairline seam leaks. Teeth are the card's white; the paper overlay shows through
     the notches. Content clears them — the close button sits 17px in, the panel
     pads ~37px. */
  -webkit-mask-image:
    linear-gradient(#000, #000),
    url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='10.3' height='7.2'><path d='M0 7.2H10.3L5.15 0Z' fill='black'/></svg>"),
    url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='10.3' height='7.2'><path d='M0 0H10.3L5.15 7.2Z' fill='black'/></svg>");
  mask-image:
    linear-gradient(#000, #000),
    url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='10.3' height='7.2'><path d='M0 7.2H10.3L5.15 0Z' fill='black'/></svg>"),
    url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='10.3' height='7.2'><path d='M0 0H10.3L5.15 7.2Z' fill='black'/></svg>");
  -webkit-mask-size: 100% calc(100% - 12.4px), 10.3px 7.2px, 10.3px 7.2px;
  mask-size: 100% calc(100% - 12.4px), 10.3px 7.2px, 10.3px 7.2px;
  -webkit-mask-position: center, left top, left bottom;
  mask-position: center, left top, left bottom;
  -webkit-mask-repeat: no-repeat, repeat-x, repeat-x;
  mask-repeat: no-repeat, repeat-x, repeat-x;
}
#newsletterForm .newsletter-figure {
  flex: 0 0 50%;            /* equal halves — poster left, panel right */
  position: relative;       /* the absolute <img> fills the stretched column */
  margin: 0;
  background: #f5f4ef;
  align-self: stretch;
}
#newsletterForm .newsletter-figure img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
#newsletterForm .newsletter-panel {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1.1rem;
  padding: clamp(1.6rem, 3vw, 2.9rem);
}
/* The header only carries the close button now — collapse it so it adds no height
   (the button itself is absolutely positioned in the content's top-right corner). */
#newsletterForm .modal-header { padding: 0; min-height: 0; border: 0; }
/* Boldonse's tall caps overlap at the base line-height (--lh-flat ≈ 1) when the
   heading wraps to two lines ("FIRST TO / KNOW"), so open it up — same fix the
   About display title uses. */
#newsletterForm .display-title { margin: 0; line-height: 1.35; }
#newsletterForm .newsletter-lede {
  margin: 0;
  font-size: var(--fs-md);
  line-height: var(--lh-tight);
  color: #000;
}
#newsletterForm .modal-body { padding: 0; }
#newsletterForm .wpcf7-form { display: flex; flex-direction: column; gap: 1.1rem; }
/* CTA — filled black box, white text, full width of the inputs above it. NOTE: this
   is a one-off local button; there is no site-wide button system yet (TODO — define
   proper button styles and replace this). See docs/DESIGN-NOTES.md → "Buttons". */
#newsletterForm .wpcf7-submit {
  align-self: stretch;
  width: 100%;
  background: #000;
  color: #fff;
  border: 1.5px solid #000;
  border-radius: 0;
  padding: 0.8rem 1rem;
  text-decoration: none;
  text-transform: uppercase;
  transition: background-color 0.18s ease, color 0.18s ease;
}
#newsletterForm .wpcf7-submit:hover:not(:disabled) { background: #fff; color: #000; }
#newsletterForm .wpcf7-submit:disabled { opacity: 0.6; cursor: default; }
#newsletterForm .wpcf7-acceptance .wpcf7-list-item-label { font-size: var(--fs-sm); line-height: var(--lh-tight); }
/* Vertically centre the checkbox against its (wrapping) label text; keep it
   left-aligned like the rest of the form. */
#newsletterForm .wpcf7-list-item label { align-items: center; justify-content: flex-start; }

@media (max-width: 640px) {
  /* Single column on phones — drop the poster, keep just the form panel. */
  #newsletterForm .modal-content { flex-direction: column; }
  #newsletterForm .newsletter-figure { display: none; }
}

/* ONE overlay wash site-wide: the site's paper grey at 90%. Used by every modal
   (poster lightbox, newsletter / mailing list) and mirrored by .cart-overlay in
   app.css — never a black scrim. See docs/DESIGN-NOTES.md → "Overlays". */
.modal-backdrop { background-color: #f5f4ef !important; }
.modal-backdrop.show { opacity: 0.9 !important; }

/* ---- Poster lightbox: image + caption ------------------------------------ */
/* Take the close-button header out of flow so the image + caption centre in the
   full modal, level with the prev/next arrows (they sit at the modal centre). */
#posterModal .modal-content-poster { position: relative; }
#posterModal .modal-header {
  position: absolute;
  top: 0;
  right: 0;
  z-index: 20;
}
#posterModal .modal-body { display: flex; overflow: visible; }
/* The theme animates the image-wrapper with a transform transition (scale
   0.7->1). For the stacked wide layout that transition gets stuck at scale(0.7)
   — and a running transition sits ABOVE !important in the cascade, so it can't
   be overridden by a normal rule. Kill the transition so the transform resolves
   instantly to its resting size. */
#posterModal .modal-dialog .image-wrapper { transition: none !important; }
#posterModal.show .modal-dialog .image-wrapper { transform: none !important; opacity: 1 !important; }
/* Give the modal a wide, fixed track so the image can sit dead-centre with the
   caption in the right-hand column and the prev/next arrows out at the edges,
   clear of the image (they used to shrink-wrap the image+caption group, which
   shoved the image left onto the left arrow). */
#posterModal .modal-content-poster { width: 92vw; max-width: 92vw; }
#posterModal .modal-navigation { position: absolute; inset: 0; pointer-events: none; }

/* ---- Lightbox controls: white circles behind prev / next / close ----------
   The theme draws the arrows as a rotated box with two borders, and the X as
   ::before/::after bars on the button. Here the BUTTON becomes the white circle
   and an inner <span> carries the glyph, so the circle keeps the black glyph
   legible wherever it sits over dark artwork. */
#posterModal .modal-nav-btn,
#posterModal .close-btn {
  width: 3.25rem;   /* 2.5rem + 30% */
  height: 3.25rem;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
}
/* Close: pin to the VIEWPORT's top-right corner. The theme positions it absolutely
   inside the modal content box, which is vertically centred — so on a short/narrow
   layout the X drifted into the middle of the screen instead of the corner. */
#posterModal .close-btn {
  position: fixed;
  top: 1.25rem;
  right: 1.25rem;
  z-index: 960;
}
/* Prev/next: undo the theme's rotation on the button and centre it vertically. */
#posterModal .modal-nav-btn {
  top: 50%;
  transform: translateY(-50%);
}
#posterModal .modal-nav-btn span {
  display: block;
  width: 0.845rem;   /* glyphs scaled +30% with the circles */
  height: 0.845rem;
  border-top: 0.208rem solid #000;
  border-right: 0.208rem solid #000;
  border-radius: 0.117rem;
}
#posterModal .modal-prev-btn span { transform: rotate(-135deg); margin-left: 0.26rem; }
#posterModal .modal-next-btn span { transform: rotate(45deg); margin-right: 0.26rem; }
/* Close: drop the theme's bars and redraw the X on the inner span, so the
   button's own box is free to be the circle. */
#posterModal .close-btn::before,
#posterModal .close-btn::after { content: none; }
#posterModal .close-btn span {
  position: relative;
  display: block;
  width: 1.235rem;   /* 0.95rem + 30% */
  height: 1.235rem;
}
#posterModal .close-btn span::before,
#posterModal .close-btn span::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 0;
  width: 100%;
  height: 0.208rem;   /* bar thickness +30% */
  border-radius: 0.117rem;
  background: #000;
}
#posterModal .close-btn span::before { transform: translateY(-50%) rotate(45deg); }
#posterModal .close-btn span::after { transform: translateY(-50%) rotate(-45deg); }

/* Centre the image with a balanced empty column on the left and the caption
   column on the right — 1fr auto 1fr keeps the image centred in the modal. */
.poster-modal-layout {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  align-content: center;   /* keep the row at image height so "end" = image bottom */
  width: 100%;
  height: 100%;
  column-gap: clamp(1.25rem, 2.5vw, 2.5rem);
}
.poster-modal-layout .image-wrapper {
  grid-column: 2;
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 0;
}
/* Image: centred, capped so both side columns keep room for the caption. */
#posterModal .modal-dialog .image-wrapper img,
#posterModal #posterImageElement {
  height: auto;
  width: auto;
  max-height: 87.5vh;   /* was 82vh — ~30% less empty space above/below the image */
  max-width: min(58vw, calc(92vw - 34rem));
  /* Same lift as the gallery thumbnails. */
  filter: drop-shadow(0 3px 3px rgba(0, 0, 0, 0.24));
}
/* Caption: right column, pinned to the image's BOTTOM, left-aligned next to it.
   BUY sits underneath the text. Same placement for portrait and horizontal. */
.modal-caption {
  grid-column: 3;
  align-self: end;
  justify-self: start;
  box-sizing: border-box;
  width: 15rem;
  max-width: 15rem;
  text-align: left;
  color: #000;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  /* White content card, no border and no shadow. The grid column-gap keeps it the
     same distance from the image as before. */
  background: #fff;
  padding: 1rem 1.15rem;
}
.mc-text {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  min-width: 0;
}
/* Typography matches the gallery caption that sits under the image
   (.image-description .title / .category): --fs-base title over an --fs-xs uppercase
   category, both 500 at --lh-tight. Keep these in step with that rule. */
.modal-caption .mc-title {
  font-size: var(--fs-base);
  font-weight: 500;
  line-height: var(--lh-tight);
}
.modal-caption .mc-category {
  font-size: var(--fs-xs);
  font-weight: 500;
  text-transform: uppercase;
  line-height: var(--lh-tight);
  margin-top: 0.15rem;
  color: #000;
}
/* PRINT / EDITION: plain text + page glyph, NO outline — the icon leads, and with no
   border or padding its left edge sits flush with the title/category text above it.
   Sits directly under the caption text so the card stays short. */
.modal-caption .mc-buy {
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  margin-top: 0.5rem;
  padding: 0;
  border: 0;
  background: transparent;
  color: #000;
  text-decoration: none;
  text-transform: uppercase;
  font-size: var(--fs-sm);
  font-weight: 500;
  letter-spacing: normal;
  line-height: var(--lh-flat);
}
.modal-caption .mc-buy svg { width: 0.95em; height: 0.95em; flex: 0 0 auto; }
.modal-caption .mc-buy:hover {
  text-decoration: underline;
  text-decoration-thickness: 2px;
  text-underline-offset: 3px;
}
.modal-caption .mc-buy[hidden] { display: none; }

/* Wide (horizontal) posters: bigger image with the caption stacked UNDERNEATH
   it, exactly like the main-page grid — title/category left, BUY on the right,
   vertically centred against the two lines of text. */
#posterModal.is-wide .poster-modal-layout {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  width: fit-content;
  max-width: 100%;
  margin: 0 auto;
  gap: 1rem;
}
/* Height-driven so the box equals the rendered image (width follows aspect);
   this lets the layout shrink-wrap to the real image width, so the caption
   underneath spans it exactly (title at the left edge, BUY at the right). */
#posterModal.is-wide .modal-dialog .image-wrapper img,
#posterModal.is-wide #posterImageElement {
  height: auto;
  width: auto;
  max-height: 76vh;
  /* Keep clear side margins for the prev/next arrows and the X so a wide image
     never covers them. 8rem each side comfortably clears the ~35px controls. */
  max-width: calc(100vw - 16rem);
}
#posterModal.is-wide .modal-caption {
  /* Shrink-wrap the box to its own content rather than spanning the image, and
     keep its left edge aligned with the image's. */
  align-self: flex-start;
  justify-self: auto;
  width: fit-content;
  max-width: 100%;
  /* Stack like the tall layout: title, category, then the buy label UNDERNEATH the
     text — not off to its side. */
  flex-direction: column;
  align-items: flex-start;
  justify-content: flex-start;
  gap: 0;
}
#posterModal.is-wide .modal-caption .mc-buy {
  flex-shrink: 0;             /* keep the label on one line */
}

/* ---- Narrow windows: stack instead of squeezing the poster -----------------
   The side-by-side layout reserves a fixed 34rem for the caption column
   (max-width: calc(92vw - 34rem)). That reserve doesn't shrink, so as the window
   narrows it eats the poster — by ~590px the image collapses to nothing and the
   art is effectively hidden behind its own description. Below 60rem the poster
   would be under ~340px wide, so switch to the same stacked treatment the
   horizontal pieces use: art on top, info card underneath pinned to its left. */
@media (max-width: 60rem) {
  #posterModal .poster-modal-layout {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    width: fit-content;
    max-width: 100%;
    margin: 0 auto;
    gap: 1rem;
  }
  /* Full width now that nothing sits beside it — just clear of the arrows/X. */
  #posterModal .modal-dialog .image-wrapper img,
  #posterModal #posterImageElement {
    height: auto;
    width: auto;
    max-height: 72vh;          /* leaves room for the card + gap underneath */
    max-width: calc(100vw - 9rem);
  }
  #posterModal .modal-caption {
    align-self: flex-start;    /* shrink-wrapped, pinned to the image's left edge */
    justify-self: auto;
    width: fit-content;
    max-width: 100%;
    /* Stacked here too: the lightbox always puts the buy label UNDER the caption
       text, whatever the poster's shape or the window's width. */
    flex-direction: column;
    align-items: flex-start;
    justify-content: flex-start;
    gap: 0;
  }
  #posterModal .modal-caption .mc-buy {
    flex-shrink: 0;
  }
}
