/* ===========================================================================
   Unvarnished.art — design system
   ---------------------------------------------------------------------------
   Every token below maps to a Dawn `settings_schema.json` entry so the theme
   editor stays useful after handoff. Section-level comments name the Dawn
   file each block becomes.
   =========================================================================== */

/* --- tokens -------------------------------------------------------------- */
:root {
  /* surface — linen, not cream. The site should look like the restored state,
     not the varnished one. Neutral greys, no amber anywhere. */
  --paper:        #FCFCFA;
  --paper-warm:   #F4F3EF;   /* linen; name kept so the Dawn mapping holds */
  --surface:      #FFFFFF;
  --ink:          #17181A;
  --ink-soft:     #4B4E52;
  --ink-muted:    #83868B;
  --rule:         #E6E5E1;
  --rule-strong:  #C9C8C3;

  /* accent — ultramarine. The pigment yellowed varnish destroys first, so the
     brand signals in the exact colour it exists to give back. */
  --accent:         #2447B8;
  --accent-bright:  #4064D6;
  --accent-wash:    #EDF1FB;

  /* the dark room the viewer opens into — neutral, not warm */
  --void:         #0E0F11;
  --void-soft:    #1A1C1F;
  --on-void:      #ECEDEE;
  --on-void-mute: #8E9297;

  /* type */
  --display: "Iowan Old Style", "Palatino Linotype", Palatino, "Book Antiqua",
             Georgia, serif;
  --body:    "Iowan Old Style", Georgia, "Times New Roman", serif;
  --ui:      -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, Helvetica,
             Arial, sans-serif;

  /* rhythm */
  --gutter: 28px;
  --max:    1440px;
  --max-prose: 660px;

  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
}

@media (max-width: 800px) {
  :root { --gutter: 18px; }
}

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

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

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--body);
  font-size: 17px;
  line-height: 1.62;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

img { display: block; max-width: 100%; height: auto; }
a   { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }

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

.wrap {
  width: 100%;
  max-width: var(--max);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.prose { max-width: var(--max-prose); }

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

/* --- type scale ---------------------------------------------------------- */
h1, h2, h3, h4 {
  font-family: var(--display);
  font-weight: 400;
  letter-spacing: -0.012em;
  line-height: 1.12;
  margin: 0;
}

.t-display { font-size: clamp(2.6rem, 6vw, 4.6rem); line-height: 1.04; letter-spacing: -0.022em; }
.t-h1      { font-size: clamp(2rem, 4vw, 3rem); }
.t-h2      { font-size: clamp(1.5rem, 2.6vw, 2.1rem); }
.t-h3      { font-size: 1.22rem; }

.t-label {
  font-family: var(--ui);
  font-size: 0.688rem;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-muted);
}

.t-lede {
  font-size: clamp(1.06rem, 1.6vw, 1.28rem);
  line-height: 1.55;
  color: var(--ink-soft);
}

.t-small { font-family: var(--ui); font-size: 0.82rem; line-height: 1.5; }

em.q { font-style: italic; }

/* --- buttons ------------------------------------------------------------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center;
  gap: 0.5em;
  font-family: var(--ui);
  font-size: 0.82rem;
  font-weight: 600;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  padding: 15px 26px;
  border: 1px solid var(--ink);
  background: var(--ink);
  color: var(--paper);
  transition: background 0.2s var(--ease), color 0.2s var(--ease),
              border-color 0.2s var(--ease);
}
.btn:hover { background: var(--accent); border-color: var(--accent); }

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

.btn--wide { width: 100%; }

.btn--quiet {
  border-color: var(--rule-strong);
  background: transparent;
  color: var(--ink-soft);
  padding: 12px 20px;
}
.btn--quiet:hover { background: var(--paper-warm); color: var(--ink); border-color: var(--rule-strong); }

/* A short label needs less padding to hold, and above the fold every
   vertical pixel is being fought over — this is deliberately smaller than
   the standard .btn, not just shorter text in the same box. */
.btn--slim { padding: 10px 20px; font-size: 0.74rem; }

/* ===========================================================================
   ANNOUNCEMENT BAR                      -> sections/announcement-bar.liquid
   =========================================================================== */
.announce {
  background: var(--void);
  color: var(--on-void);
  font-family: var(--ui);
  font-size: 0.75rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  text-align: center;
  padding: 10px var(--gutter);
}
.announce a, .announce button {
  color: inherit; font: inherit; letter-spacing: inherit;
  border-bottom: 1px solid rgba(236, 237, 238, 0.4); padding-bottom: 1px;
}
.announce a:hover, .announce button:hover {
  border-color: var(--accent-bright); color: var(--accent-bright);
}

/* ===========================================================================
   EMAIL CAPTURE SHEET                     -> snippets/study-file-sheet.liquid
   Opened from the announcement bar. The bar sells; this page explains,
   asks consent, and makes the privacy promise in plain words.
   =========================================================================== */
.sheet {
  position: fixed; inset: 0; z-index: 220;
  display: none;
  place-items: center;
  padding: var(--gutter);
  background: rgba(14, 15, 17, 0.62);
  backdrop-filter: blur(3px);
}
.sheet.is-open { display: grid; }
body.sheet-open { overflow: hidden; }

.sheet__card {
  background: var(--paper);
  max-width: 480px;
  width: 100%;
  padding: 38px 38px 32px;
  border-top: 3px solid var(--accent);
  max-height: 92dvh;
  overflow-y: auto;
}
@media (max-width: 520px) { .sheet__card { padding: 28px 22px 24px; } }

.sheet__close {
  float: right;
  font-family: var(--ui); font-size: 1.3rem; line-height: 1;
  color: var(--ink-muted); margin: -8px -8px 0 0;
}
.sheet__close:hover { color: var(--ink); }

.sheet__card h2 { font-size: 1.62rem; margin-bottom: 12px; }
.sheet__what { color: var(--ink-soft); font-size: 0.98rem; margin: 0 0 22px; }

.sheet__form { display: flex; gap: 0; }
.sheet__form input {
  flex: 1; min-width: 0;
  font-family: var(--ui); font-size: 0.92rem;
  padding: 14px 15px;
  border: 1px solid var(--rule-strong); border-right: 0;
  background: var(--surface); color: var(--ink);
}
.sheet__form input:focus { outline: none; border-color: var(--accent); }
.sheet__form button { white-space: nowrap; }

.sheet__terms {
  font-family: var(--ui); font-size: 0.78rem; line-height: 1.55;
  color: var(--ink-muted);
  margin: 18px 0 0;
}
.sheet__terms + .sheet__terms { margin-top: 10px; }
.sheet__terms b { color: var(--ink-soft); font-weight: 600; }

.sheet__done { text-align: center; padding: 14px 0 4px; }
.sheet__done p { color: var(--ink-soft); }

/* ===========================================================================
   HEADER — transparent over hero, opaque on scroll  -> sections/header.liquid
   =========================================================================== */
.hdr {
  position: sticky; top: 0; z-index: 60;
  background: transparent;
  border-bottom: 1px solid transparent;
  transition: background 0.32s var(--ease), border-color 0.32s var(--ease),
              backdrop-filter 0.32s var(--ease);
}
.hdr.is-stuck {
  background: rgba(251, 249, 245, 0.93);
  backdrop-filter: saturate(180%) blur(14px);
  -webkit-backdrop-filter: saturate(180%) blur(14px);
  border-bottom-color: var(--rule);
}

.hdr__in {
  display: flex; align-items: center; justify-content: space-between;
  gap: 24px;
  height: 74px;
}
.hdr.is-stuck .hdr__in { height: 62px; transition: height 0.32s var(--ease); }

.hdr__mark { display: flex; align-items: center; }
.hdr__markimg { display: block; height: 26px; width: auto; }

.hdr__nav { display: flex; gap: 30px; }
.hdr__nav a {
  font-family: var(--ui);
  font-size: 0.78rem;
  font-weight: 500;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  padding: 6px 0;
  border-bottom: 1px solid transparent;
}
.hdr__nav a:hover, .hdr__nav a[aria-current] { border-bottom-color: var(--accent); }

.hdr__tools { display: flex; align-items: center; gap: 18px; }
.hdr__cart {
  font-family: var(--ui); font-size: 0.78rem; letter-spacing: 0.09em;
  text-transform: uppercase;
}
.hdr__cart b { font-weight: 600; color: var(--accent); }

.hdr__burger { display: none; }

@media (max-width: 900px) {
  .hdr__nav { display: none; }
  .hdr__burger { display: block; font-size: 1.3rem; line-height: 1; }
}

/* Phone only (620px is the site's phone/tablet line elsewhere too) — kept
   exactly as it was; this was already tuned and Roberto confirmed the
   iPhone fold is fine as it stands. */
@media (max-width: 620px) {
  .announce { padding: 7px var(--gutter); }
  .hdr__in { height: 58px; }
  .hdr.is-stuck .hdr__in { height: 50px; }
}

/* iPad and desktop, NOT the phone: too much white space was sitting above
   the hero slider on both, and the picture's own size isn't moving, so the
   fix is stealing a bit more room from the chrome above it instead. */
@media (min-width: 621px) {
  .announce { padding: 6px var(--gutter); }
  .hdr__in { height: 54px; }
  .hdr.is-stuck .hdr__in { height: 48px; }
}

/* ===========================================================================
   FEATURED REVEAL                        -> sections/featured-reveal.liquid
   The opening moment: one live slider, deliberately short so the grid below
   breaks the fold and people scroll. This is the ONLY live slider on the
   page — the grid tiles stay static.
   =========================================================================== */
.feature {
  /* The rule that used to divide this from The Collection below is gone —
     reported as just taking up room on desktop. "The Collection" itself,
     enlarged to match this section's own title, now does the job of
     marking where one ends and the other begins.
     Bottom padding trimmed further: enlarging that heading pushed the
     grid's own first row down past the fold on some screens — tightened
     here rather than by shrinking the hero picture, which stays exactly
     the size it was. */
  padding: 18px 0 0;
}
.feature__in {
  display: grid;
  grid-template-columns: 0.62fr 1.75fr;
  gap: 38px;
  align-items: center;
}
@media (max-width: 900px) {
  .feature__in { grid-template-columns: 1fr; gap: 22px; }
}

/* Makes .feature__meta a query container for its own inline size, so the
   title below can size itself off the actual column width rather than the
   full viewport (vw). That distinction matters a lot here: at the 900px
   breakpoint the layout goes from one column to two, and the type column's
   real width does NOT track the viewport 1:1 — a vw-based font can be sized
   fine for a 1400px-wide phone-stacked column and still be far too big for
   the ~300-450px two-column width at a similar-ish viewport, because the
   column is only ~35% of that viewport, not 100% of it. cqi reads the
   container's own width, so the same formula holds at every breakpoint. */
.feature__meta { container-type: inline-size; }
.feature__meta .t-label { display: block; margin-bottom: 12px; }
/* "Paintings should look painted." is now the whole first line (the
   "Not antiqued." correction moved to its own line below), and it has to
   hold that one line on iPad and desktop without wrapping — Roberto's
   explicit requirement. The 5.5cqi figure isn't a guess: for this phrase,
   at roughly 0.55em of width per character across 30 characters, the line
   needs about 16.5x its own font-size in container width to fit. Sizing
   the font at no more than 1/16.5 (~6%) of the container's width keeps
   that ratio under 1 at every container size, so the fit holds whether the
   column is narrow (iPad landscape, ~300px) or wide (a big desktop
   window) — 5.5cqi leaves a safety margin under that ceiling. The 2.15rem
   cap only matters on very wide desktop windows, where the column itself
   gets wide enough that the formula would otherwise keep scaling the type
   up past a comfortable reading size. */
.feature__title {
  font-family: var(--display);
  font-size: clamp(1.15rem, 5.5cqi, 2.15rem);
  line-height: 1.22;
  letter-spacing: -0.01em;
}

/* Its own line now, under the claim — Roberto's request, reverting the
   single-line version this was briefly condensed to. Same font (1em) and
   the same fit guarantee as .feature__title above: "Not antiqued." is
   shorter than the line above it, so if that one holds without wrapping,
   this one does too. */
.feature__title-aside {
  display: block;
  font-size: 1em;
  font-style: italic;
  color: var(--ink-soft);
  letter-spacing: -0.008em;
  margin-top: 0.12em;
}

.feature__attrib {
  font-family: var(--ui); font-size: 0.84rem; color: var(--ink-muted);
  margin: 10px 0 0;
}
.feature__blurb {
  margin: 14px 0 0; color: var(--ink-soft); font-size: 1rem;
  max-width: 42ch;
}
/* The closing line ("Artist Roberto Azank Unvarnished them.") breaks onto
   its own line under the paragraph now via a <br> in the markup —
   Roberto's request, so it reads as a short, italic answer to the
   sentence above rather than trailing off the end of it. No width/fit
   constraint here: unlike the title, Roberto didn't ask this one to hold
   a single line, and it already does at any column width that matters.

   The line plays the same two-part game as the h1 above it: "Artist
   Roberto Azank" is the upright half (mirrors "Paintings should look
   painted."), "Unvarnished them." is the italic half (mirrors the
   .feature__title-aside "Not antiqued."). .feature__blurb-name below
   un-italicises just the name portion; the rest of the <em> stays
   italic by default.

   Sized to match .feature__title-aside ("Not antiqued.") exactly — same
   clamp values, same container it's read against, AND now the same
   font-family and letter-spacing too. Those last two weren't set here
   before: .feature__title-aside sits inside the <h1> and inherits
   var(--display) (Iowan Old Style, Palatino, ...) from the h1 rule further
   up; .feature__blurb em sits inside a <p> and was inheriting var(--body)
   instead (Iowan Old Style, Georgia, Times New Roman — a shorter, plainer
   stack). Both start with the same first-choice font, so on a machine that
   has it the glyphs matched — but the two stacks fall back differently
   past that, and a shorter, denser fallback face reads visibly larger than
   Palatino at the same nominal font-size, which is what "seems larger" was
   actually seeing. Pinning the family (and matching the title's
   letter-spacing) removes that gap regardless of which face ends up
   rendering. */
.feature__blurb em {
  display: block;
  margin-top: 0.2em;
  font-family: var(--display);
  font-size: clamp(1.15rem, 5.5cqi, 2.15rem);
  font-style: italic;
  line-height: 1.22;
  letter-spacing: -0.008em;
  color: var(--ink-soft);
}
.feature__blurb-name { font-style: normal; }
.feature__cta { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 22px; }
@media (max-width: 900px) { .feature__cta .btn { flex: 1 1 auto; } }

/* The hero makes a confident claim; this concedes the judgement immediately
   underneath it. They are a pair — don't ship one without the other. */
.feature__honest {
  font-family: var(--ui);
  font-size: 0.76rem;
  line-height: 1.5;
  color: var(--ink-muted);
  margin: 14px 0 0;
  padding-left: 12px;
  border-left: 2px solid var(--rule-strong);
  max-width: 44ch;
}
.feature__honest a { color: var(--accent); white-space: nowrap; }
.feature__honest a:hover { border-bottom: 1px solid var(--accent); }

.feature__media { margin: 0; }
.feature__caption {
  font-family: var(--ui);
  font-size: 0.74rem;
  line-height: 1.45;
  color: var(--ink-muted);
  margin-top: 9px;
  max-width: 62ch;
}
.feature__caption b {
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  font-size: 0.64rem;
  color: var(--accent);
  display: block;
  margin-bottom: 2px;
}
.feature__caption span { display: block; max-width: 92ch; }

/* .feature__mission (the "Every giclée…"/Shop CTA under the hero reveal) is
   gone — the floating Shop button now covers that job on every page, and
   .feature__meta (below, near .feature__toolbar) replaces it with the
   grid-tile-style title/artist/Learn More link instead. */

/* the inline slider itself */
/* The frame takes the painting's shape.
   It used to be a fixed 3:2 whatever was in it, so a 7:5 Bruegel sat in it
   with a white column down either side — and the two labels, which are
   anchored to the frame, landed on the white instead of on the picture,
   missing it by about a letter. That reads as a mistake rather than a
   margin. `--ar` is set from the master once it is measured; the fallback
   is what it always was, for the moment before that happens.
   `max-width` has to follow, or the height cap below makes the box wide
   again and the white columns come back. */
.reveal {
  position: relative;
  overflow: hidden;
  background: var(--paper-warm);
  aspect-ratio: var(--ar, 3 / 2);
  /* 52vh -> 60vh on desktop only (this rule is overridden below 900px).
     Desktop has no fold pressure the way a phone does — the window is wide,
     not tall — so there's room to give the painting more of the screen
     without touching the iPad/iPhone caps below, which were tuned earlier
     specifically to keep the grid's first row peeking above the fold. */
  max-height: 60vh;
  max-width: calc(60vh * var(--arn, 1.5));
  margin-inline: auto;
  cursor: ew-resize;
  touch-action: pan-y;
  user-select: none;
}
@media (max-width: 900px) {
  /* An iPad in portrait has an unusually tall reveal (44vh of a ~1180px-tall
     screen is over 500px) without any of the phone-specific chrome trimming
     below, so the grid title one section down was barely peeking above the
     fold — worse than on a phone, where the picture itself is smaller in
     absolute terms. 40vh buys back real pixels on the picture without
     visibly shrinking it. dvh is added alongside vh for the same reason the
     short-landscape rule uses it: Safari's collapsing address bar makes vh
     a moving target on both an iPad and a phone. */
  .reveal {
    max-height: 40vh;
    max-width: calc(40vh * var(--arn, 1.3333));
    max-height: 40dvh;
    max-width: calc(40dvh * var(--arn, 1.3333));
    aspect-ratio: var(--ar, 4 / 3);
  }
  .feature { padding: 12px 0 12px; }
}
/* Phone keeps the 12px top set just above (already tuned, fold confirmed
   fine there). iPad and desktop get a deeper cut on top only — the bottom
   padding was already 0 on desktop and stays that way — so the slider sits
   closer to the header instead of leaving white space above it. */
@media (min-width: 621px) and (max-width: 900px) {
  .feature { padding-top: 6px; }
}
@media (min-width: 901px) {
  .feature { padding-top: 8px; }
}

/* transformable layer — the whole toy lives here, not in a lightbox */
.reveal__pan {
  position: absolute; top: 0; left: 0;
  transform-origin: 0 0;
  will-change: transform;
}

/* Once a painting is tiled, stop promoting the layer.

   `will-change: transform` asks the browser to keep this element in its own
   compositing layer, permanently rasterised and ready to move. That is a
   good bargain for a 3,600px picture. It is a terrible one for a
   ten-thousand-pixel grid: the layer has to be rasterised at its full size,
   the browser stalls while it tries, gives up somewhere short of it, and
   from then on shows a scaled copy of that attempt — with every tile
   boundary visible, and still visible after you zoom back out, because the
   layer is cached and nothing has invalidated it.

   Laying the tiles out in real CSS pixels was supposed to end exactly this,
   and this declaration was quietly undoing it. Without the promotion the
   browser paints what is on screen and no more, which is the whole point of
   tiling. */
.is-tiled .reveal__pan,
.is-tiled .viewer__pan { will-change: auto; }
.reveal__pan img {
  position: absolute; top: 0; left: 0;
  max-width: none;
  -webkit-user-drag: none; user-select: none;
}
.reveal__after  { position: relative; }   /* establishes the box */
.reveal__before { z-index: 2; }           /* clipped on top, full opacity */

.reveal.is-panning { cursor: grabbing; }
.reveal.is-wiping  { cursor: ew-resize; }
.reveal.is-zoomed  { cursor: grab; }

/* A sibling of .reveal now, not a child — it used to sit over the bottom
   corner of the picture; now it sits just below it, right-aligned, same
   corner in spirit. Off the image and onto the page, so the dark
   glass-on-photo styling gives way to a plain light button. */
/* The reveal is letterboxed inside .feature__media — its own max-width caps
   it below the column's full width, and margin-inline:auto centers it in
   whatever room is left over. Right-aligning .reveal__zoom to the FIGURE's
   width put "Reset" past the picture's own right edge. Giving this the same
   max-width and centering rule as .reveal keeps the two boxes the same
   width, in the same place, so flex-end here lands exactly on the
   picture's right edge instead of the column's. */
.reveal__zoom {
  display: flex; justify-content: flex-end; gap: 5px;
  margin-top: 8px;
  max-width: calc(52vh * var(--arn, 1.5));
  margin-inline: auto;
}
@media (max-width: 900px) {
  .reveal__zoom { max-width: calc(44vh * var(--arn, 1.3333)); }
}

/* Zoom controls and the mission line ("Every giclée…"/Shop) used to be two
   stacked rows below the reveal — Roberto asked for them on one row, zoom
   left and mission right, to buy back a whole line above the fold. Same
   width/centering as the reveal itself; the two children stop centering or
   right-aligning themselves once they're inside it, since the row's own
   space-between does that job now.
   align-items is flex-start, not center: the mission text now runs two
   lines (see the hard break in the markup) and is taller than the single
   row of zoom buttons. Centering the row centered the zoom buttons against
   that extra height too, dropping them visibly below the reveal instead of
   sitting flush against it. Aligning both to the top keeps the zoom row
   anchored right under the picture regardless of how tall the mission
   text runs. */
.feature__toolbar {
  /* Was space-between: controls hard left, painting/Learn More hard right —
     which put Learn More right where the page's own fixed Shop button
     floats, on both iPad and desktop, so one obscured the other. A plain
     centered flex row (tried next) fixed that but crammed the two groups
     together in the middle instead. Landed on two even halves: controls
     sit at the start of the left half, the painting/Learn More group
     centers itself within the right half — clear of the Shop corner,
     and with real air between the two groups instead of both. */
  display: grid;
  /* minmax(0, 1fr), not a bare 1fr — a plain 1fr track won't shrink below
     its content's own min-content width, so if either group ever ran wider
     than half the row it could push past the middle line into the other
     column instead of wrapping. minmax(0, 1fr) lets the track actually
     honour the 50/50 split. */
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  align-items: flex-start;
  gap: 10px 22px;
  margin-top: 10px;
  /* Has to match .reveal's own max-width exactly, in the same unit — this
     was left at the old 52vh when the reveal itself moved to 60vh, so the
     toolbar (Learn More / Shop) sat narrower than the picture above it. */
  max-width: calc(60vh * var(--arn, 1.5));
  margin-inline: auto;
}
.feature__toolbar .reveal__zoom { justify-self: center; }
.feature__toolbar .feature__paintmeta { justify-self: center; }
@media (max-width: 900px) {
  .feature__toolbar {
    max-width: calc(40vh * var(--arn, 1.3333));
    max-width: calc(40dvh * var(--arn, 1.3333));
  }
}
@media (max-width: 620px) {
  /* Too narrow on a phone for two real columns — stack back to a single
     centered flow, same as before this change. */
  .feature__toolbar { display: flex; flex-wrap: wrap; justify-content: center; }
  .feature__toolbar .feature__paintmeta { justify-self: auto; }
}
.feature__toolbar .reveal__zoom {
  margin-top: 0; max-width: none; margin-inline: 0;
}

/* Replaces .feature__mission (the "Every giclée…"/Shop pitch) now that the
   floating Shop button covers that job everywhere. Same "second way in" as
   a grid tile's caption — title, artist, a visible Learn More cue, all one
   link to the product page — deliberately NOT on the picture itself: this
   hero's whole job is demonstrating the drag gesture, so a click-through on
   the image would cut that lesson short, and it would stand a first-time
   visitor one page deep with no way back down to the collection grid.

   Named .feature__paintmeta, NOT .feature__meta — that name was already
   taken by the unrelated <div class="feature__meta"> that wraps the H1
   claim ("Paintings should look painted. Not antiqued."), higher up the
   same section. Reusing it here right-aligned that H1 on every width down
   to 620px: invisible on a desktop line that fits in one go, but on an
   iPad-width wrap, the second line's left edge no longer matched the
   first — read as a stray extra space before the wrapped word. Its own
   name means its own rules, and the H1 is untouched: left-aligned at
   every width now, on desktop included. */
/* Was a single stacked block — title, artist, "Learn More" each on their
   own line, three lines tall. Roberto's mockup put "Learn More" beside the
   title/artist instead of under it, in line with the zoom controls to its
   left — one row saved. .feature__paintid keeps the title+artist pairing
   stacked (they read as one unit); .feature__meta-cta now sits beside that
   pairing rather than below it. */
.feature__paintmeta {
  /* Was right-aligned to sit at the picture's right edge, back when the
     toolbar was space-between. Now the toolbar centers the whole row, so
     this just needs to read left-to-right like normal text. */
  display: flex; align-items: baseline; gap: 12px; justify-content: flex-start;
  text-align: left; color: inherit; text-decoration: none;
  flex: 0 0 auto;
}
.feature__paintid { display: block; }
.feature__title-sm {
  font-family: var(--display); font-size: 0.92rem; line-height: 1.25; display: block;
}
.feature__master-sm {
  font-family: var(--ui); font-size: 0.72rem; color: var(--ink-muted); display: block; margin-top: 2px;
}
/* Named .feature__meta-cta, not .feature__cta — that name already belongs
   to the old button-row class from the pre-floating-Shop hero (line ~361),
   which a smoke test guards against ever coming back. This is a different
   element entirely; reusing the name would have fooled that guard. */
.feature__meta-cta {
  font-family: var(--ui); font-size: 0.72rem; font-weight: 600;
  letter-spacing: 0.03em; color: var(--accent); white-space: nowrap;
}
.feature__paintmeta:hover .feature__title-sm { text-decoration: underline; }
.feature__paintmeta:hover .feature__meta-cta { color: var(--accent-bright); text-decoration: underline; }
@media (max-width: 620px) {
  .feature__paintmeta { text-align: left; justify-content: flex-start; }
}
.reveal__zoom button {
  /* Equal width on all three, "Reset" included — it was the widest of the
     three (real text vs. a single glyph), so centering the group by its
     own bounding box put "+" left of true center, and let "Reset" reach
     further into the picture's right half than the other two ever did.
     Same width everywhere means the group's centre and the "+" button's
     centre are now the same point. */
  font-family: var(--ui); font-size: 0.68rem; font-weight: 600;
  letter-spacing: 0.06em;
  width: 44px; height: 28px; padding: 0;
  text-align: center;
  color: var(--ink);
  background: var(--paper);
  border: 1px solid var(--rule-strong);
  transition: all 0.18s var(--ease);
}
.reveal__zoom button:hover { background: var(--ink); color: var(--paper); border-color: var(--ink); }

/* "Enlarge" doesn't fit the 44px square built for a single glyph or
   "Reset" — the word ran edge to edge, the last letter touching the
   border. Its own width, sized to the text with real side padding,
   rather than the shared fixed-width rule above.

   THE SELECTOR HAS TO BE .reveal__zoom .reveal__enlarge, not bare
   .reveal__enlarge. A single class has lower specificity than
   ".reveal__zoom button" above (one class vs. class+type), so on iPhone —
   where the coarse-pointer override further down sets min-width on
   .reveal__zoom button rather than a bare width — that more specific
   rule won the min-width property outright and this one's min-width had
   no effect at all, leaving the button at the generic 44px/38px box
   with "Enlarge" spilling past its edges. It happened to look right on
   desktop only because that breakpoint's competing rule uses `width`,
   not `min-width`, so min-width here was left uncontested there — good
   luck, not a real fix. Also setting `width` explicitly now, not just
   min-width, so this can't lose that way again regardless of which
   property a future rule change happens to use. */
.reveal__zoom .reveal__enlarge { width: auto; min-width: 84px; padding: 0 16px; }

.reveal__hint {
  position: absolute; left: 12px; bottom: 12px; z-index: 7;
  font-family: var(--ui); font-size: 0.66rem; font-weight: 600;
  letter-spacing: 0.08em; text-transform: uppercase;
  color: #fff; text-shadow: 0 1px 8px rgba(0,0,0,0.9);
  background: rgba(14, 15, 17, 0.45);
  padding: 7px 12px;
  pointer-events: none;
  transition: opacity 0.5s var(--ease);
}
/* One instruction, and it's gone the moment it's used. It used to swap to a
   second message about zooming and fade a few seconds later, but that
   message is now redundant with .reveal__zoomcue's centred cue — nothing
   left to swap to, so it just disappears, freeing the picture for someone
   who is only dragging the seam back and forth to see the whole canvas. */
.reveal.is-used .reveal__hint { opacity: 0; }
@media (max-width: 620px) { .reveal__hint { display: none; } }

/* The zoom cue, centred on the reveal (see .viewer__zoomcue for the shared
   base rule). Shown as soon as the reveal is on the page; dismissed for
   good the first time this painting is zoomed, same sticky rule as the
   popup — Reset must not bring it back. */
.reveal.is-shown .reveal__zoomcue { opacity: 1; }
.reveal.is-zoom-used .reveal__zoomcue { opacity: 0; }

.reveal__seam {
  position: absolute; top: 0; bottom: 0; left: 50%;
  width: 2px; margin-left: -1px; z-index: 5;
  background: rgba(255, 255, 255, 0.92);
  box-shadow: 0 0 12px rgba(0, 0, 0, 0.4);
  pointer-events: none;
}
/* Reported: the circle backdrop obstructs the view too much. Just the
   arrows now — no border, no fill, no blur — with a text-shadow standing
   in for the old dark disc so the glyph still reads over a light passage
   of the painting. The 46px box stays: it's the touch target, not the
   visible mark, and shrinking it would just make the handle harder to
   land a thumb on. No badge on hover/grip either now — just the arrows,
   a little larger, backed only by the text-shadow. No square, no circle. */
.reveal__grip {
  position: absolute; top: 50%; left: 50%;
  width: 46px; height: 46px; margin: -23px 0 0 -23px;
  display: grid; place-items: center;
  color: #fff; font-family: var(--ui); font-size: 1.05rem; letter-spacing: 0.1em;
  text-shadow: 0 1px 6px rgba(0, 0, 0, 0.85), 0 0 3px rgba(0, 0, 0, 0.6);
  /* the seam above is pointer-events:none so it never blocks a drag on the
     picture. The grip has to opt back in, or its pointerdown never fires and
     the drag falls through to the stage — which pans, once you are zoomed. */
  pointer-events: auto;
  cursor: ew-resize;
  touch-action: none;
  transition: transform 0.18s var(--ease), opacity 0.4s var(--ease);
}
.reveal__grip:hover, .reveal__seam.is-gripping .reveal__grip {
  transform: scale(1.15);
}
/* Reported: it sits over the middle of the picture and gets in the way of
   actually looking at it once someone already knows how the wipe works.
   Same rule as the hint text above — gone on first use, not lingering.
   Left clickable (no pointer-events change) so the drag-the-handle
   gesture still works from muscle memory; only the visible circle goes.

   BUT NOT ZOOMED. Once zoomed, dragging the picture anywhere else pans it
   — the grip is the only remaining way to wipe at all, not a convenience,
   so hiding it there took the wipe with it: the seam looked stuck because
   there was nothing left to grab it with. Visible again for the whole
   zoomed session regardless of is-used; the declutter is for browsing at
   fit, not for zoomed viewing, where it's load-bearing. */
.reveal.is-used:not(.is-zoomed) .reveal__grip { opacity: 0; }
.reveal__tag {
  position: absolute; top: 14px;
  font-family: var(--ui); font-size: 0.6rem; font-weight: 600;
  letter-spacing: 0.14em; text-transform: uppercase;
  color: #fff; text-shadow: 0 1px 8px rgba(0, 0, 0, 0.85);
  pointer-events: none;
}
.reveal__tag--old { left: 14px; }
.reveal__tag--new { right: 14px; }

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

/* .reveal__expand — the round corner entry point into the full-size popup —
   is gone. Roberto's request: it sat on top of the painting for no real
   reason once the same job could be done by a plain "Enlarge" control in
   the toolbar below, next to -/+/Reset, the same way the product page
   already does it. See .reveal__enlarge, which .reveal__zoom button styles
   generically and which has its own width override further down. */

/* A persistent invitation, on the picture itself. The wipe is the hook; the
   zoom is the payoff, and it was living in a button beside the image where
   nobody playing with a slider will ever look. Clicking anywhere but the grip
   opens the deep-zoom viewer. Scroll deliberately stays page-scroll — inline
   scroll-to-zoom traps anyone trying to reach the grid. */
.reveal__open {
  position: absolute; right: 14px; bottom: 14px; z-index: 6;
  display: inline-flex; align-items: center; gap: 7px;
  font-family: var(--ui); font-size: 0.68rem; font-weight: 600;
  letter-spacing: 0.11em; text-transform: uppercase;
  color: #fff;
  background: rgba(14, 15, 17, 0.52);
  backdrop-filter: blur(3px);
  border: 1px solid rgba(255,255,255,0.55);
  padding: 9px 15px;
  pointer-events: none;
  transition: background 0.2s var(--ease), border-color 0.2s var(--ease);
}
.reveal:hover .reveal__open { background: rgba(14, 15, 17, 0.72); border-color: #fff; }
.reveal__open i { font-style: normal; font-size: 0.9rem; line-height: 1; }
@media (max-width: 620px) { .reveal__open { left: 14px; right: 14px; justify-content: center; } }

/* ===========================================================================
   FILTER TABS                            -> snippets/collection-filters.liquid
   Replaces the old "three shop-by paths" homepage section.
   =========================================================================== */
/* Everything above the grid is bought at the cost of a painting nobody can
   see. No heading, no lede, two tabs, count inline. */
.filters { padding-block: 0; }
.filters__count { padding: 0; }
/* iPad/desktop only: the toolbar above this (Reset / Starry Night / Learn
   More) had zero gap below it, same as this section's own zero top padding
   — the two zeroes stacked and left the heading's last word ("goes.")
   sitting right against the controls row above it, close enough to read as
   a layout error. The vertical fold fight is won now, so there's room to
   give this seam a little air back. Not the phone: unchanged there. */
@media (min-width: 621px) {
  .filters { padding-top: 14px; }
}

.filters__intro { max-width: 560px; margin-bottom: 18px; }
.filters__intro h1, .filters__intro h2 { margin-bottom: 10px; }

@media (min-width: 1000px) {
  .filters__head {
    display: grid;
    grid-template-columns: 1fr auto;
    gap: 40px;
    align-items: end;
  }
  .filters__intro { margin-bottom: 0; }
}

.filters__bar {
  display: flex; align-items: baseline; gap: 6px;
  flex-wrap: wrap;
  border-bottom: 1px solid var(--rule);
  padding-bottom: 0;
}

.tab {
  font-family: var(--ui);
  font-size: 0.78rem;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-muted);
  padding: 12px 16px;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  transition: color 0.18s var(--ease), border-color 0.18s var(--ease);
}
.tab:hover { color: var(--ink); }
.tab[aria-selected="true"] { color: var(--ink); border-bottom-color: var(--accent); }

/* second-level chips, revealed when a tab has sub-options */
.chips {
  display: flex; gap: 8px; flex-wrap: wrap;
  padding: 18px 0 2px;
}
.chips[hidden] { display: none; }
.chip {
  font-family: var(--ui);
  font-size: 0.76rem;
  letter-spacing: 0.05em;
  color: var(--ink-soft);
  padding: 7px 15px;
  border: 1px solid var(--rule-strong);
  border-radius: 999px;
  transition: all 0.18s var(--ease);
}
.chip:hover { border-color: var(--ink); color: var(--ink); }
.chip[aria-pressed="true"] { background: var(--ink); border-color: var(--ink); color: var(--paper); }

.filters__count {
  font-family: var(--ui); font-size: 0.78rem; color: var(--ink-muted);
  padding: 16px 0 0;
}

/* ===========================================================================
   REVEAL GRID                            -> sections/reveal-grid.liquid
   Static split preview per tile. Deliberately NOT 50 live sliders.
   =========================================================================== */
.grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  /* Two flagships in a row leave a hole. The first takes columns one and
     two; the second needs two columns as well, cannot fit in the one that
     is left, and drops to the next row — with the top-right slot empty.
     `dense` lets the next single-width painting back-fill it. It can only
     ever pull forward something that fits, and the twelve finished
     paintings all sort ahead of the placeholders, so nothing unfinished
     can be promoted into a gap near the top. */
  grid-auto-flow: row dense;
  gap: 30px 24px;
  padding: 14px 0 60px;
}
@media (max-width: 1080px) { .grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 640px)  { .grid { grid-template-columns: 1fr; gap: 40px; } }

.tile { position: relative; text-align: left; width: 100%; display: block; }
.tile--pinned { grid-column: span 2; }
@media (max-width: 640px) { .tile--pinned { grid-column: span 1; } }

/* The picture-opens-the-Revealer button. Reset to plain block so it reads
   as part of the tile, not as a button — the `button` resets in the base
   styles do the chrome, this just fills the tile's width. */
.tile__open { display: block; width: 100%; padding: 0; text-align: left; }

.tile__frame {
  /* A <span> is display:inline, and aspect-ratio and overflow do not apply to
     an inline box. Without this the frame collapsed to a line box: no height,
     so the absolutely-positioned images inside had nothing to fill and the
     whole catalogue rendered as titles with no pictures. It looked like the
     images were failing to load. They were loading perfectly into a box of
     zero height. */
  display: block;
  position: relative;
  overflow: hidden;
  background: var(--paper-warm);
  aspect-ratio: 4 / 3;
}
/* Fallbacks only. Every painting whose real dimensions are known carries its
   own aspect-ratio inline, which wins over all of these — a frame should take
   the painting's shape, not the other way round. The 16:9 flagship in
   particular was cutting 31% off The Night Watch, which is nearly 5:4. */
.tile--tall .tile__frame { aspect-ratio: 3 / 4; }
.tile--pinned .tile__frame { aspect-ratio: 16 / 9; }
@media (max-width: 640px) { .tile--pinned .tile__frame { aspect-ratio: 4 / 3; } }

/* uneven heights are the honest result; sit each tile at the top of its row
   rather than stretching it to match its neighbours */
.grid { align-items: start; }

/* the static composite: unrestored left, restored right, hard seam between */
/* Images carry a real src and native loading="lazy" — never JS-dependent,
   so a tile can't ever render as a bare title. */
.tile__img {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  background: var(--paper-warm);
}
/* The seam and the clip read from one variable, so the thumbnail can be
   wiped by moving across it — which is what its handle has always promised.
   A grip that grows under the cursor and then does nothing is worse than no
   grip at all: someone watching a friend use this sat moving the mouse over
   a tile, waiting for the picture to follow, and gave up. */
.tile__frame { --wipe: 50%; }
.tile__img--after {
  clip-path: inset(0 0 0 var(--wipe));
  transition: clip-path 0.45s var(--ease);
}

.tile__seam {
  position: absolute; top: 0; bottom: 0; left: var(--wipe);
  transition: left 0.45s var(--ease);
  width: 1px;
  background: rgba(255, 255, 255, 0.85);
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.18);
}
/* Was a dark filled disc — reported as too intrusive sitting in the
   middle of every tile in the grid. Same fix already applied to
   .reveal__grip and .viewer__grip: drop the circle entirely and use just
   the wipe arrows, held legible over any passage of the painting by a
   text-shadow instead of a background. No border, no fill, no blur. */
.tile__seam::after {
  content: "↔";
  position: absolute; top: 50%; left: 50%;
  width: 38px; height: 38px;
  display: grid; place-items: center;
  transform: translate(-50%, -50%);
  color: #fff; font-family: var(--ui); font-size: 0.9rem; letter-spacing: 0.1em;
  text-shadow: 0 1px 6px rgba(0, 0, 0, 0.85), 0 0 3px rgba(0, 0, 0, 0.6);
  transition: transform 0.3s var(--ease);
}
/* While the pointer is driving it there must be no easing, or the seam
   swims behind the hand. The transition is for the release. */
.tile--tracking .tile__img--after,
.tile--tracking .tile__seam { transition: none; }

.tile:hover .tile__seam::after {
  transform: translate(-50%, -50%) scale(1.14);
}

/* the "open the real viewer" affordance */
.tile__cue {
  position: absolute; left: 50%; top: 50%;
  transform: translate(-50%, -50%);
  z-index: 2;
  pointer-events: none;
  font-family: var(--ui); font-size: 0.62rem; font-weight: 600;
  letter-spacing: 0.14em; text-transform: uppercase;
  color: #fff;
  opacity: 0;
  transition: opacity 0.3s var(--ease);
  text-shadow: 0 1px 6px rgba(0,0,0,0.6);
  margin-top: 34px;
}
/* Scoped to .is-unopened, not just to hover, at Roberto's request. On a
   mouse the pointer has to sit over the tile to drag its wipe seam at all
   — that's the whole gesture — so "hover-only" meant the cue kept coming
   back on every pass over a tile someone had already opened a dozen
   Revealers from, on desktop specifically (touch has no hover state to
   trigger this from, which is why iPad and iPhone never showed the
   problem). Once .is-unopened is gone — see seenRevealer() in
   unvarnished.js, set the first time any Revealer opens anywhere on the
   site, and permanent — the cue stops appearing at all, hover included,
   not just the unhovered "shout" state below. */
.grid.is-unopened .tile:hover .tile__cue { opacity: 1; }
/* Until the first Revealer has been opened, the first few tiles say so
   without being hovered. */
.grid.is-unopened .tile:nth-child(-n+3) .tile__cue { opacity: 0.92; }

.tile__corner {
  position: absolute; top: 12px; left: 12px; z-index: 2;
  display: flex; gap: 6px;
}
.tile__tag {
  font-family: var(--ui); font-size: 0.6rem; font-weight: 600;
  letter-spacing: 0.12em; text-transform: uppercase;
  padding: 5px 9px;
  background: rgba(251, 249, 245, 0.92);
  color: var(--ink);
}
.tile__tag--edition { background: rgba(14, 15, 17, 0.82); color: var(--accent-bright); }
.tile__tag--low     { background: var(--accent); color: #fff; }

/* Title and artist only. No price: let someone fall for a painting before
   they are asked to price it. Specs and money belong on the product page.
   For a finished painting this is a real link straight to that page — the
   second way in, for anyone who wants to skip the Revealer and go look at
   what's for sale. Reset to plain text colour so it doesn't look like a
   web link until you're on it; the underline on hover is the only tell. */
.tile__meta { padding-top: 10px; display: block; color: inherit; text-decoration: none; }
a.tile__meta:hover .tile__title { text-decoration: underline; }
.tile__title { font-family: var(--display); font-size: 1.06rem; line-height: 1.25; display: block; }
.tile--pinned .tile__title { font-size: 1.3rem; }
.tile__master { font-family: var(--ui); font-size: 0.74rem; color: var(--ink-muted); display: block; margin-top: 2px; }

/* The visible half of the "second way in" — unlike the title/artist text
   above it, this is meant to read as a link on sight, not only on hover,
   because the quiet version of this went unclicked. */
.tile__cta {
  font-family: var(--ui);
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.03em;
  color: var(--accent);
  display: block;
  margin-top: 6px;
}
a.tile__meta:hover .tile__cta { color: var(--accent-bright); text-decoration: underline; }

.grid__sentinel { height: 1px; }
.grid__more { text-align: center; padding: 10px 0 90px; }
.grid__spinner {
  font-family: var(--ui); font-size: 0.76rem; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--ink-muted);
}

/* ===========================================================================
   DEEP-ZOOM POPUP                        -> snippets/reveal-viewer.liquid
   Mockup implementation of the OpenSeadragon interaction model:
   hard-edge wipe, clipped to the CURRENT VIEWPORT, recomputed on pan/zoom.
   =========================================================================== */
/* Explicit grid rows pinned to the viewport height. Flex + min-height:auto
   let the control row slide below the fold on short windows — this is the
   bug that buried "View print options". Rows are auto / 1fr / auto, and the
   stage gets min-height:0 so it is the only thing that ever shrinks. */
.viewer {
  position: fixed; inset: 0; z-index: 200;
  background: var(--void);
  display: none;
  grid-template-rows: auto minmax(0, 1fr) auto;
  height: 100vh;
  height: 100dvh;
  max-height: 100dvh;
  overflow: hidden;
}
.viewer.is-open { display: grid; }
body.viewer-open { overflow: hidden; }

/* Order left to right is deliberate: the way out first, then the controls
   people use while browsing, and the purchase last — it is the least-used
   button while someone is cruising pictures. */
.viewer__bar {
  display: flex; align-items: center; gap: 14px;
  padding: 14px 20px;
  color: var(--on-void);
  border-bottom: 1px solid rgba(236, 237, 238, 0.12);
  flex-wrap: wrap;
}
.viewer__id { flex: 1; min-width: 180px; text-align: right; }
.viewer__tools { order: -1; }
@media (max-width: 860px) {
  .viewer__id { order: 3; flex: 1 0 100%; text-align: left; }
}
.viewer__title { font-family: var(--display); font-size: 1.16rem; }
.viewer__sub {
  font-family: var(--ui); font-size: 0.74rem; letter-spacing: 0.08em;
  color: var(--on-void-mute); margin-top: 2px;
}
.viewer__tools { display: flex; align-items: center; gap: 8px; }
.viewer__btn {
  font-family: var(--ui); font-size: 0.72rem; letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--on-void);
  border: 1px solid rgba(236, 237, 238, 0.24);
  padding: 9px 13px;
  transition: all 0.18s var(--ease);
}
.viewer__btn:hover { border-color: var(--accent-bright); color: var(--accent-bright); }
.viewer__btn--icon { width: 38px; padding: 9px 0; text-align: center; font-size: 0.95rem; }
.viewer__btn--back { border-color: rgba(236,237,238,0.5); }
.viewer__btn--back:hover { background: rgba(236,237,238,0.1); border-color: var(--on-void); color: var(--on-void); }

/* primary CTA, repeated in the top bar so it is visible the instant the
   popup opens, whatever the window height. Was solid near-white on the
   black bar — read as shouting next to the outline buttons beside it. A
   quieter light-grey fill keeps it the one filled button in the row (still
   the thing to notice) without the stark contrast. Text sized down rather
   than the button sized up, so "See the Full Story" fits the same
   footprint "Learn More" did. */
.viewer__btn--buy {
  background: #D6D7D9; color: var(--void); border-color: #D6D7D9;
  font-weight: 600;
  font-size: 0.64rem; letter-spacing: 0.04em;
}
.viewer__btn--buy:hover {
  background: var(--accent-bright); border-color: var(--accent-bright); color: #fff;
}
/* Used to hide below 600px width, which is every phone in portrait — so the
   one CTA that's supposed to be visible "whatever the window height" (see
   above) vanished on the orientation people actually hold their phone in,
   and reappeared only in landscape. .viewer__bar already wraps, so letting
   it wrap here costs a line, not the button. */

/* the stage */
.viewer__stage {
  position: relative; min-height: 0; overflow: hidden;
  cursor: grab;
  touch-action: none;
  background:
    radial-gradient(ellipse at center, #17151300 0%, #0C0B0A 78%),
    var(--void);
}
.viewer__stage.is-panning { cursor: grabbing; }
.viewer__stage.is-wiping { cursor: ew-resize; }

.viewer__pan {
  position: absolute; top: 0; left: 0;
  transform-origin: 0 0;
  will-change: transform;
}
.viewer__layer {
  position: absolute; top: 0; left: 0;
  max-width: none;
  user-select: none; -webkit-user-drag: none;
}
.viewer__layer--after { position: relative; }   /* establishes the box size */
.viewer__layer--before { z-index: 2; }          /* clipped on top, full opacity */

/* The seam, with a grip you can actually grab. Dragging the grip wipes;
   dragging anywhere else pans. That split is why the wipe went undiscovered:
   on the homepage drag meant wipe, in here it meant pan. */
.viewer__seam {
  position: absolute; top: 0; bottom: 0;
  width: 2px; margin-left: -1px; z-index: 5;
  background: rgba(255, 255, 255, 0.92);
  pointer-events: none;
  box-shadow: 0 0 14px rgba(0, 0, 0, 0.55);
}
/* Same as .reveal__grip — the slider itself is just the arrows, no badge,
   no shape. The round shape belongs to .shopfloat, the Shop button, not
   the grip. */
.viewer__grip {
  position: absolute; top: 50%; left: 50%;
  width: 54px; height: 54px; margin: -27px 0 0 -27px;
  display: grid; place-items: center;
  color: #fff; font-family: var(--ui); font-size: 1.3rem; letter-spacing: 0.06em;
  text-shadow: 0 1px 6px rgba(0, 0, 0, 0.85), 0 0 3px rgba(0, 0, 0, 0.6);
  pointer-events: auto;
  cursor: ew-resize;
  touch-action: none;
  transition: transform 0.18s var(--ease), opacity 0.4s var(--ease);
}
.viewer__grip:hover, .viewer__seam.is-gripping .viewer__grip {
  transform: scale(1.15);
}
/* same rule as .reveal__grip — gone on first use, still grabbable from
   where it was, but not while zoomed: the grip is the only way to wipe once
   zoomed, so hiding it there took the wipe with it. Reset per painting so a
   freshly opened one shows it again. */
.viewer.is-used:not(.is-zoomed) .viewer__grip { opacity: 0; }

/* labels sit in the top corners, out of the way of the seam itself */
.viewer__seamlabel {
  position: absolute; top: 16px; z-index: 6;
  font-family: var(--ui); font-size: 0.62rem; font-weight: 600;
  letter-spacing: 0.16em; text-transform: uppercase;
  color: rgba(255,255,255,0.78);
  text-shadow: 0 1px 8px rgba(0,0,0,0.9);
  pointer-events: none;
}
#seamOld { left: 18px; }
#seamNew { right: 18px; }

/* the buy CTA lives inside the stage, so it cannot be pushed below anything.
   Shaped to match .shopfloat — the same round accent pill used site-wide —
   instead of its own square-cornered look, so the one CTA that stays fixed
   here still reads as the same button as everywhere else. */
.viewer__buy {
  /* bottom is anchored to iOS's own safe-area boundary, not a guessed
     pixel offset — env(safe-area-inset-bottom) IS the height of the
     home-indicator gesture strip on a given phone (0 on non-notched
     devices, ~34px on notched ones), so this sits the button right at
     the edge of the painting while staying just outside the zone a
     swipe-up reads as "go to dock" on every device, automatically. */
  position: absolute; right: 18px;
  bottom: calc(env(safe-area-inset-bottom, 0px) + 10px);
  z-index: 8;
  font-family: var(--ui); font-size: 0.82rem; font-weight: 600;
  padding: 14px 24px;
  background: var(--accent); color: #fff;
  border-radius: 999px;
  box-shadow: 0 8px 20px rgba(14, 15, 17, 0.22);
  transition: background 0.15s var(--ease), transform 0.15s var(--ease);
}
.viewer__buy:hover { background: var(--accent-bright); transform: translateY(-2px); }
/* Used to stretch edge-to-edge on a narrow phone — reported as a "huge
   blue button" sitting over the middle of the painting. It's the same
   round pill as .shopfloat everywhere else in the site; there's no reason
   it should be the one place that pill turns into a bar. No mobile
   override any more: same small, discrete, bottom-right pill at every
   width, matching what "the floating Shop button" means everywhere else. */

/* .viewer__learnmore — the product-page path on a phone too narrow for the
   bar's own "See the Full Story" button (that one hides below 620px; see
   the comment above .viewer__btn--buy's mobile rule). Not anything added
   to the bar — the bar is exactly where trying to fit this before pushed
   the whole stage down the screen. Hidden above 620px on purpose: the
   bar's button already covers this there, and two paths to the same page
   is one too many.
   Opposite corner from Shop, not stacked above it — the two together in
   one corner sat over the painting while zooming; split left/right, each
   is out of the way of the other and out of the way of the middle of the
   picture, where zooming in actually needs the room. */
.viewer__learnmore { display: none; }
@media (max-width: 620px) {
  .viewer__learnmore {
    display: block;
    /* bottom is .viewer__buy's bottom (env(safe-area-inset-bottom) + 10px)
       plus the height difference from its taller vertical padding, so the
       two buttons' TOP edges line up instead of their bottoms — on an
       iPhone, Learn More sitting lower than Shop put it close enough to
       the bottom edge that a tap could be read as a swipe-up-for-dock
       gesture instead of a click.
       14 Aug: switched both buttons from guessed pixel offsets to
       env(safe-area-inset-bottom) — that value IS the height of the
       home-indicator gesture strip, so anchoring to it (rather than a
       fixed number chosen to clear it) lets both buttons sit right at
       the edge of the painting on every device without covering more of
       the artwork than the gesture zone actually requires. */
    position: absolute; left: 18px;
    bottom: calc(env(safe-area-inset-bottom, 0px) + 21px);
    z-index: 8;
    font-family: var(--ui); font-size: 0.72rem; font-weight: 600;
    padding: 9px 18px;
    background: rgba(14, 15, 17, 0.55);
    color: #fff;
    border: 1px solid rgba(255, 255, 255, 0.55);
    border-radius: 999px;
    backdrop-filter: blur(4px);
    transition: background 0.15s var(--ease);
  }
  .viewer__learnmore:hover { background: rgba(14, 15, 17, 0.75); }
}

/* On a narrow phone in portrait the bar has to fit "Back to the Collection",
   Reset, − and +, the painting's title, AND "Learn More" on one strip —
   there isn't room, so it wraps to two or three lines and pushes the stage
   (and the picture in it) down past the bottom of the screen. Shrinking the
   buttons buys a little room but can't guarantee a single line on every
   phone width, so the bar's own "Learn More" steps aside here; the floating
   "Shop" CTA already sits inside the stage for exactly this case — it costs
   no bar height because it's positioned over the picture, not stacked
   beside it. */
@media (max-width: 620px) {
  .viewer__bar { padding: 8px 12px; gap: 6px; }
  .viewer__btn { padding: 6px 9px; font-size: 0.66rem; }
  .viewer__btn--icon { width: 32px; padding: 6px 0; }
  .viewer__btn--buy { display: none; }
}

.viewer__hint {
  position: absolute; left: 50%; bottom: 22px;
  transform: translateX(-50%);
  z-index: 7;
  font-family: var(--ui); font-size: 0.68rem; letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--on-void-mute);
  background: rgba(14, 15, 17, 0.66);
  padding: 8px 16px;
  transition: opacity 0.4s var(--ease);
  pointer-events: none;
}
.viewer__hint.is-gone { opacity: 0; }

/* The zoom, said where the zoom is.
   The hint at the bottom teaches the wipe and then leaves — which is exactly
   the moment someone starts wondering what else the thing does. Watching
   people use it, they wipe, look for more, and give up: on a Magic Mouse
   there is no wheel to suggest scrolling, and the + button reads as chrome.
   So the cue sits beside the buttons and stays until they have zoomed once. */
/* Centred on the stage rather than pinned to a corner, so it sits over the
   painting itself even when the painting doesn't fill the window — a
   portrait canvas inside a landscape frame leaves a corner cue floating
   over empty letterbox, where it is never acknowledged. Shared base rule
   for the popup's cue and the inline reveal's cue (.reveal__zoomcue, added
   further down) so the wording and behaviour match everywhere. */
.viewer__zoomcue, .reveal__zoomcue {
  position: absolute; left: 50%; top: 50%; z-index: 8;
  transform: translate(-50%, -50%);
  font-family: var(--ui); font-size: 0.62rem; font-weight: 600;
  letter-spacing: 0.12em; text-transform: uppercase;
  color: #fff; text-align: center; white-space: nowrap;
  background: rgba(14, 15, 17, 0.62);
  padding: 8px 14px;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.4s var(--ease);
}
.viewer.is-open .viewer__zoomcue { opacity: 1; }
.viewer.is-open.is-zoomed .viewer__zoomcue { opacity: 0; }
/* Sticky dismissal: once the picture has been zoomed the first time, the
   cue must not reappear just because Reset returned it to fit — it has
   already done its job, and reappearing over the whole painting is the
   "interferes with the view" complaint this exists to fix. Deliberately a
   second, separate rule from the one above rather than a replacement for
   it, so both the live "currently zoomed" state and the permanent
   "already used once" state hide the cue. */
.viewer.is-zoom-used .viewer__zoomcue { opacity: 0; }

/* wipe control — the slider only. The CTA is inside the stage now. */
.viewer__control {
  display: flex; align-items: center; gap: 18px;
  padding: 14px 22px 18px;
  border-top: 1px solid rgba(236, 237, 238, 0.12);
}
.viewer__control .t-label { color: var(--on-void-mute); white-space: nowrap; }

.wipe {
  -webkit-appearance: none; appearance: none;
  flex: 1; height: 2px;
  background: rgba(236, 237, 238, 0.26);
  cursor: ew-resize;
}
.wipe::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 22px; height: 22px; border-radius: 50%;
  background: var(--on-void);
  border: 3px solid var(--void);
  box-shadow: 0 0 0 1px var(--on-void);
  cursor: ew-resize;
}
.wipe::-moz-range-thumb {
  width: 22px; height: 22px; border-radius: 50%;
  background: var(--on-void); border: 3px solid var(--void);
  box-shadow: 0 0 0 1px var(--on-void);
}

@media (max-width: 720px) {
  .viewer__control { gap: 12px; }
}

/* Hidden on touch devices (phone and tablet both — pointer:coarse catches
   iPad as well as iPhone), not removed. Nobody drags the track on a touch
   screen; the grip on the seam is what everyone actually uses, and this
   strip sat right at the bottom edge of the screen — exactly where iOS
   reads a drag as its own edge-swipe gesture (back to the home screen /
   switch app), so a touch here as often fought the OS as it moved the
   seam. Hiding it also hands its row back to the picture: .viewer's grid
   is auto/1fr/auto, so removing this row from layout lets the middle row
   take the freed height, meaning more of the screen for the zoomed image.
   Still in the DOM and still wired up: the popup's own ArrowLeft/ArrowRight
   handler drives this input's value programmatically (see unvarnished.js),
   so keyboard control on desktop is untouched by hiding it visually. */
@media (pointer: coarse) {
  .viewer__control { display: none; }
}

/* ===========================================================================
   FOOTER                                 -> sections/footer.liquid
   =========================================================================== */
.foot {
  background: var(--void);
  color: var(--on-void);
  padding: 72px 0 34px;
  margin-top: 40px;
}
/* Down to three columns now that the "Free Download Offer" column has
   moved into the page itself (see .restoredby__cta on each page) — the
   footer no longer needs to carry that pitch too. Re-weighted rather than
   just dropping the fourth track: the brand block gets a little more
   room now that it isn't being squeezed by a wide fourth column, and the
   two link lists sit closer together since they're reading as a pair. */
.foot__grid {
  display: grid;
  grid-template-columns: 1.3fr 0.85fr 0.85fr;
  gap: 60px;
  padding-bottom: 54px;
  border-bottom: 1px solid rgba(236, 237, 238, 0.12);
}
@media (max-width: 760px) { .foot__grid { grid-template-columns: 1fr 1fr; gap: 40px; } }
@media (max-width: 560px) { .foot__grid { grid-template-columns: 1fr; } }

.foot__mark { display: inline-block; margin-bottom: 12px; }
.foot__markimg { display: block; height: 34px; width: auto; }
.foot__blurb { color: var(--on-void-mute); font-size: 0.92rem; max-width: 30ch; }

.foot h4 { font-family: var(--ui); font-size: 0.68rem; font-weight: 600;
  letter-spacing: 0.16em; text-transform: uppercase; color: var(--on-void-mute);
  margin-bottom: 16px; }
.foot ul { list-style: none; margin: 0; padding: 0; }
.foot li { margin-bottom: 9px; }
.foot li a { font-size: 0.94rem; color: var(--on-void); border-bottom: 1px solid transparent; }
.foot li a:hover { border-bottom-color: var(--accent-bright); color: var(--accent-bright); }

.signup { display: flex; gap: 0; margin-top: 14px; max-width: 380px; }
.signup input {
  flex: 1; min-width: 0;
  background: transparent;
  border: 1px solid rgba(236, 237, 238, 0.26);
  border-right: 0;
  color: var(--on-void);
  padding: 13px 15px;
  font-family: var(--ui); font-size: 0.86rem;
}
.signup input::placeholder { color: var(--on-void-mute); }
.signup input:focus { outline: none; border-color: var(--accent-bright); }
.signup button {
  background: var(--on-void); color: var(--void);
  font-family: var(--ui); font-size: 0.72rem; font-weight: 600;
  letter-spacing: 0.1em; text-transform: uppercase;
  padding: 0 20px;
  border: 1px solid var(--on-void);
}
.signup button:hover { background: var(--accent-bright); border-color: var(--accent-bright); }

.foot__fine {
  display: flex; justify-content: space-between; gap: 20px; flex-wrap: wrap;
  padding-top: 24px;
  font-family: var(--ui); font-size: 0.74rem; color: var(--on-void-mute);
}
.foot__social { display: flex; gap: 16px; }
.foot__social a { color: var(--on-void-mute); }
.foot__social a:hover { color: var(--accent-bright); }
.foot__social svg { width: 19px; height: 19px; display: block; }

/* ===========================================================================
   PRODUCT PAGE                           -> sections/main-product.liquid
   =========================================================================== */
.crumbs {
  font-family: var(--ui); font-size: 0.74rem; letter-spacing: 0.06em;
  color: var(--ink-muted); padding: 26px 0 0;
}
.crumbs a:hover { color: var(--ink); }
.crumbs span { margin: 0 7px; opacity: 0.5; }

.pdp {
  display: grid;
  grid-template-columns: 1.32fr 1fr;
  gap: 58px;
  padding-block: 22px 24px;
  /* Was align-items: start — a flat 58vh cap on the picture meant a
     landscape painting (short at that width) hung from the top of the row
     with the buy column's extra height showing as dead space below it, and
     a portrait painting hit the height cap before it ever used the
     column's width, rendering small in a wide empty column either way.
     Stretch lets .pdp__media take the row's REAL height (set by whichever
     column is taller) instead of a fixed number, and .pdp__media centers
     the picture inside whatever that turns out to be — see below. */
  align-items: stretch;
}
/* Stacked on a narrow screen, the picture and the whole buy column (head,
   jumps, formats, trust) run one after another before the below-fold
   sections even start — on a small phone that pushed the next paragraph
   entirely out of view, with no hint anything followed. Tightening the
   padding here, not the content, buys back that room. */
@media (max-width: 980px) {
  .pdp { grid-template-columns: 1fr; gap: 30px; padding-block: 14px 16px; }
  .pdp__head { padding-bottom: 14px; }
  .pdp__jumps { padding: 6px 0; }
  .trust { margin-bottom: 12px; }
}

.pdp__media {
  position: relative;
  /* Column direction so the reveal and its zoom-controls row (a sibling,
     see product.html) stay stacked together as one block, and that block
     centers as a unit in whatever height the grid stretch above gives this
     column — not just the picture alone, or the controls would float away
     from it.

     align-items HAS TO BE stretch, not center. .reveal's own content
     (.reveal__pan and its image layers) is all position:absolute, so it
     contributes nothing to .reveal's intrinsic width — a flex item sized
     by content (which is what align-items:center asks for on the cross
     axis) resolves that to 0, and aspect-ratio then computes a 0 height
     from it: the exact "only the buttons show" collapse reported twice
     now. Stretch instead gives .reveal a real, definite width (the full
     column), the same starting point the homepage hero's .reveal already
     gets from its block-level parent — max-width below then narrows a
     portrait painting back down from there, same as it does on the
     homepage. */
  display: flex;
  flex-direction: column;
  align-items: stretch;
  justify-content: center;
}
.pdp__hero {
  position: relative; overflow: hidden; background: var(--paper-warm);
  aspect-ratio: 4 / 3;
  cursor: zoom-in;
}
.pdp__hero .tile__img { opacity: 1; }
.pdp__thumbs {
  display: grid; gap: 12px; margin-top: 12px;
  grid-template-columns: repeat(auto-fit, minmax(0, 1fr));
}
.pdp__thumb {
  position: relative; aspect-ratio: 1; overflow: hidden;
  background: var(--paper-warm);
  border: 1px solid transparent;
  transition: border-color 0.2s var(--ease);
}
.pdp__thumb img { width: 100%; height: 100%; object-fit: cover; }
.pdp__thumb:hover, .pdp__thumb[aria-pressed="true"] { border-color: var(--accent); }
.pdp__thumb figcaption {
  position: absolute; left: 0; right: 0; bottom: 0;
  font-family: var(--ui); font-size: 0.58rem; font-weight: 600;
  letter-spacing: 0.1em; text-transform: uppercase;
  color: #fff; background: linear-gradient(transparent, rgba(0,0,0,0.62));
  padding: 14px 8px 6px; text-align: center;
}

/* right column, stacked in brief order */
.pdp__head { border-bottom: 1px solid var(--rule); padding-bottom: 22px; }

/* The three headings that used to live only below the fold, brought up
   beside the picture so everything about it is visible without scrolling —
   clicking one jumps straight to the section it names. */
.pdp__jumps {
  display: flex; flex-direction: column;
  padding: 14px 0; border-bottom: 1px solid var(--rule);
}
.pdp__jump {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 10px; margin: 0 -10px;
  border-left: 2px solid transparent;
  transition: border-color 0.15s var(--ease), background 0.15s var(--ease);
}
.pdp__jump:hover, .pdp__jump:focus-visible {
  border-left-color: var(--accent); background: var(--paper-warm);
}
.pdp__jump-body { display: flex; flex-direction: column; gap: 2px; flex: 1; min-width: 0; }
.pdp__jump-label {
  font-family: var(--ui); font-size: 0.6rem; font-weight: 600;
  letter-spacing: 0.12em; text-transform: uppercase; color: var(--ink-muted);
}
.pdp__jump-text { font-family: var(--display); font-size: 0.92rem; color: var(--ink); }

/* Always visible, not a hover reveal — a touch screen never hovers, and
   without this the jump links looked like plain text, giving no sign that
   the full paragraph lives somewhere else. */
.pdp__jump-cue {
  display: flex; align-items: center; gap: 5px;
  flex: 0 0 auto;
  font-family: var(--ui); font-size: 0.62rem; font-weight: 600;
  letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--accent);
  border: 1px solid var(--rule-strong);
  border-radius: 999px;
  padding: 5px 10px;
  transition: border-color 0.15s var(--ease), background 0.15s var(--ease);
}
.pdp__jump:hover .pdp__jump-cue, .pdp__jump:focus-visible .pdp__jump-cue {
  border-color: var(--accent); background: var(--paper);
}
@media (max-width: 380px) { .pdp__jump-cue span { display: none; } }
.pdp__title { font-size: clamp(1.9rem, 3.2vw, 2.7rem); }
.pdp__attrib { font-family: var(--ui); font-size: 0.86rem; color: var(--ink-muted); margin-top: 10px; }
.pdp__attrib b { font-weight: 500; color: var(--ink-soft); }

.pdp__price { display: flex; align-items: baseline; gap: 12px; padding: 22px 0 6px; }
.pdp__price .amount { font-family: var(--display); font-size: 1.9rem; }
.pdp__price .was { font-family: var(--ui); font-size: 0.95rem; color: var(--ink-muted); text-decoration: line-through; }
.pdp__value { font-family: var(--ui); font-size: 0.82rem; color: var(--ink-muted); padding-bottom: 22px; }

.opt { padding: 0 0 22px; }
.opt__label { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 11px; }
.opt__label .sel { font-family: var(--ui); font-size: 0.8rem; color: var(--ink-soft); }
.pills { display: flex; gap: 8px; flex-wrap: wrap; }
.pill {
  font-family: var(--ui); font-size: 0.8rem;
  padding: 10px 16px;
  border: 1px solid var(--rule-strong);
  color: var(--ink-soft);
  transition: all 0.18s var(--ease);
}
.pill:hover { border-color: var(--ink); color: var(--ink); }
.pill[aria-pressed="true"] { background: var(--ink); border-color: var(--ink); color: var(--paper); }
.pill small { display: block; font-size: 0.68rem; opacity: 0.66; margin-top: 2px; }

.pdp__actions { display: grid; gap: 10px; padding-bottom: 22px; }

.trust {
  display: grid; grid-template-columns: repeat(3, 1fr);
  gap: 2px; background: var(--rule);
  border: 1px solid var(--rule);
  margin-bottom: 20px;
}
.trust div { background: var(--paper); padding: 14px 12px; text-align: center; }
.trust dt { font-family: var(--ui); font-size: 0.64rem; font-weight: 600;
  letter-spacing: 0.1em; text-transform: uppercase; color: var(--ink); }
.trust dd { margin: 4px 0 0; font-family: var(--ui); font-size: 0.7rem; color: var(--ink-muted); line-height: 1.35; }

.counter {
  display: flex; align-items: center; gap: 14px;
  padding: 15px 18px;
  background: var(--accent-wash);
  border-left: 2px solid var(--accent);
}
.counter__bar { flex: 1; height: 3px; background: rgba(138, 106, 53, 0.22); position: relative; }
.counter__bar i { position: absolute; inset: 0 auto 0 0; background: var(--accent); display: block; }
.counter__txt { font-family: var(--ui); font-size: 0.76rem; color: var(--accent); font-weight: 600;
  letter-spacing: 0.04em; white-space: nowrap; }

/* ---------------------------------------------------------------------------
   THREE FORMATS                          -> sections/format-offer.liquid
   Every painting is sold three ways. All three check out in the same basket:
   the print suppliers make and ship, they never take the customer.
   --------------------------------------------------------------------------- */
.formats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; }
@media (max-width: 940px) { .formats { grid-template-columns: 1fr; gap: 14px; } }

.fmt {
  display: flex; flex-direction: column;
  border: 1px solid var(--rule-strong);
  background: var(--surface);
  padding: 26px 24px 24px;
}
.fmt--current { border-color: var(--ink); background: var(--paper-warm); }

.fmt__top { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; }
.fmt__name { font-family: var(--display); font-size: 1.16rem; line-height: 1.2; }
.fmt__from { font-family: var(--ui); font-size: 0.76rem; color: var(--ink-muted); white-space: nowrap; }
.fmt__from b { font-size: 0.98rem; color: var(--ink); font-weight: 400; font-family: var(--display); }

.fmt__here {
  font-family: var(--ui); font-size: 0.58rem; font-weight: 600;
  letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--accent); margin-top: 8px; display: block;
}

.fmt__blurb { font-size: 0.94rem; color: var(--ink-soft); margin: 12px 0 0; }

.fmt__points { list-style: none; margin: 16px 0 0; padding: 0; }
.fmt__points li {
  font-family: var(--ui); font-size: 0.78rem; color: var(--ink-muted);
  padding-left: 16px; position: relative; margin-bottom: 6px;
}
.fmt__points li::before {
  content: "·"; position: absolute; left: 4px; color: var(--accent);
  font-size: 1.2rem; line-height: 0.9;
}

.fmt__foot { margin-top: auto; padding-top: 20px; }
.fmt__made {
  font-family: var(--ui); font-size: 0.68rem; color: var(--ink-muted);
  margin: 10px 0 0; display: block;
}

/* The lean trio beside the reveal — maker and lead time move to the
   size-selection step each button opens, so the glance beside the picture
   stays a glance. The tax/duty line stays: it's a promise, not logistics. */
.formats--trio { gap: 10px; margin-top: 2px; }
.formats--trio .fmt { padding: 14px 15px 12px; }
.formats--trio .fmt__name { font-size: 0.9rem; }
.formats--trio .fmt__from { font-size: 0.64rem; }
.formats--trio .fmt__from b { font-size: 0.8rem; }
.formats--trio .fmt__blurb { font-size: 0.72rem; line-height: 1.4; margin-top: 6px; }
.formats--trio .fmt__foot { padding-top: 8px; }
/* Much smaller than the standard .btn at Roberto's request — this trio
   sits above the fold beside the reveal, and the full-size button was
   costing more height than the three cards needed. */
.formats--trio .fmt__foot .btn {
  width: 100%; text-align: center; padding: 5px 8px; font-size: 0.6rem;
}
.formats--trio .fmt__made { font-size: 0.58rem; line-height: 1.3; margin-top: 8px; }
@media (max-width: 560px) { .formats--trio { grid-template-columns: 1fr; } }

/* below the fold */
.band { padding: 60px 0; border-top: 1px solid var(--rule); }
.band--tight { padding: 44px 0; }
.band__head { margin-bottom: 26px; }
.band__head .t-label { display: block; margin-bottom: 10px; }

/* The full 60px .band padding was written for the gap BETWEEN sections
   further down the page, where it earns its keep. Sitting directly under
   the reveal, it was a full inch of dead space before Description even
   started — on desktop and an iPad that title was never read, because it
   sat below the fold behind whitespace nobody needed. Only the top is
   tightened; the bottom stays, so Description and the next section
   (Story) still breathe. */
#pdpDescription { padding-top: 24px; }

/* "Description" and "The story of this painting" — both the heading and
   the paragraphs under it — enlarged at Roberto's request. Scoped to
   these two sections specifically rather than touching .t-h2 itself,
   which is the shared heading style used across the rest of the site.
   The extra padding-top on #pdpDescription above (18px -> 24px) and the
   added padding-top on #pdpStory are the "boxes drop a bit" half of the
   request — a bigger heading sitting right at the same old top edge read
   as cramped against the jump nav above it. */
#pdpDescription .t-h2,
#pdpStory .t-h2 {
  font-size: clamp(1.65rem, 3vw, 2.3rem);
}
#pdpStory { padding-top: 8px; }
#pDesc p,
#pStory p {
  font-size: 1.1em;
  line-height: 1.68;
}

.honest {
  margin-top: 22px;
  padding: 18px 22px;
  background: var(--paper-warm);
  border-left: 2px solid var(--rule-strong);
  font-size: 0.95rem;
  color: var(--ink-soft);
}

/* "Restored by software" — bullet/numbered lists inside prose, and the
   free-download callout at the foot of it. */
.restoredby__list {
  margin: 12px 0 0;
  padding-left: 1.2em;
  display: grid;
  gap: 10px;
}
.restoredby__list--sources { font-size: 0.92rem; color: var(--ink-soft); }

/* A veil point with a detail crop attached (see the {text,img,alt} shape
   in unvarnished.js) — text and image sit as a two-column row so the
   image reads as illustrating that specific point, not just floating
   loose in the section. Below ~780px there usually isn't room for both
   a readable line length AND a legible image side by side, so it drops
   to one column with the image on top, same as a captioned figure.

   The grid lives on .restoredby__point-row, a wrapper *inside* the <li>,
   not on the <li> itself: a list item only gets its native bullet marker
   while its own computed display stays list-item, so display:grid on the
   <li> directly silently drops the dot. First shipped that way and it
   showed — the two points with images lost their bullet while the
   plain-text point kept its own, which read as a mistake, not a design. */
.restoredby__point-row {
  display: grid;
  grid-template-columns: 1fr minmax(120px, 34%);
  gap: 16px;
  align-items: center;
}
.restoredby__point-text { display: block; }
.restoredby__detail-img {
  width: 100%;
  height: auto;
  display: block;
  border-radius: 3px;
  border: 1px solid var(--rule);
}
@media (max-width: 780px) {
  .restoredby__point-row {
    grid-template-columns: 1fr;
    gap: 10px;
  }
  .restoredby__detail-img {
    order: -1;
    max-width: 280px;
    margin: 0 auto;
  }
}
.restoredby__cta {
  margin-top: 28px;
  padding: 20px 22px;
  background: var(--paper-warm);
  border-left: 2px solid var(--rule-strong);
}
.restoredby__cta .t-h3 { margin: 0 0 8px; }
.restoredby__cta p { margin: 0 0 14px; color: var(--ink-soft); }

/* Process Poster cross-sell — TEST placement, gated to one slug in JS.
   The source image is a wide strip (the trio, cropped tight with almost no
   margin — see tools note in unvarnished.js), not a portrait poster, so
   this is a stacked layout: full-width banner image on top, copy below —
   not the side-by-side card a taller mockup would want. Flat, not tilted:
   a rotated wide strip reads oddly, where a rotated portrait reads as an
   object. The shadow alone is enough to lift it off the page as a product
   photo rather than a flat repeat of the #pTrio images above it. */
.postercta {
  padding: 36px 0;
  text-align: center;
}
.postercta__art {
  margin-bottom: 24px;
}
.postercta__art img {
  display: block;
  width: 100%;
  max-width: 640px;
  height: auto;
  margin: 0 auto;
  border-radius: 2px;
  box-shadow: 0 20px 40px rgba(23,24,26,0.18), 0 3px 8px rgba(23,24,26,0.1);
}
.postercta__copy { max-width: 520px; margin: 0 auto; }
.postercta__copy .btn[disabled] {
  opacity: 0.5;
  cursor: not-allowed;
}

/* full-width repeat of the reveal */
.wide-reveal {
  position: relative; overflow: hidden;
  aspect-ratio: 21 / 9; background: var(--void);
  cursor: zoom-in;
}
@media (max-width: 760px) { .wide-reveal { aspect-ratio: 4 / 3; } }
.wide-reveal img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.wide-reveal .after { clip-path: inset(0 0 0 46%); }
.wide-reveal .seam { position: absolute; top: 0; bottom: 0; left: 46%; width: 1px;
  background: rgba(255,255,255,0.9); }

/* accordion */
.acc { border-top: 1px solid var(--rule); }
.acc__item { border-bottom: 1px solid var(--rule); }
.acc__btn {
  width: 100%; display: flex; justify-content: space-between; align-items: center;
  gap: 20px; padding: 20px 2px; text-align: left;
  font-family: var(--ui); font-size: 0.88rem; font-weight: 500; letter-spacing: 0.02em;
}
.acc__btn:hover { color: var(--accent); }
.acc__btn i { font-style: normal; font-size: 1.1rem; color: var(--ink-muted); transition: transform 0.24s var(--ease); }
.acc__btn[aria-expanded="true"] i { transform: rotate(45deg); }
.acc__panel { padding: 0 2px 24px; max-width: var(--max-prose); }
.acc__panel[hidden] { display: none; }
.acc__panel dl { display: grid; grid-template-columns: 168px 1fr; gap: 10px 20px; margin: 0; font-size: 0.94rem; }
@media (max-width: 620px) { .acc__panel dl { grid-template-columns: 1fr; gap: 2px 0; }
  .acc__panel dd { margin-bottom: 14px; } }
.acc__panel dt { font-family: var(--ui); font-size: 0.78rem; letter-spacing: 0.05em;
  text-transform: uppercase; color: var(--ink-muted); padding-top: 3px; }
.acc__panel dd { margin: 0; color: var(--ink-soft); }

/* certificate */
.cert {
  display: grid; grid-template-columns: auto 1fr auto;
  gap: 28px; align-items: center;
  border: 1px solid var(--rule-strong);
  padding: 30px 32px;
  background: var(--surface);
}
@media (max-width: 760px) { .cert { grid-template-columns: 1fr; gap: 18px; text-align: left; } }
.cert__seal {
  width: 76px; height: 76px; border-radius: 50%;
  border: 1px solid var(--accent);
  display: grid; place-items: center;
  font-family: var(--display); font-size: 1.5rem; color: var(--accent);
}
.cert p { margin: 8px 0 0; color: var(--ink-soft); font-size: 0.95rem; max-width: 56ch; }

/* artist + interpretation
   Used to be text beside a single Homage image, side by side. Roberto's
   read: mostly words, not enough picture to sell the print on. Now the
   prose sits full width, and the before/homage/after trio — the same
   panel markup process.html uses to explain the method — runs full width
   underneath, doing the selling. */
.artist { display: block; }
/* subtitle, then the trio (full width, not held to the text column), then
   the paragraph making the case underneath it — two .prose blocks either
   side of the trio rather than one, so the trio isn't squeezed to 640px. */
.artist .prose { max-width: 640px; margin: 0 auto 44px; }
.artist .prose.artist__lede { margin-bottom: 30px; }
.artist .twice__row { margin-bottom: 40px; }
.artist figure { margin: 0; }

/* cross-sell */
.rail { display: grid; grid-template-columns: repeat(4, 1fr); gap: 22px; }
@media (max-width: 900px) { .rail { grid-template-columns: repeat(2, 1fr); } }
/* Real proportions, not a square crop — same fix as the By Artist
   thumbnails. The frame keeps every card the same height across the row;
   object-fit: contain letterboxes whatever doesn't fill it rather than
   cutting the painting off. */
.rail__frame {
  display: block; aspect-ratio: 4 / 5;
  background: var(--paper);
  overflow: hidden;
}
.rail__frame img { width: 100%; height: 100%; object-fit: contain; }
.rail a .tile__title { font-size: 0.98rem; margin-top: 11px; }

/* ===========================================================================
   COLLECTION PAGE                        -> sections/main-collection.liquid
   =========================================================================== */
.collhead {
  background: var(--paper-warm);
  padding: 36px 0;
  border-bottom: 1px solid var(--rule);
}
.collhead .t-label { display: block; margin-bottom: 14px; }
.collhead p { max-width: 74ch; margin: 16px 0 0; }
.collhead p:empty { display: none; }

.sortbar {
  display: flex; justify-content: space-between; align-items: center;
  gap: 20px; flex-wrap: wrap;
  padding: 22px 0;
  border-bottom: 1px solid var(--rule);
}
.sortbar__group { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.sortbar select {
  font-family: var(--ui); font-size: 0.8rem;
  padding: 9px 13px; border: 1px solid var(--rule-strong);
  background: var(--paper); color: var(--ink-soft);
}

/* ===========================================================================
   BY ARTIST DIRECTORY                    -> sections/main-collection.liquid
   One name per row, ranked, leading to that artist's own collection.
   =========================================================================== */
.artistlist { list-style: none; padding: 6px 0 90px; margin: 0; }
.artistrow { border-bottom: 1px solid var(--rule); }
/* A fixed tab-stop, not a right-justified flex group — the restoration
   count is gone (Roberto's request: it wasn't adding anything), and the
   thumbnails no longer float wherever a right-aligned group happens to
   land depending on name length. Every row's thumbnails start at the same
   point — halfway across the row — so scanning down the page reads as a
   deliberate column of pictures, on a phone exactly as on a desktop. */
.artistrow a {
  display: grid; grid-template-columns: 2.5ch 50% 1fr;
  align-items: center; gap: 18px;
  padding: 20px 4px;
  transition: background 0.15s var(--ease);
}
.artistrow a:hover { background: var(--paper-warm); }
.artistrow__rank {
  font-family: var(--ui); font-size: 0.76rem; color: var(--ink-muted);
}
.artistrow__name {
  font-family: var(--display); font-size: clamp(1.1rem, 2vw, 1.5rem);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  min-width: 0;
}
.artistrow__thumbs {
  display: flex; justify-content: flex-start; gap: 10px;
  min-width: 0;
}
.artistrow__thumbs img {
  height: 64px; width: auto; max-width: 80px;
  object-fit: contain;
  flex-shrink: 0;
  border: 1px solid var(--rule);
  background: var(--paper);
}
@media (max-width: 700px) {
  .artistrow__thumbs img { height: 46px; max-width: 58px; }
}
@media (max-width: 500px) {
  .artistrow a { gap: 10px; padding: 14px 2px; }
  .artistrow__name { font-size: 0.98rem; }
  .artistrow__thumbs img { height: 40px; max-width: 48px; }
  /* two thumbnails, not three — the tab-stop holds regardless, only the
     column gets a little narrower */
  .artistrow__thumbs img:nth-child(3) { display: none; }
}

/* ===========================================================================
   SHOP PAGE (shop.html)                  -> sections/main-shop.liquid
   The fast lane: no hero, no slider, no story — a plain grid of finished
   paintings that leads straight to a product page in one click. Deliberately
   plainer than every other page on the site; that's the point.
   =========================================================================== */
.shophead {
  padding: 34px 0 22px;
  border-bottom: 1px solid var(--rule);
}
.shophead .t-label { display: block; margin-bottom: 10px; }
.shophead .t-lede { margin: 0; max-width: 52ch; }

.shopgrid {
  display: grid; grid-template-columns: repeat(5, 1fr); gap: 20px;
  padding: 34px 0 90px;
}
@media (max-width: 980px) { .shopgrid { grid-template-columns: repeat(4, 1fr); } }
@media (max-width: 760px) { .shopgrid { grid-template-columns: repeat(3, 1fr); gap: 14px; } }
@media (max-width: 500px) { .shopgrid { grid-template-columns: repeat(2, 1fr); gap: 10px; } }

/* No fixed aspect-ratio box here on purpose. A locked box + object-fit:
   contain letterboxes a landscape painting inside a tall frame — the
   picture ends up small and centered, with empty space above and below it,
   and the title (which sits right after the frame) reads as floating way
   below the picture instead of belonging to it. Letting each image keep
   its own natural height fixes that: the title always sits right at the
   painting's own bottom edge. The trade-off is that rows aren't perfectly
   flush across columns any more — worth it for the title never looking
   like it belongs to the wrong painting. */
.shoptile { display: block; }
.shoptile__frame {
  display: block;
  background: var(--paper);
  overflow: hidden;
}
.shoptile__frame img { display: block; width: 100%; height: auto; }
.shoptile:hover .shoptile__frame img { transform: scale(1.02); }
.shoptile__frame img { transition: transform 0.2s var(--ease); }
.shoptile__title {
  display: block; margin-top: 10px;
  font-family: var(--ui); font-size: 0.8rem; color: var(--ink-soft);
}

/* ===========================================================================
   FLOATING SHOP BUTTON — on every page except shop.html itself. One click
   away from a buyable painting, no matter where someone is on the site.
   =========================================================================== */
.shopfloat {
  position: fixed; right: 18px; bottom: 70px; z-index: 150;
  font-family: var(--ui); font-size: 0.82rem; font-weight: 600;
  padding: 14px 24px;
  background: var(--accent); color: #fff;
  border-radius: 999px;
  box-shadow: 0 8px 20px rgba(14, 15, 17, 0.22);
  transition: background 0.15s var(--ease), transform 0.15s var(--ease);
}
.shopfloat:hover { background: var(--accent-bright); transform: translateY(-2px); }
@media (max-width: 620px) {
  /* the dev mockbar hides itself below this width, so there's nothing left
     to clear — the button can sit in the natural thumb-reach corner */
  .shopfloat { right: 14px; bottom: 14px; padding: 12px 20px; font-size: 0.78rem; }
}

.pager { display: flex; justify-content: center; gap: 8px; padding: 20px 0 90px; }
.pager a, .pager span {
  font-family: var(--ui); font-size: 0.82rem;
  min-width: 40px; height: 40px; display: grid; place-items: center;
  border: 1px solid var(--rule-strong); color: var(--ink-soft);
}
.pager a:hover { border-color: var(--ink); color: var(--ink); }
.pager [aria-current] { background: var(--ink); border-color: var(--ink); color: var(--paper); }

/* ===========================================================================
   ABOUT PAGE                             -> sections/main-page-about.liquid
   =========================================================================== */
.manifesto {
  padding: clamp(70px, 11vw, 130px) 0;
  border-bottom: 1px solid var(--rule);
}
.manifesto p {
  font-family: var(--display);
  font-size: clamp(1.6rem, 3.5vw, 2.65rem);
  line-height: 1.28;
  letter-spacing: -0.015em;
  max-width: 20ch;
  margin: 0;
}
.manifesto p + p { margin-top: 0.7em; max-width: 26ch; color: var(--ink-soft); }
.manifesto em { color: var(--accent); font-style: italic; }

/* --- the compact variant, for pages where the manifesto is a standfirst
   and the real content has to be visible under it ------------------------- */
.manifesto--tight { padding: clamp(34px, 4.5vw, 56px) 0 clamp(26px, 3.4vw, 40px); }
.manifesto--tight p {
  font-size: clamp(1.35rem, 2.5vw, 1.95rem);
  line-height: 1.24;
  max-width: 26ch;
}
.manifesto--tight p + p { max-width: 34ch; margin-top: 0.4em; }
@media (min-width: 900px) {
  /* the two lines sit side by side: the claim, then the answer to it */
  .manifesto--tight .wrap {
    display: grid; grid-template-columns: 1fr 1fr; gap: 44px; align-items: baseline;
  }
  .manifesto--tight p, .manifesto--tight p + p { max-width: 28ch; margin-top: 0; }
}
/* and the method follows immediately, not after a band of air */
.manifesto--tight + .band { padding-top: clamp(28px, 3.6vw, 46px); }


/* Explicit placement, not grid auto-flow: both photos in column 1 (portrait
   on row 1, the studio shot on row 2, directly below it), the bio text in
   column 2 spanning both rows so it runs the full height beside them. */
.bio {
  display: grid;
  grid-template-columns: 0.72fr 1fr;
  grid-template-rows: auto auto;
  gap: 30px 58px;
  align-items: start;
}
.bio__photo { position: relative; grid-column: 1; grid-row: 1; }
.bio__photo--studio { grid-row: 2; }
.bio__body { grid-column: 2; grid-row: 1 / 3; }
.bio__photo img { width: 100%; aspect-ratio: 4/5; object-fit: cover; }
.bio__photo--studio img { aspect-ratio: 1; }
.bio__photo figcaption { font-family: var(--ui); font-size: 0.74rem; color: var(--ink-muted); margin-top: 10px; }
.bio__body p { max-width: 62ch; }
.bio__body p:first-of-type { margin-top: 0; }
/* Below 880px: a single flex column, reordered so the studio photo — the
   one meant to break up the long bio text — actually lands after it,
   right before "Palette Homages", instead of stacking back-to-back with
   the portrait ahead of any text at all. */
@media (max-width: 880px) {
  .bio { display: flex; flex-direction: column; gap: 30px; }
  .bio__photo { order: 1; }
  .bio__body { order: 2; }
  .bio__photo--studio { order: 3; }
}

/* Kinetic murals: video left, institutional context right — same shape as
   .bio above, mirrored, so the two media-plus-text sections on this page
   read as a pair rather than two unrelated layouts. */
.muralcta { display: grid; grid-template-columns: 1fr 0.86fr; gap: 48px; align-items: center; }
@media (max-width: 880px) { .muralcta { grid-template-columns: 1fr; gap: 26px; } }
.muralcta__media video {
  width: 100%; aspect-ratio: 16/9; object-fit: cover;
  background: var(--paper-warm); display: block;
}
.muralcta__body p { max-width: 52ch; }

/* toggle used to decide full vs short bio in situ — Open Item §9 */
.bioswitch {
  display: inline-flex; gap: 0; margin-bottom: 24px;
  border: 1px solid var(--rule-strong);
}
.bioswitch button {
  font-family: var(--ui); font-size: 0.72rem; letter-spacing: 0.08em;
  text-transform: uppercase; padding: 9px 16px; color: var(--ink-muted);
}
.bioswitch button[aria-pressed="true"] { background: var(--ink); color: var(--paper); }

.steps {
  counter-reset: s;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(178px, 1fr));
  gap: 30px 26px;
}
@media (max-width: 560px) { .steps { grid-template-columns: 1fr; } }
.step--pivot { border-top-color: var(--accent); }
.step--pivot h3 { color: var(--accent); }
.step { border-top: 1px solid var(--ink); padding-top: 18px; }
.step::before {
  counter-increment: s; content: counter(s, decimal-leading-zero);
  font-family: var(--ui); font-size: 0.7rem; font-weight: 600; letter-spacing: 0.12em;
  color: var(--accent); display: block; margin-bottom: 12px;
}
.step h3 { font-size: 1.08rem; margin-bottom: 8px; }
.step p { font-size: 0.94rem; color: var(--ink-soft); margin: 0; }

.pullquote {
  border-left: 2px solid var(--accent);
  padding: 6px 0 6px 28px;
  margin: 34px 0;
  font-style: italic;
  font-size: 1.08rem;
  color: var(--ink-soft);
  max-width: 62ch;
}
.pullquote footer { font-style: normal; font-family: var(--ui); font-size: 0.76rem;
  letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink-muted); margin-top: 14px; }

/* --- "The second restoration" ------------------------------------------
   Three panels: first restoration | the homage, painted by hand | second
   restoration. The middle panel is the argument, so it is visually the
   loudest of the three.   -> sections/second-restoration.liquid
   ---------------------------------------------------------------------- */
.twice { padding-top: 8px; }

.twice__line {
  font-family: var(--display);
  font-size: clamp(1.5rem, 3vw, 2.3rem);
  line-height: 1.2;
  letter-spacing: -0.015em;
  margin: 0 0 40px;
  /* This headline carries its own manual <br> breaks, chosen for the sense
     of the phrase — a max-width here was re-wrapping mid-line on top of
     them (36ch lines forced into a 24ch box), landing the break in the
     wrong place. No cap: the four lines as written are the layout. */
}
.twice__line em { color: var(--accent); font-style: italic; }

.twice__row {
  display: grid;
  grid-template-columns: 1fr auto 1.22fr auto 1fr;
  gap: 0 18px;
  align-items: center;
}
@media (max-width: 900px) {
  .twice__row { grid-template-columns: 1fr; gap: 30px 0; }
}

.panel { margin: 0; }
.panel img {
  width: 100%;
  aspect-ratio: 4 / 3;
  /* Was cover, which crops whatever doesn't fit the fixed 4:3 box — fine
     for a painting that happens to be close to 4:3, but it was cutting
     into the first/after restoration photos on anything more portrait or
     more landscape than that. contain keeps the whole painting visible,
     letterboxed on the warm paper background instead of trimmed. The box
     shape (4:3) stays, for the three panels to line up the same size. */
  object-fit: contain;
  background: var(--paper);
}
.panel figcaption { margin-top: 12px; }
.panel__n {
  font-family: var(--ui); font-size: 0.66rem; font-weight: 600;
  letter-spacing: 0.14em; color: var(--accent);
  display: block; margin-bottom: 6px;
}
.panel__t { font-family: var(--display); font-size: 1.08rem; display: block; }
.panel__d {
  font-family: var(--ui); font-size: 0.78rem; color: var(--ink-muted);
  line-height: 1.45; margin-top: 5px; display: block;
}

/* the middle panel — the thing nobody else does */
.panel--hero img {
  aspect-ratio: 1 / 1;
  outline: 1px solid var(--accent);
  outline-offset: 7px;
}
.panel--hero { padding: 9px 0; }
.panel--hero .panel__t { font-size: 1.28rem; }

.twice__arrow {
  font-family: var(--ui); font-size: 1.1rem; color: var(--rule-strong);
  align-self: center; padding-bottom: 46px;
}
@media (max-width: 900px) {
  .twice__arrow { display: none; }
  /* Was forced to 4/3 here, same as the two outer panels — but the Homage
     is a square painting (the source files are 1000x1000), and object-fit:
     cover into a landscape box was cropping a quarter of it off, top and
     bottom, on every phone. Left at 1/1, its own shape, same as desktop. */
}

.twice__foot {
  font-family: var(--display);
  font-style: italic;
  font-size: 1.05rem;
  color: var(--ink-muted);
  text-align: center;
  margin: 30px 0 0;
  padding-top: 20px;
  border-top: 1px dashed var(--rule-strong);
}

/* The studio photo ahead of the sourcing paragraph — evidence for the claim
   that follows it, not decoration. Kept narrow rather than full-width so it
   reads as a supporting image beside the text, not a competing hero. */
.why__photo { max-width: 280px; margin: 0 0 26px; }
.why__photo img { width: 100%; aspect-ratio: 1; object-fit: cover; display: block; }
.why__photo figcaption {
  font-family: var(--ui); font-size: 0.74rem; color: var(--ink-muted); margin-top: 8px;
}

/* Roberto's own voice — first person, set apart from the site's third */
.voice {
  margin: 40px 0 0;
  padding: 34px 38px;
  background: var(--accent-wash);
  border-left: 3px solid var(--accent);
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 26px;
}
@media (max-width: 760px) {
  .voice { grid-template-columns: 1fr; gap: 10px; padding: 26px 24px; }
}
.voice__mark {
  font-family: var(--display); font-size: 4rem; line-height: 0.7;
  color: var(--accent); opacity: 0.4;
}
.voice p {
  font-family: var(--display);
  font-size: clamp(1.06rem, 1.7vw, 1.3rem);
  line-height: 1.55;
  color: var(--ink);
  max-width: 58ch;
  margin: 0 0 1em;
}
.voice footer {
  font-family: var(--ui); font-size: 0.74rem; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--ink-muted);
}

.homagerail { display: grid; grid-template-columns: repeat(4, 1fr); gap: 18px; margin-top: 30px; }
@media (max-width: 720px) { .homagerail { grid-template-columns: repeat(2, 1fr); } }
.homagerail figure { margin: 0; }
.homagerail img { aspect-ratio: 1; object-fit: cover; width: 100%; display: block; }
.homagerail figcaption {
  margin-top: 10px; font-family: var(--body); font-size: 0.88rem;
  line-height: 1.35; color: var(--ink-muted); text-align: left;
}
.homagerail figcaption i { font-style: italic; color: var(--ink); }

.closing {
  background: var(--void); color: var(--on-void);
  text-align: center; padding: clamp(64px, 9vw, 110px) 0;
}
.closing h2 { max-width: 18ch; margin: 0 auto 26px; }
.closing .btn { background: var(--on-void); border-color: var(--on-void); color: var(--void); }
.closing .btn:hover { background: var(--accent-bright); border-color: var(--accent-bright); }

/* ===========================================================================
   MOCKUP-ONLY CHROME — delete before porting to Liquid
   =========================================================================== */
.mockbar {
  position: fixed; right: 14px; bottom: 14px; z-index: 300;
  font-family: var(--ui); font-size: 0.68rem; letter-spacing: 0.06em;
  background: rgba(14, 15, 17, 0.9); color: #ECEDEE;
  padding: 9px 14px; border-radius: 2px;
  display: flex; gap: 12px; align-items: center;
}
.mockbar a { color: var(--accent-bright); }
.mockbar b { font-weight: 600; text-transform: uppercase; letter-spacing: 0.12em; opacity: 0.55; }
@media (max-width: 620px) { .mockbar { display: none; } }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation: none !important; transition: none !important; }
  html { scroll-behavior: auto; }
}

/* ---------------------------------------------------------------------------
   PRODUCT HERO — live reveal left, the three formats stacked right.
   The reveal here is the same component as the homepage; only the box differs,
   because on a product page there is no grid underneath fighting for the fold.

   Two things tried before this and reverted:

   1. Percentage max-height against the row's own stretched height. Real
      CSS technique, but stacked on the flex column in .pdp__media it
      produced a circular sizing dependency some browsers couldn't resolve
      — the reveal rendered at zero size, only the zoom buttons showing.

   2. A fixed square (aspect-ratio: 1), same width as before, and let the
      Revealer's own "contain" fit-to-box logic centre whatever painting is
      inside. That rendered fine, but it was the wrong shape to ask for: a
      square forces every painting — landscape or portrait — into the same
      footprint, so a portrait piece is letterboxed on the sides and its
      actual HEIGHT is capped at the square's own side length, same as a
      landscape painting gets. Reported back: the Scream should be able to
      get taller than that, not just centred in the same small box.

   What the config already asked for, and what this restores: shapeToImage
   (see the shared makeReveal() call — "the hero and the product page take
   the painting's shape") sets --ar/--arn from the real image the moment
   it's measured. Sizing against THAT, the same way the homepage hero
   already does, means there is no square and no letterboxing at all — a
   landscape painting is exactly as wide as the column and only as tall as
   its own shape needs; a portrait painting grows to the height cap and is
   only as wide as its shape needs. min(100%, ...) on max-width keeps a
   very wide landscape painting from overrunning the column; align-items:
   stretch on .pdp and .pdp__media (see above) gives this box a real
   height/width to work from, instead of pinning everything to the top
   the way the old flat-cap version did.

   width: 100% IS LOAD-BEARING, confirmed in an actual browser (Chromium,
   via Playwright — this is the third time this box has rendered at zero
   size and the first time it was checked against a real layout engine
   instead of reasoned about statically). align-items: stretch on a flex
   column is supposed to hand this box a definite width on its own, and
   most of the time it does — but paired with aspect-ratio and a max-width
   written as min(100%, calc(...)), the stretch resolution fell through
   and the box measured 0×0 anyway, controls floating over nothing. Adding
   width: 100% explicitly removes the dependency on that resolution
   entirely; max-width still narrows a portrait painting down from there,
   same as before. Do not remove this and go back to relying on stretch
   alone without re-testing in a real browser — jsdom (what tools/smoke.mjs
   runs against) never renders layout, so it cannot catch this class of
   bug, and it passed clean every time this was broken. */
.pdp__media .reveal {
  aspect-ratio: var(--ar, 3 / 2);
  width: 100%;
  height: auto;
  max-height: 64vh;
  max-width: calc(64vh * var(--arn, 1.5));
  /* Restated with min(100%, ...): the calc() alone matches the vh cap in
     the same units (the pairing every reveal box needs, see the smoke
     test), but a very wide landscape painting could still overrun the
     column's own width — min() adds that second ceiling without dropping
     the first. Last declaration wins, so this is the one that actually
     renders. */
  max-width: min(100%, calc(64vh * var(--arn, 1.5)));
  max-height: 64dvh;
  max-width: calc(64dvh * var(--arn, 1.5));
  max-width: min(100%, calc(64dvh * var(--arn, 1.5)));
  background: var(--paper);
}

/* Both cues were sitting centred over the picture, on top of the seam's
   own wipe arrows, from the moment the page loads — "Drag to reveal" and
   then, layered right over it, "Scroll or double-click to zoom" (a gesture
   that does nothing here: the wheel belongs to the page on this component,
   same as the homepage hero, see wheelZoom: false). Between the two of
   them the grip arrows never had a clear moment to be seen at all. Product
   page only — the homepage hero keeps both, this is scoped narrower on
   purpose. JS still creates and updates these elements; hiding them here
   is enough, nothing needs to change in unvarnished.js. */
.pdp__media .reveal__hint,
.pdp__media .reveal__zoomcue {
  display: none;
}

.formats--stack { grid-template-columns: 1fr; gap: 14px; margin-top: 24px; }
.formats--stack .fmt { padding: 20px 22px; }
.formats--stack .fmt__blurb { font-size: 0.89rem; margin-top: 8px; }
.formats--stack .fmt__foot {
  padding-top: 16px;
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
}
.formats--stack .fmt__foot .btn { width: auto; padding: 11px 20px; }
.formats--stack .fmt__made { margin: 0; }

/* the honesty note, out of the accordion and onto the page */
.restored { margin: 26px 0 0; }
.restored dt {
  font-family: var(--ui); font-size: 0.68rem; font-weight: 600;
  letter-spacing: 0.1em; text-transform: uppercase; color: var(--ink-muted);
  margin-top: 22px; padding-top: 18px; border-top: 1px solid var(--rule);
}
.restored dt:first-child { margin-top: 0; }
.restored dd { margin: 8px 0 0; color: var(--ink-soft); }

.formats--stack .counter {
  font-family: var(--ui); font-size: 0.76rem; color: var(--ink-muted);
  margin: 10px 0 0;
}
.formats--stack .counter b { color: var(--accent); font-weight: 600; }

/* Back to one line, title + em-dash aside inline together — Roberto's call:
   the two-line, hero-sized version (see git history / prior comment here)
   read better on its own, but the enlarged hero (52vh -> 60vh) left no
   vertical budget for it, and the grid's first row disappeared below the
   fold on iPad and desktop with no peek to invite scrolling. This is the
   whole reason that redesign existed in the first place, so undoing it here
   is the highest-value line to cut. */
.filters { display: block; }
.filters__title {
  font-family: var(--display);
  font-size: 1.62rem;
  line-height: 1.3;
  letter-spacing: -0.005em;
  font-weight: 400;
}
.filters__count { margin: 0; }
.filters__title-aside {
  display: inline;
  font-family: var(--ui);
  font-size: 1.02rem;
  font-weight: 400;
  font-style: italic;
  letter-spacing: 0.005em;
  color: var(--ink-soft);
}

/* the tool names itself here, where someone has already used it once — a
   coined name earns nothing on first sight, only on the second */
.viewer__kicker {
  font-family: var(--ui); font-size: 0.58rem; font-weight: 600;
  letter-spacing: 0.18em; text-transform: uppercase;
  color: var(--on-void-mute);
  margin-bottom: 4px;
}

/* ---------------------------------------------------------------------------
   TOUCH — a phone has no wheel, no hover and no cursor. Everything the
   Revealer can do has to be reachable with a finger.
   --------------------------------------------------------------------------- */
@media (pointer: coarse) {
  /* At fit the page must still scroll past the picture, so vertical drags
     belong to the browser. Once zoomed they belong to the picture, or there
     is no way to reach the bottom of a canvas with a finger. */
  .reveal.is-zoomed { touch-action: none; }

  /* fingertip-sized targets: 28px is a cursor measurement. Kept smaller than
     the usual 44px minimum here specifically — on the homepage hero these
     sit under the reveal and above the fold, and the extra height was
     pushing the picture itself off the bottom of an iPhone screen. Still
     comfortably tappable, just not as generous as elsewhere. */
  .reveal__zoom { gap: 6px; }
  .reveal__zoom button { min-width: 38px; height: 38px; font-size: 0.78rem; }
  .reveal__zoom .reveal__enlarge { width: auto; min-width: 96px; padding: 0 16px; }
  .reveal__grip { width: 56px; height: 56px; margin: -28px 0 0 -28px; font-size: 0.95rem; }
  .viewer__grip { width: 60px; height: 60px; margin: -30px 0 0 -30px; }
  .viewer__btn { min-height: 40px; }

  /* no hover on touch, so the hint has to be legible rather than a whisper,
     and it must not sit under the thumb that is doing the wiping */
  .reveal__hint { display: block; }
}

/* iPad specifically, not the phone: at 38px the zoom buttons read oversized
   next to the small text beside them (title-sm is 0.92rem, master-sm and
   the buttons' own label are smaller still) — a mismatch that doesn't show
   up on a phone, where the whole hero sits closer to the eye. min-width
   621px keeps the phone's more generous 38px target (it was already tuned
   down once, for room, not for scale) and only shrinks the tablet case. */
@media (pointer: coarse) and (min-width: 621px) {
  .reveal__zoom button { width: 40px; height: 32px; font-size: 0.7rem; }
  .reveal__zoom .reveal__enlarge { width: auto; min-width: 88px; padding: 0 14px; }
}

/* ---------------------------------------------------------------------------
   HERO, LONG-COPY VARIANT (test) — the original claim paragraph is eight
   lines, so the type column has to be wider than it was when it held a single
   drag instruction. The reveal keeps two thirds of the row.
   --------------------------------------------------------------------------- */
.feature__in { grid-template-columns: 0.86fr 1.62fr; }
.feature__blurb { font-size: 0.95rem; line-height: 1.6; max-width: 46ch; }
.feature__meta .t-label { margin-bottom: 10px; }
@media (max-width: 900px) { .feature__in { grid-template-columns: 1fr; } }

/* ---------------------------------------------------------------------------
   CATALOGUE INTRO — the claim sits here, immediately above the evidence for
   it, and doubles as the instruction: it says what the grid does when you
   touch it. Two jobs, one block, no buttons.
   --------------------------------------------------------------------------- */
/* padding-BLOCK, never the shorthand.
   These sections carry .wrap as well, and .wrap is what holds the page's
   left and right gutter. A `padding` shorthand with `0` in the horizontal
   slot quietly overwrites that gutter, and the section slides out to the
   window edge while the header and the hero above it stay where they were —
   three different left margins down one page. */
/* The "You have never seen these paintings / Only the varnish on them"
   title that used to live here, and its lede, are both gone — folded into
   ".filters__title-aside" above the grid instead, as one sentence. Nothing
   in .catintro is used any more. */

/* the standfirst, in its new home at the foot of How It's Made */
.closing__sig {
  font-family: var(--display);
  font-style: italic;
  font-size: clamp(1.3rem, 2.4vw, 1.9rem);
  color: var(--accent);
  margin: 0.5em 0 0;
}
.closing .t-h1 { max-width: 24ch; margin-inline: auto; }

/* ---------------------------------------------------------------------------
   HOW IT'S MADE — the page now opens directly on this section, so it carries
   no top rule and less air above it than a band that follows another band.
   Every pixel here is one the five steps below do not get.
   --------------------------------------------------------------------------- */
#second-restoration { border-top: 0; padding-top: clamp(26px, 3.4vw, 42px); }
#second-restoration .twice__line { margin-bottom: clamp(22px, 2.6vw, 32px); }
/* The trio and the five steps are one argument, not two topics, so no rule
   divides them and the gap between is a breath rather than a break. The rule
   returns at "Why it is done this way", which genuinely is a new thought. */
#second-restoration { padding-bottom: clamp(14px, 1.8vw, 22px); }
#process { border-top: 0; padding-top: clamp(18px, 2.2vw, 28px); }

/* ---------------------------------------------------------------------------
   ABOUT SIGN-OFF — the page about the painter ends in the painter's own
   voice. Set on the dark closing band, so the quote reads as a statement
   rather than as one more paragraph of body copy.
   --------------------------------------------------------------------------- */
.closing__voice { max-width: 46ch; margin: 0 auto 30px; position: relative; }
.closing__mark {
  font-family: var(--display);
  font-size: 4.6rem; line-height: 0.7;
  color: var(--accent-bright);
  opacity: 0.5;
  margin-bottom: 12px;
}
.closing__voice p {
  font-family: var(--display);
  font-size: clamp(1.05rem, 1.9vw, 1.45rem);
  line-height: 1.42;
  font-style: italic;
  color: var(--on-void);
  margin: 0;
}
.closing__voice footer {
  font-family: var(--ui);
  font-size: 0.66rem; font-weight: 600;
  letter-spacing: 0.18em; text-transform: uppercase;
  color: var(--on-void-mute);
  margin-top: 18px;
}

/* ===========================================================================
   SHORT LANDSCAPE — a phone turned sideways
   ---------------------------------------------------------------------------
   Turning the phone is a request: "show me more of the painting." The height
   caps here are written in vh, and 44vh of a 390px-tall viewport is 170px —
   so rotating for a bigger picture produced a smaller one. Anything measured
   against the long edge has to be re-measured against the short one.
   =========================================================================== */
@media (orientation: landscape) and (max-height: 560px) {

  /* the announcement bar costs a tenth of the screen to say one thing */
  .announce { display: none; }
  .hdr__in { padding-top: 8px; padding-bottom: 8px; }

  .feature { padding: 10px 0 14px; }

  /* type goes back beside the picture: stacked, it leaves the reveal a strip */
  .feature__in {
    grid-template-columns: 0.72fr 1.9fr;
    gap: 20px;
    align-items: center;
  }
  .feature__meta .t-label { margin-bottom: 6px; }
  .feature__title { font-size: clamp(1.15rem, 3.4vh, 1.5rem); }
  .feature__blurb { font-size: 0.8rem; line-height: 1.45; margin-top: 8px; }

  /* The picture takes the height that is actually left, not a share of the
     whole screen. 78vh sounds generous until you subtract a sticky header
     from above it: the total came to more than one screen, so the hero never
     sat still — you always had to scroll to see the bottom of it, which is
     what "not anchored" meant. dvh rather than vh, because Safari's collapsing
     toolbar makes vh a moving target and the picture jumps as you scroll.

     THE WIDTH CAP HAS TO COME WITH IT. The base rule pairs `max-height:
     44vh` with `max-width: calc(44vh * --arn)`, so that clamping the height
     cannot leave white columns either side. That pairing was written for
     portrait and is wrong here: 44vh of a landscape iPhone is 172px, so the
     picture was being held to about 217px wide while this rule gave it 286px
     of height — a frame taller than it is wide, on a painting that is 1.26:1,
     and smaller than the same phone shows in portrait.

     So the cap is restated in terms of the height actually available. The
     picture keeps the painting's shape and takes the room that is left. */
  .reveal,
  .pdp__media .reveal {
    aspect-ratio: var(--ar, 3 / 2);
    height: auto;
    max-height: calc(100vh - 104px);
    max-width: calc((100vh - 104px) * var(--arn, 1.5));
    max-height: calc(100dvh - 104px);
    max-width: calc((100dvh - 104px) * var(--arn, 1.5));
    min-height: 180px;
  }

  /* the header stops floating over the picture and simply scrolls away */
  .hdr { position: static; }
  .feature { border-bottom: 0; }

  .reveal__hint { display: none; }          /* no room, and no hover to need it */

  /* --- the popup, where the picture owns the screen --------------------- */
  .viewer__bar { padding: 6px 14px; gap: 8px; }
  .viewer__title { font-size: 0.9rem; }
  .viewer__sub, .viewer__kicker { display: none; }
  .viewer__id { min-width: 0; }
  .viewer__btn { padding: 7px 10px; font-size: 0.68rem; min-height: 0; }
  .viewer__control { padding: 6px 16px 8px; gap: 12px; }
  .viewer__hint { display: none; }
  .viewer__buy { bottom: 10px; }

  /* the product page stacks its columns rather than squeezing both */
  .pdp { grid-template-columns: 1fr; }
  .pdp__thumbs { display: none; }
}

/* very short — a small phone in landscape is barely 320px tall */
@media (orientation: landscape) and (max-height: 430px) {
  .feature__meta { display: none; }         /* the painting, and nothing else */
  .feature__in { grid-template-columns: 1fr; }
  /* Same pairing as the block above: cap the height and the width in the
     same units, or the base rule's portrait-derived width cap survives and
     squeezes the picture into a column. On a phone this rule and the one
     above BOTH apply, so getting it right in only one of them fixes
     nothing. */
  .reveal, .pdp__media .reveal {
    aspect-ratio: var(--ar, 3 / 2);
    height: auto;
    max-height: calc(100vh - 78px);
    max-width: calc((100vh - 78px) * var(--arn, 1.5));
    max-height: calc(100dvh - 78px);
    max-width: calc((100dvh - 78px) * var(--arn, 1.5));
  }
}

/* ===========================================================================
   THE PALE BLUE FLASH
   ---------------------------------------------------------------------------
   Reported while dragging the wipe back and forth: a pale blue wash appears
   over the painting for an instant, then clears. Two different causes, one
   per device, and both are the browser doing something reasonable with a
   gesture we have taken over.

   · DESKTOP — a drag is also how you select text. `user-select: none` was on
     the inline reveal but never on the popup's stage, so its labels, hint and
     buy button were all selectable. Sweep the drag across them and the
     browser paints them in the system selection colour, which on macOS is
     exactly that pale blue.

   · TOUCH — Safari flashes `-webkit-tap-highlight-color` over whatever you
     press. It is a translucent blue-grey, it lasts an instant, and it is
     drawn on top of everything.

   Nothing inside a picture that exists to be dragged should be selectable or
   highlightable, so say so once, for every descendant, in both places.
   =========================================================================== */
.reveal, .reveal *,
.viewer, .viewer * {
  -webkit-user-select: none;
  -moz-user-select: none;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
}
.reveal img, .viewer img { -webkit-user-drag: none; }

/* and if a selection is somehow made anyway, do not paint it over the work */
.reveal ::selection,  .reveal::selection,
.viewer ::selection,  .viewer::selection { background: transparent; }
.reveal ::-moz-selection, .viewer ::-moz-selection { background: transparent; }

/* Mockup only — remove before launch. Marks a tile whose real files have not
   been ingested yet, so a placeholder can never be mistaken for finished work
   in a screenshot or a share. */
.tile__tag--wip {
  background: rgba(14, 15, 17, 0.62);
  color: rgba(255, 255, 255, 0.82);
  border-color: rgba(255, 255, 255, 0.3);
}

.restored__note { font-size: 1.02rem; color: var(--ink); margin-top: 22px; max-width: 62ch; }
.restored__draft:not(:empty) {
  font-family: var(--ui); font-size: 0.68rem; font-weight: 600;
  letter-spacing: 0.1em; text-transform: uppercase;
  color: #8A5A1E; background: var(--paper-warm);
  border-left: 2px solid #C08A3E; padding: 9px 12px; margin-top: 16px;
  max-width: 62ch;
}

/* a tile whose image genuinely will not load says so, rather than sitting blank */
.is-missing .tile__frame::after,
.is-missing::after {
  content: "image missing";
  position: absolute; inset: 0; display: grid; place-items: center;
  font-family: var(--ui); font-size: 0.66rem; letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--ink-muted);
  background: var(--paper-warm);
}

/* ---------------------------------------------------------------------------
   DEEP-ZOOM TILES
   A grid laid over the flat pair, so the largest masters can be looked at at
   full resolution without ever decoding the whole thing. Tiles carry their
   own width and height inline: they must not be sized by the intrinsic size
   of the file, because a tile with no src yet still has to hold its place.
   --------------------------------------------------------------------------- */
.reveal__tiles {
  position: absolute; top: 0; left: 0;
  pointer-events: none;
}
.reveal__tiles img {
  position: absolute;
  max-width: none;
  -webkit-user-drag: none; user-select: none;
}

/* ---------------------------------------------------------------------------
   "LOADING DETAIL"
   Deep zoom fetches a megabyte or two of tiles. Most of the time they arrive
   before anyone notices; occasionally they do not, and a viewer that has gone
   quiet for three seconds reads as broken rather than busy. This says which
   it is — but only after half a second, so the ordinary case stays silent.
   --------------------------------------------------------------------------- */
.reveal__busy {
  /* In the middle, not tucked in a corner. It appears once, briefly, and has
     to be noticed in that one moment or it may as well not exist. Sat below
     the centre rather than on it, so it never lands under the seam handle. */
  position: absolute; left: 50%; top: 62%; z-index: 8;
  transform: translateX(-50%);
  font-family: var(--ui); font-size: 0.62rem; font-weight: 600;
  letter-spacing: 0.14em; text-transform: uppercase;
  color: #fff;
  background: rgba(14, 15, 17, 0.55);
  padding: 6px 11px;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.25s var(--ease);
}
.is-busy .reveal__busy { opacity: 1; }
.reveal__busy::after {
  content: "";
  display: inline-block;
  width: 0.55em; height: 0.55em;
  margin-left: 8px;
  border: 1px solid currentColor;
  border-right-color: transparent;
  border-radius: 50%;
  vertical-align: baseline;
  animation: busyspin 0.8s linear infinite;
}
@keyframes busyspin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .reveal__busy::after { animation: none; }
}
