/** Shopify CDN: Minification failed

Line 2312:0 Unexpected "}"

**/
/* ===================================================================
   2026-07-30 - homepage structure + mobile spacing pass
   Four things changed on the page, so four things change here:
   1. The craft/print block is gone from the section, so its exits
      collapse to one row of two links under "No stock. No batch."
   2. Client work stacks: label, then visual, then the studio link.
   3. The pin map's photo circles were colliding on a phone and the
      pinned frame left a dead white band under the map.
   4. The fan caption sat too close to the print strips on a phone.
   =================================================================== */

/* --- 1. Shop exits, one row under the statement block ------------- */
.blkcta{display:flex;flex-wrap:wrap;justify-content:center;align-items:center;gap:14px 32px}
.blkcta .linkarrow{font-size:14.5px}
@media(max-width:680px){
  .blkcta{gap:12px 24px;margin-top:24px}
  .blkcta .linkarrow{font-size:14px}
}

/* --- 2. Client work, stacked ---------------------------------------
   Was a two-column grid with the copy beside the carousel. The h2 is
   the whole argument, so it reads better as a single line the visual
   then answers, with the route to the Studio page after the proof
   rather than before it. Cards shrink on desktop because a card that
   was 66% of a narrow column becomes enormous at full wrap width. */
.studio .wrap{display:block}
.studiotext{max-width:34ch}
.studiovisual{margin-top:30px}
.studiolinks{margin-top:26px}
.studio .scard{flex:0 0 clamp(240px,30%,340px);scroll-snap-stop:always}
@media(max-width:680px){
  .studio{padding:6vh 0}
  .studiotext{max-width:none}
  .studiovisual{order:0;margin-top:22px}
  .studiolinks{margin-top:20px}
  .studio .scard{flex:0 0 74%}
}

/* --- 3. Pin map on mobile ------------------------------------------
   The circles were clamp(64px,21vw,88px) on a ~335px-wide map, so
   France/Italy touched at the top and Indonesia/Australia overlapped
   at the bottom right. Smaller circles, six hand-checked positions
   with real clearance at 375px wide, and the dashed trail hidden
   because its coordinates belong to the desktop layout and a
   traveller dot drifting away from the pins looks like a bug.

   .pstage is pinned by GSAP at 100vh, and the map only filled the
   top third of that frame, which is the "large open white space"
   under the map. Centring the cluster splits that space above and
   below so it reads as margin instead of a hole. */
@media(max-width:680px){
  .pstage{height:86vh;justify-content:center;padding-top:0;gap:16px}
  .pinmap-inner{width:100%;aspect-ratio:1000/560}
  .pintrail{display:none}
  .pinphoto{width:clamp(36px,10.5vw,48px)}
  .pin{gap:5px}
  .pin.active{transform:translate(-50%,-50%) scale(1.12)}
  .pinname b{font-size:10.5px}
  .pinname span{font-size:9px;margin-top:1px}
  .pin.p1{left:41%;top:26%}
  .pin.p2{left:88%;top:68%}
  .pin.p3{left:34%;top:58%}
  .pin.p4{left:15%;top:32%}
  .pin.p5{left:64%;top:22%}
  .pin.p6{left:75%;top:47%}

  /* 2026-09-11: Australia's label only. Reported from a phone recording -
     "Sandstone coast" rendered as "Sandstone coas", cut off at the right.

     .pin is centred on its coordinate (translate(-50%,-50%)) and .pinname
     is a nowrap flex child centred under the dot, so a label always spans
     its pin's x plus or minus half its own width. p2 sits at left:88%,
     which on a 362px map box leaves 43px to the container's right edge,
     while the label measures 89px across when active - 44.5px each side.
     It therefore lands about 1px past the edge of .pinmap-scroll, which
     is overflow-x:auto and clips it. One pixel here, but it is the whole
     glyph on a device whose text metrics run slightly wider, which is
     what the recording shows.

     Measured all six the same way at 402x874: p2 is the only one that
     overflows (right edge 383 against the box's 382). p4 is the nearest
     on the other side and still clears by 15px, p6 by 58px, so this is
     genuinely a one-pin problem and is fixed as one rather than by
     changing .pinname's wrapping or size for all of them.

     Shifting the LABEL rather than the pin: the dot stays exactly where
     the artwork puts it, which is the constraint the pin percentages and
     the traveller dot both depend on. A label tucking inward at the edge
     of a map reads as intentional. 26% of its own width is 23px, leaving
     about 24px of clearance - enough that wider text metrics cannot
     reach the edge. */
  .pin.p2 .pinname{transform:translateX(-26%)}
}

/* --- 4. Print strips and their caption on mobile -------------------
   The caption box sat 6vh off the bottom of a 100vh stage with 48vh
   cards lifted upward, which left the copy crowding the bottom edge
   of the fan. Shorter cards, caption lifted and narrowed to a
   readable measure, and real air before "No stock. No batch." */
@media(max-width:680px){
  .fanstage{height:92vh}
  .strip{width:38vw;max-width:200px;height:42dvh}
  .fancaption{bottom:9dvh;width:calc(100vw - 40px);max-width:390px;padding:18px 22px}
  .fancaption .mono{margin-bottom:8px}
  .fancaption p{font-size:13.5px;max-width:31ch;margin:0 auto}
  .blockgap{height:3vh}
  .blk{padding:3vh 0 4vh}
}

/* --- 5. Range strips now carry every collection --------------------
   Nine look cards and six place cards, so the strips genuinely need
   their horizontal scroll. Slightly narrower cards on mobile so a
   second card is always partly visible, which is what tells someone
   the row scrolls at all. */
@media(max-width:680px){
  .rcard{width:138px}
  .rangestrip{gap:12px}
}

/* ===================================================================
   2026-08-03 - CTA colour fix, selected-grid Tetris rebuild, range
   orientation, section-gap tokens
   User feedback batch: "take one home" heading rendering black instead
   of white, grey letterboxing around some selected-grid photos, range
   cards forcing every photo into one crop, gaps between blocks still
   too wide, and a request to lock section spacing into one system
   instead of ad hoc per-section values.
   =================================================================== */

/* --- 6. CTA scene heading: force white -----------------------------
   .cta-inner sets color:var(--paper) on the parent, but Dawn's base
   theme applies its own element-selector h2 colour rule with higher
   specificity, so the inherited colour was losing the cascade. This
   sheet loads after cc-v3-home.css and after Dawn's own styles, so a
   direct selector on the heading (no !important needed once it's this
   specific, but added anyway since Dawn's rule is the known offender)
   settles it for good. */
.cta-scene .cta-inner h2,
.cta-scene .cta-inner p{color:var(--paper) !important}
/* 2026-08-23: direct feedback that the two CTA buttons were "taking
   over too much." The "Enquire about a commission" button was removed
   in sections/cc-home-v3.liquid (see that file's comment on this
   block) - a print-shop CTA doesn't need a second, competing action
   for a different flow. Trimmed the one remaining button's padding a
   touch now that it's carrying the moment alone, so it reads as a
   clear single action rather than a slab. .liquid is only used in
   this one block sitewide (checked), so this is scoped defensively
   rather than out of necessity. */
.cta-scene .btnrow .liquid{padding:13px 26px;font-size:14px}

/* Mixed type scale on "Take one home." - large/small/large, echoing
   the reference layout (Love Languages-style word-weight contrast).
   Sizes are relative to the base h2 clamp already set in
   cc-v3-home.css, not a fixed px value, so it still scales down on
   mobile. */
.cta-scene h2.mixedtype{display:flex;flex-wrap:wrap;align-items:baseline;justify-content:center;gap:0 .28em;line-height:.98}
.cta-scene h2.mixedtype .wd{display:inline-block}
.cta-scene h2.mixedtype .wd-lg{font-size:1em}
.cta-scene h2.mixedtype .wd-sm{font-size:.56em;font-style:italic;font-weight:400;opacity:.86;align-self:center}
@media(max-width:680px){
  .cta-scene h2.mixedtype .wd-sm{font-size:.5em}
}
/* 2026-08-23: this rule never set a font-family, so "Take one home."
   was inheriting cc-brand.css's bare h1/h2/h3 rule (--font-display,
   Fraunces - a serif) by accident. Direct feedback: apply the same
   script+Switzer mix used elsewhere on the site for this kind of
   accent moment (.cc-statement's "Fine Art for walls", .cc-float-
   tagline) - script carries the larger/primary words, Switzer carries
   the smaller connector, matching that established pairing. .wd-lg
   ("Take"/"home.") now gets the sitewide accent script; .wd-sm ("one")
   now gets the sitewide body sans instead of the inherited serif.
   Scoped to these two classes only - does not touch the bare h1/h2/h3
   rule itself, so nothing else on the site is affected. */
.cta-scene h2.mixedtype .wd-lg{font-family:'Tempting',cursive;font-style:normal;font-weight:400}
.cta-scene h2.mixedtype .wd-sm{font-family:var(--font-body,'Switzer','Helvetica Neue',Arial,sans-serif)}

/* --- 7. Selected grid: no more letterboxing, Tetris layout ---------
   Base sheet uses object-fit:contain on non-.tc tiles, which shows the
   background colour around any photo whose real ratio doesn't hit the
   fixed aspect-ratio:3/2 box exactly. Cover fixes that outright - the
   frame is the print's presentation card, not the raw file, so a crop
   to fill is correct here (matches how .tc tiles already behave).

   Layout moves from a single flex row to a 3-column grid with dense
   auto-flow: .v tiles (portrait prints, confirmed 1720x2580 via
   Shopify product data) take one column and span two rows; .h tiles
   (landscape, 2580x1720) take two columns and one row. Grid handles
   the packing, so the count of h/v tiles doesn't need to divide evenly
   - dense flow fills gaps left by an odd tile. */
.selected .tile .ph img{object-fit:cover}
.selected .grid{display:grid;grid-template-columns:repeat(3,1fr);grid-auto-flow:dense;gap:22px}
.selected .tile{display:flex;flex-direction:column}
.selected .tile .ph{aspect-ratio:auto;flex:1}
.selected .tile.h{grid-column:span 2}
.selected .tile.v{grid-column:span 1;grid-row:span 2}
@media(max-width:900px){
  .selected .grid{grid-template-columns:repeat(2,1fr)}
  .selected .tile.h{grid-column:span 2}
  .selected .tile.v{grid-column:span 1;grid-row:span 2}
}
@media(max-width:560px){
  .selected .grid{grid-template-columns:1fr;gap:16px}
  .selected .tile.h,.selected .tile.v{grid-column:span 1;grid-row:span 1}
}

/* --- 8. Range cards: real orientation, not a forced crop ------------
   Base .rcard is a fixed aspect-ratio:4/5 box with object-fit:cover,
   which crops every landscape source photo into a portrait sliver.
   .rcard.h keeps the strip's horizontal-scroll card at a wider,
   shorter box that matches a 3:2 photo; .rcard.v keeps the existing
   4:5 box for portrait sources. Orientation is set server-side in the
   liquid loop via the place_vertical/look_vertical lists - see
   sections/cc-home-v3.liquid. Those lists are a reasoned estimate for
   the range collection thumbnails (direct asset dimension lookup was
   blocked this session) and should be checked visually once the
   browser tools are back. */
.rcard.h{aspect-ratio:3/2;width:280px}
.rcard.v{aspect-ratio:4/5;width:200px}
@media(max-width:680px){
  .rcard.h{width:220px}
  .rcard.v{width:138px}
}

/* --- 9. Section-gap token system ------------------------------------
   Every section used its own ad hoc Xvh top/bottom padding, which is
   how the gaps drifted wide and inconsistent. One scale, three sizes,
   applied here so it can be tuned in one place going forward instead
   of per-section. Values are deliberately tighter than the old ones.

   2026-08-03 (fourteenth pass) - tightened further per direct feedback
   ("reduce the gap between all the blocks"). This is a global retune of
   the same three tokens, not a per-section patch - every section that
   reads var(--cc-gap-*) picks up the smaller number automatically. */
/* 2026-09-09 (breathability pass): the scale is now px-based, not vh.

   Two changes, and the second is the point of the first.

   1. vh was the wrong unit for vertical rhythm. Type is sized in clamp()
      against viewport WIDTH, so on a short laptop 3vh put 21px under a 100px
      headline and on a tall display 43px under the same headline - the
      rhythm drifted with a dimension the type never reads. These now clamp
      against width, so spacing and type scale together.

   2. The values are larger. Measured on the live page at 1440x900 the median
      gap between sections was 27px under headlines set at 100px+, which is
      the whole "not breathable" report in one number.

   This deliberately walks back part of the fourteenth pass (2026-08-03,
   "reduce the gap between all the blocks"). That feedback was real, but it
   was about DEAD space - blank paper exposed by pin spacers - and the fix
   was applied to this token scale, which crushed the legitimate type rhythm
   along with it. The dead space has since been addressed at its source (pin
   lengths, and Portal unpinned this pass), so restoring rhythm here should
   not bring the original complaint back. If it does, this block is the one
   lever to pull - not per-section patches. */
:root{
  /* 2026-09-09, dialled back one notch after a first look: 120/88/56 read as
     drift rather than breathing room. Still ~3.5x the 27px it replaced. */
  --cc-gap-lg:clamp(52px, 6.6vw, 96px);
  --cc-gap-md:clamp(36px, 5vw, 72px);
  --cc-gap-sm:clamp(24px, 3.2vw, 44px);
}
.selected{padding:var(--cc-gap-lg) 0}
.studio{padding:var(--cc-gap-md) 0 var(--cc-gap-lg)}
.contact{padding:var(--cc-gap-lg) 0}
.range{padding:var(--cc-gap-md) 0 var(--cc-gap-sm)}
.blk{padding:var(--cc-gap-sm) 0 var(--cc-gap-md)}
.blockgap{height:.3vh}
@media(max-width:680px){
  :root{
    /* Same scale shape, ~30% tighter: a phone shows less of the page at
       once, so an identical gap reads as a longer pause. */
    --cc-gap-lg:clamp(44px, 11vw, 72px);
    --cc-gap-md:clamp(32px, 8vw, 56px);
    --cc-gap-sm:clamp(20px, 5vw, 36px);
  }
}

/* ===================================================================
   2026-08-03 (second pass) - portal headline mixed type scale
   Same "smooth and gentle" mixed-size treatment requested for the
   portal section's "The start of a conversation." as was applied to
   the CTA's "Take one home." - small italic connector words between
   large set words, echoing the reference spread sent for the overall
   typographic language. The whole .ptype element is what GSAP scales
   through the portal pin (see portalTl in cc-v3-home.js), so nesting
   spans inside it doesn't touch that animation at all.
   =================================================================== */
.portal .ptype.mixedtype{white-space:nowrap}
.portal .ptype.mixedtype .wd{display:inline-block}
.portal .ptype.mixedtype .wd-lg{font-size:1em}
.portal .ptype.mixedtype .wd-sm{font-size:.46em;font-weight:400;opacity:.84;vertical-align:middle}
.portal .ptype.mixedtype .wd-sm.wd-italic{font-style:italic}
@media(max-width:680px){
  .portal .ptype.mixedtype .wd-sm{font-size:.4em}
}

/* 2026-08-25: matching font-family fix for the portal headline as was
   applied to "Take one home." (.cta-scene h2.mixedtype, above) - this
   rule never set a font-family either, so "The start of a
   conversation." was inheriting the bare h1/h2/h3 serif (--font-
   display, Fraunces) by accident, same bug, same fix. Direct
   instruction: "for the start of a conversation change the font to
   switzer and tempting", referencing the already-approved script+
   Switzer pairing used on the CTA heading and sitewide accent moments
   (.cc-accent-script). .wd-lg ("start"/"conversation.") gets the
   sitewide accent script; .wd-sm ("The"/"of a") gets the sitewide body
   sans instead of the inherited serif - same split, same reasoning as
   the CTA fix. Scoped to these two classes only. */
.portal .ptype.mixedtype .wd-lg{font-family:'Tempting',cursive;font-style:normal;font-weight:400}
.portal .ptype.mixedtype .wd-sm{font-family:var(--font-body,'Switzer','Helvetica Neue',Arial,sans-serif)}

/* ===================================================================
   2026-08-03 (third pass) - portal headline split across two lines
   Requested split: "the start of a" / "conversation" on its own line.
   .ptype base sets white-space:nowrap (needed for the single-line CTA
   and the old single-line portal text); .twoline overrides that here
   and stacks the two .ptype-line spans as a flex column instead, so
   the break is deliberate rather than a wrap the browser chooses for
   itself at some viewport width. Still one .ptype element for GSAP's
   scale animation to grab (see portalTl in cc-v3-home.js) - the lines
   are children, not siblings of it. */
.portal .ptype.mixedtype.twoline{
  white-space: normal;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.08em;
}
.portal .ptype.mixedtype.twoline .ptype-line{
  display: block;
  white-space: nowrap;
}

/* ===================================================================
   2026-08-03 (fifth pass) - fan/print section: cut the dead space
   before "No stock. No batch."
   Root cause: .fanstage is a 100vh box (see cc-v3-home.css) with the
   three prints centered inside it via flexbox, then nudged up by a
   JS transform (liftY, cc-v3-home.js) that only ever moves them by a
   fixed amount (max 180px) - nowhere near enough to compensate for a
   full 100vh box on a normal-height screen. The result is a large,
   genuinely empty band of paper background below the prints before
   .blockgap and .blk even start. This has been the same complaint
   across multiple passes; the earlier "scroll-gap saga" fix (locked
   status doc, 2026-07-29) addressed the *pinned* sections (hero, map,
   portal) by switching them from CSS sticky to GSAP pin with an
   explicit end distance - .fanstage was never pinned in the first
   place (it's plain document flow, confirmed in cc-v3-home.js's own
   comment), so that fix never touched this specific dead-space cause.

   Rather than resize .fanstage itself (risky blind: the JS liftY
   value and the fan-spread animation's start/end scrub are both
   tuned against the existing 100vh box, and this session has no
   working browser to visually confirm a resize wouldn't clip the
   prints), this pulls the *next* section up into the empty tail
   instead. .blockgap sits directly after .fanstage and shares the
   same --paper background, so a negative top margin here closes the
   gap with no visible seam and zero risk to the fan animation itself.
   Scoped to desktop/tablet; a smaller pull-up on mobile, where
   .strip is taller relative to the viewport and there's less spare
   room to begin with.
   =================================================================== */
.blockgap {
  margin-top: -26vh;
  position: relative;
  z-index: 1;
}
@media (max-width: 680px) {
  .blockgap {
    margin-top: -10vh;
  }
}

/* ===================================================================
   2026-08-03 (sixth pass) - fix text hidden behind fan strips, close
   the gap further, and LOCK this as the sitewide section-gap standard
   -------------------------------------------------------------------
   What broke: the fifth-pass fix (.blockgap{margin-top:-26vh}) pulls
   .blockgap and the .blk statement block up into the empty tail of
   .fanstage to close the gap. But .strip (the print cards) get an
   inline z-index of 10-12 from GSAP (gsap.set in cc-v3-home.js), and
   neither .fanstage nor .blk ever set an explicit z-index of their
   own - both sit at the default stacking level. Once .blk's box was
   pulled up far enough to physically overlap the tail of .fanstage,
   the .strip images (explicit z-index 10-12) painted on top of the
   "No stock. No batch." text (implicit z-index 0), which is why the
   text looked like it had vanished. It never left the page, it was
   just stacked underneath a photo.

   Fix: give .blk an explicit z-index well above anything .strip ever
   uses, so the statement text always wins regardless of how far it
   gets pulled up. This is the safe fix - it doesn't touch the fan
   animation, the lift math, or any pinned scroll distance, it only
   settles paint order.

   Gap: pulled a further 6vh tighter (-26vh to -32vh desktop, -10vh to
   -12vh mobile) now that the text can no longer be hidden by the
   overlap this creates.

   LOCKED STANDARD - read this before touching any section gap again:
   1. Section spacing runs on the --cc-gap-lg/md/sm tokens defined in
      this same file's "Section-gap token system" block above. New
      sections should use those tokens, not a new ad hoc Xvh value.
   2. Any section that sits directly after a GSAP-pinned or
      flex-centered 100vh stage (.fanstage is the only one today) is
      allowed to use a negative margin to close dead space left by
      that stage's own animation math, but MUST also carry an
      explicit z-index higher than any inline z-index set by that
      stage's JS (.strip's inline z-index tops out at 12; anything
      pulled up next to it uses z-index 30, giving clear headroom).
      Skipping the z-index half of this pattern is exactly what
      caused this regression.
   3. SUPERSEDED - see the thirteenth pass at the end of this file for
      the current, corrected rule; everything between here and there
      (tenth/eleventh/twelfth passes) was built on an incomplete model
      of the fan animation and has been superseded in turn.
   =================================================================== */
.blk {
  position: relative;
  z-index: 30;
}
.blockgap {
  margin-top: -32vh;
  position: relative;
  z-index: 1;
}
@media (max-width: 680px) {
  .blockgap {
    margin-top: -12vh;
  }
}

/* ===================================================================
   2026-08-03 (seventh pass) - remove the duplicate header on the
   homepage
   -------------------------------------------------------------------
   The homepage renders two navigation bars: Dawn's own header-group
   (sections/header.liquid, has search/account/cart icons, already
   carries the same tagline + elevated wordmark as this page) and this
   section's own floating .tape nav (defined inline in
   sections/cc-home-v3.liquid), which was meant to sit as a
   translucent brand strip layer but now just reads as a second, plain
   header stacked on top of the real one. Per direct instruction: keep
   the icon-bearing Dawn header, remove the duplicate. This file only
   loads on the homepage section, so scoping isn't needed - .tape does
   not exist as a rendered element anywhere else in the theme.
   =================================================================== */
.tape { display: none; }

/* ===================================================================
   2026-08-03 (eighth pass) - map pins are now real links
   -------------------------------------------------------------------
   Each .pin in the Block 4 travel map (sections/cc-home-v3.liquid) is
   now an <a> pointing at the actual matching print, not a dead <div>.
   Anchors carry default link styling (underline, blue/visited colour)
   that the surrounding .pinphoto/.pinname markup was never designed
   for, so this resets that back to how the div version looked and
   adds a focus ring for keyboard users, since .pin has no visible
   focus state otherwise.
   =================================================================== */
.pin {
  text-decoration: none;
  color: inherit;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.pin:focus-visible {
  outline: 2px solid var(--ink, #11110F);
  outline-offset: 4px;
  border-radius: 50%;
}

/* ===================================================================
   2026-08-03 (tenth pass, SUPERSEDED - see thirteenth pass) - shrink
   .fanstage itself instead of an ever-larger negative margin
   =================================================================== */
@media (min-width: 681px) {
  .fanstage {
    height: 76vh;
  }
}

/* ===================================================================
   2026-08-03 (eleventh pass, SUPERSEDED - see thirteenth pass)
   =================================================================== */

/* ===================================================================
   2026-08-03 (twelfth pass, SUPERSEDED - see thirteenth pass) - fixed
   a mobile regression from the eleventh pass, but the underlying
   number (-22vh) was still wrong; see thirteenth pass.
   =================================================================== */

/* ===================================================================
   2026-08-03 (thirteenth pass) - correct the fan-gap math: liftY was
   never accounted for, and it breaks the symmetric-dead-space model
   the tenth/eleventh/twelfth passes were built on
   -------------------------------------------------------------------
   Root cause of the still-huge gap the user kept screenshotting:
   cc-v3-home.js sets "var liftY = -1 * Math.min(180, innerHeight*0.2)"
   (roughly -19 to -20vh on ordinary viewports) and applies it as a
   permanent upward shift to the whole print cluster inside .fanstage,
   on top of the plain flexbox centering. Every pass so far (tenth
   through twelfth) modeled the cluster as sitting dead-center in the
   box, splitting dead space evenly top and bottom. It doesn't - liftY
   drags it upward, so the real layout is: a small top gap (~3-6vh,
   which is exactly the thin strip visible in every screenshot the
   user sent) and a MUCH larger bottom gap than the symmetric model
   predicted.

   Worked through properly this time (strip 55vh desktop / 42vh mobile,
   liftY ~19vh, centered in whatever height .fanstage is):
   - Desktop needs .fanstage >= ~95vh just to avoid clipping the TOP of
     the cards once liftY pulls them up - the eleventh pass's 68vh box
     was very likely cropping the tops of the Opera House prints this
     whole time (the torn-paper edge treatment on the images may have
     been masking that visually, which is probably why it wasn't
     flagged directly). Reverting .fanstage to 100vh (i.e. undoing the
     tenth/eleventh pass override entirely) so nothing is cropped.
   - With a 100vh box, real bottom dead space works out to roughly
     41-44vh on both breakpoints (mobile's 92vh box lands in the same
     order once liftY is factored in). That's what .blockgap actually
     needs to pull up now, not the -5vh/-22vh this session had been
     using based on the wrong model.

   .blk's z-index:30 (sixth pass) already protects the text if this
   pull ever overlaps the card itself, but -40vh is sized to land in
   the empty tail below the cards, not into the photo, so that safety
   net shouldn't need to do any work in practice.

   LOCKED STANDARD, corrected again (supersedes tenth-pass point 2):
   1. Section-gap tokens still govern ordinary section padding.
   2. .fanstage is NOT safe to shrink while liftY exists in its current
      form (~19-20vh) - the math above is the reason. Retuning liftY
      itself (not .fanstage) would be the correct way to shrink this
      stage in the future; don't repeat the tenth-pass mistake of
      resizing the box in isolation from the JS that moves its
      contents.
   3. Closing the fan/statement gap is, for now, back to a pull-up
      margin on .blockgap, sized against the ACTUAL asymmetric dead
      space (liftY-shifted), not a naive half-the-leftover-height
      guess. If liftY's value in cc-v3-home.js ever changes, this
      margin needs to be recalculated against the new numbers, not
      copied forward.
   =================================================================== */
@media (min-width: 681px) {
  .fanstage {
    height: 100vh;
  }
}
.blockgap {
  margin-top: -40vh;
}
@media (max-width: 680px) {
  .blockgap {
    margin-top: -40vh;
  }
}
/* thirteenth pass SUPERSEDED by the fifteenth pass below - the -40vh
   guess still left a large visible gap in practice (confirmed against
   fresh screenshots, not a caching issue - the underlying file content
   was verified correct). Kept here for the historical record only. */

/* ===================================================================
   2026-08-03 (fifteenth pass) - stop guessing a pull-up number, fix
   the actual cause instead: liftY plus an oversized 100vh box
   -------------------------------------------------------------------
   Every pass from the fifth through the thirteenth treated the fan
   section's dead space as something to patch after the fact with a
   bigger and bigger negative margin on .blockgap. Each guess (-26vh,
   -32vh, -40vh) was computed from an estimate of how much empty space
   liftY leaves inside a 100vh .fanstage box, and each estimate turned
   out smaller than the real gap on screen. Rather than guess a fourth
   number, this pass removes the two things that create the dead space
   in the first place:

   1. liftY (cc-v3-home.js) is set to 0. It existed only to nudge the
      cluster up inside an oversized box so the cards would clear the
      bottom of the viewport sooner while scrolling in - a timing aid
      for the reveal, not a layout requirement. With .fanstage now
      sized to fit its content (below), there is no oversized box left
      to nudge the cluster around inside, so the offset is dead weight
      that only pushes the top edge closer to clipping.
   2. .fanstage no longer holds an arbitrary 100vh - it is sized to the
      card height plus a fixed buffer that comfortably covers the
      rotate(+-11deg) spread and drop-shadow blur on the two angled
      side cards, so nothing at the corners clips against
      overflow:hidden. Desktop cards are 55vh tall -> 74vh stage.
      Mobile cards are 42vh tall (this file's own fourth-pass override,
      the one actually in effect - see cc-v3-home.css's 48vh mobile
      rule, which loads first and is superseded by this file) -> 58vh
      stage.

   With the box hugging its content, .blockgap goes back to a small,
   ordinary negative margin instead of a number sized to swallow tens
   of vh of dead air - -2vh both breakpoints, just enough to remove the
   seam between the stage and the statement block below it.

   LOCKED STANDARD, corrected a second time (supersedes the
   thirteenth-pass version of this note):
   1. Section-gap tokens still govern ordinary section padding.
   2. .fanstage's height is now a content-fit value (card height + a
      fixed rotation/shadow buffer), not an arbitrary viewport
      fraction. If the card height or rotation angle ever changes,
      recompute the buffer against the new numbers.
   3. liftY stays at 0. Do not reintroduce a nonzero liftY without also
      re-sizing .fanstage to match - that mismatch is exactly what
      caused this entire saga.
   =================================================================== */
@media (min-width: 681px) {
  .fanstage {
    height: 100vh;
  }
}
@media (max-width: 680px) {
  .fanstage {
    height: 100vh;
  }
}
.blockgap {
  margin-top: -2vh;
}
@media (max-width: 680px) {
  .blockgap {
    margin-top: -2vh;
  }
}

/* ===================================================================
   2026-08-19 - actually ship the fifteenth pass's own fix: .fanstage
   was never resized
   -------------------------------------------------------------------
   The fifteenth pass above (comment block, same file) lays out the full
   reasoning for shrinking .fanstage from an arbitrary 100vh to a
   content-fit box - card height plus a fixed buffer for the
   rotate(+-11deg) spread on the two angled side cards - and states the
   target numbers directly: "Desktop cards are 55vh tall -> 74vh stage.
   Mobile cards are 42vh tall... -> 58vh stage." But the rules it
   actually shipped (directly above this block) still read
   height:100vh at both breakpoints - the numeric half of that pass was
   never applied, only the liftY removal was. That's the large blank
   gap flagged in review: liftY going to 0 fixed the reveal timing, but
   the box itself stayed full-viewport, so the cards sit flex/absolute-
   centered inside a box roughly 25-42vh taller than they need, leaving
   large near-equal empty bands above and below.

   Reconfirmed safe against the CURRENT fan mechanism before shipping
   this (not just trusting the old comment): liftY doesn't exist
   anywhere in cc-v3-home.js any more - the whole reveal was replaced on
   2026-08-10 by a gesture-gated pin rebuild in its own file,
   cc-fan-stepped.js, which centers each strip with xPercent/
   yPercent:-50 (no liftY-style offset) and animates outer strips to
   x: d*spreadX, y: abs(d)*spreadY, rotate: d*11 (d = +-1), where
   spreadX = min(220, vw*.17) and spreadY = min(38, vw*.052) - the exact
   numbers already baked into this file's own static .strip transform
   (cc-v3-home.css: translate(min(220px,17vw), min(38px,5.2vw))
   rotate(11deg) on nth-child(1)/(3)), so CSS and JS agree on the
   geometry. Checked the rotated bounding box of a side card (55vh tall
   x 22vw/300px-cap wide desktop, 42vh x 38vw/200px-cap mobile, each
   rotated 11deg) plus its vertical offset against .fanstage centered at
   half its own height: clears overflow:hidden with positive margin at
   normal desktop and phone viewport heights. Margin narrows in very
   short/landscape viewports (roughly under ~450px tall), which is an
   inherent trade-off of a content-fit box vs. the old always-safe
   100vh - accepted here because it's the only thing that actually
   closes the gap, and it's what this pass already decided.

   Supersedes the fifteenth pass's shipped (but incomplete) 100vh
   numbers directly above - left unchanged, per this file's append-only
   convention, rather than edited in place. */
@media (min-width: 681px) {
  .fanstage {
    height: 74vh;
  }
}
@media (max-width: 680px) {
  .fanstage {
    height: 58dvh;
  }
}

/* ===================================================================
   2026-08-03 (sixteenth pass) - close the gap above and below the
   travel map (.porthole/.pstage), same root cause as the fan section,
   different mechanism
   -------------------------------------------------------------------
   .pstage is GSAP-pinned at 100vh (cc-v3-home.js, ScrollTrigger.create
   on .pstage) so the section holds the full viewport height while the
   pin's scroll distance plays out - unlike .fanstage, shrinking this
   box outright risks showing the sections before/after it peeking in
   around the sides while it's pinned, so the fix here is different:
   make the map itself scale with the viewport instead of staying a
   fixed pixel size inside an ever-taller box.

   cc-v3-home.css caps .pinmap-inner at width:min(1000px,100%) with a
   1000/520 aspect ratio - roughly 520px tall, always, on any screen.
   That's fine on a small laptop but on a larger display the 100vh
   .pstage box keeps growing while the map stays pinned at ~520px,
   which is exactly the "huge white gap top and bottom of the map"
   the screenshots show - confirmed by the pinmap-inner width rule
   never referencing viewport height at all, only viewport width and
   a flat px cap.

   Sizing the map off vh instead of a flat px cap makes it grow with
   the same box it sits inside, so the map fills a consistent
   proportion of .pstage on any screen instead of a shrinking one on
   larger displays. ~64vh tall leaves reasonable, roughly even margin
   above and below once the eyebrow text, the 20px gap, and the
   header's ~72px overlap at the top of the pin are accounted for. */
@media (min-width: 681px) {
  .pinmap-inner {
    width: min(1000px, 100%, 123vh);
  }
}

/* ===================================================================
   2026-08-03 (seventeenth pass) - same map-gap fix, mobile breakpoint
   -------------------------------------------------------------------
   The sixteenth pass only scoped the vh-driven map size to desktop
   (min-width:681px). Mobile has its own .pstage height (86vh, see the
   third pass earlier in this file) and its own .pinmap-inner rule
   (width:100%, aspect-ratio 1000/560 - sized off container width, not
   viewport height), so it has the exact same disease as desktop had:
   a pinned box that scales with the screen, holding a map that
   doesn't. Confirmed by a mobile screenshot showing the same large
   white band above and below the map, flagged separately from the
   desktop shot.

   Same fix, mobile numbers: aspect-ratio 1000/560 means width =
   height * 1.7857, so capping width at 98vh (in addition to the
   existing 100% container cap) targets a map roughly 55vh tall -
   comfortably bigger than the previous container-width-only sizing on
   a typical phone, while still respecting the 100% cap on very wide
   viewports near the 680px breakpoint edge. */
@media (max-width: 680px) {
  .pinmap-inner {
    width: min(100%, 98vh);
  }
}

/* ===================================================================
   2026-08-04 (nineteenth pass) - "selected" grid rebuilt uniform,
   matching the shop grid's own fourth-pass structure and the Through
   Our Lens reference sent (every card the same size/shape, rounded
   corners, hover title pill). Replaces the Tetris mosaic from the
   seventh pass above - direction was explicit that the mosaic still
   didn't read as a resolved grid and the reference's uniform layout
   is what to match instead. .cc-hovertag itself is styled once,
   shared, in cc-sections.css; this block only handles the grid/tile
   sizing specific to this homepage section.
   =================================================================== */
.selected .grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  grid-auto-flow: row;
  gap: 16px;
}
@media (min-width: 750px) {
  .selected .grid { grid-template-columns: repeat(3, 1fr); gap: 20px; }
}
@media (min-width: 1200px) {
  .selected .grid { grid-template-columns: repeat(4, 1fr); gap: 24px; }
}
.selected .tile {
  display: flex;
  flex-direction: column;
  grid-column: span 1;
  grid-row: span 1;
}
.selected .tile .ph {
  aspect-ratio: 4 / 5;
  flex: none;
  border-radius: 1.4rem;
  overflow: hidden;
}
.selected .tile .ph img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* --- Range section: single tabbed shelf, replacing two stacked
   always-visible strips, matching the Through Our Lens tab-switch
   pattern (Best Selling / Rock Pools / Harbour Scenes - tabs above one
   horizontal-scroll row, clicking a tab swaps the row's contents).
   Same fonts as the rest of the site (--cc-font-mono for the tab
   labels, already used elsewhere for this uppercase/tracked register)
   - only the switching mechanic and tab chrome are new. .rangerow is
   hidden by default and shown via .is-active, toggled by the small
   inline script in sections/cc-home-v3.liquid. */
.rangetabs {
  display: flex;
  gap: 28px;
  justify-content: center;
  margin: 8px 0 28px;
  flex-wrap: wrap;
}
.rangetab {
  background: none;
  border: none;
  cursor: pointer;
  padding: 0 0 8px;
  font-family: var(--cc-font-mono, 'Switzer', sans-serif);
  font-size: 13px;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--cc-hint, #8a8a8a);
  border-bottom: 2px solid transparent;
  transition: color 200ms ease, border-color 200ms ease;
}
.rangetab.is-active { color: var(--cc-fg, #111); border-bottom-color: var(--cc-fg, #111); }
.rangetab:hover { color: var(--cc-fg, #111); }
.rangerow { display: none; }
.rangerow.is-active { display: block; }
@media (max-width: 680px) {
  .rangetabs { gap: 20px; margin: 6px 0 20px; }
  .rangetab { font-size: 12px; }
}

/* ===================================================================
   2026-08-04 (twentieth pass) - fix a real bug: the Tetris spans never
   actually left
   -------------------------------------------------------------------
   Direct feedback confirmed the "selected" grid still reads like the
   old mosaic after the nineteenth pass. Root cause found on review:
   the seventh pass above defines `.selected .tile.h` and
   `.selected .tile.v` (two classes each - .tile plus .h/.v), which is
   MORE specific than this file's own nineteenth-pass rule
   `.selected .tile` (one class - .tile only). CSS specificity beats
   source order, so the seventh pass's grid-column:span 2 /
   grid-row:span 2 was winning regardless of being written earlier in
   the file - the nineteenth pass's grid-column:span 1 never actually
   applied to any .h or .v tile. That is why cards kept rendering at
   different sizes despite a "uniform grid" rule already being in the
   file - this was a genuine bug, not a design miss.

   Fixing by matching the same selector specificity here, later in the
   cascade, so this rule genuinely wins. !important added as a hard
   stop - this exact rule has already churned through the seventh,
   thirteenth and fifteenth passes above, so specificity alone has
   proven too easy to accidentally out-rank again.
   =================================================================== */
.selected .tile.h,
.selected .tile.v {
  grid-column: span 1 !important;
  grid-row: span 1 !important;
}

/* ===================================================================
   2026-08-04 (twenty-first pass) - flat text exits become real
   buttons, matching the pill style already on this page
   -------------------------------------------------------------------
   Direct request: the plain underlined text links under "No stock. No
   batch." (See every print / Paper, sizes and framing), under the
   client-work carousel (View the Studio) and under the selected grid
   (Browse the full catalogue) needed to read as actual buttons - "the
   same design as the other buttons' style already present on the
   theme." That style already exists twice on this page: the CTA
   scene's .liquid pills (frosted glass on a dark photo) and the
   contact section's .booklink pill (same frosted-glass language,
   inverted for a light/paper background). These three spots all sit
   on the paper background, so they get the .booklink treatment
   exactly, not a new pattern: dark pill, backdrop blur, shine sweep on
   hover. The anchors keep their existing class="linkarrow" and href -
   only the visual language changes, so no liquid/markup edit was
   needed. .linkarrow's own underline-on-hover pseudo (::after) is
   switched off here since a pill button doesn't want an underline.
   =================================================================== */
/* 2026-08-28 (Round 35): per direct instruction ("I want the black
   button... the glass effect as in the Apple design"), the button
   already had backdrop-filter declared (twenty-first pass, above) but
   at 0.92 background opacity there was almost nothing left for the
   blur to show through - it read as flat solid black, not glass. Apple's
   own vibrancy/glass buttons sit at a much lower fill opacity so the
   blurred backdrop stays visibly part of the effect, with a light rim
   highlight instead of a near-invisible dark one. Mirrors this same
   file's own .tape nav bar (cc-v3-home.css), the site's other proven
   glass surface: lower opacity, stronger blur+saturate, light-bordered
   rim. Text/hover/focus/shine-sweep behavior below is unchanged. */
.blkcta .linkarrow,
.studiolinks .linkarrow,
.selectedcta .linkarrow {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--paper, #F7F5F0);
  background: rgba(17, 17, 15, 0.62);
  backdrop-filter: blur(16px) saturate(1.5);
  -webkit-backdrop-filter: blur(16px) saturate(1.5);
  text-decoration: none;
  font-weight: 500;
  font-size: 14.5px;
  line-height: 1;
  padding: 13px 26px;
  border-radius: 999px;
  border: 1px solid rgba(255, 255, 255, 0.16);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.22), 0 10px 24px -14px rgba(17, 17, 15, 0.4);
  position: relative;
  overflow: hidden;
  isolation: isolate;
  transition: background 0.3s ease, transform 0.2s ease;
}
.blkcta .linkarrow::after,
.studiolinks .linkarrow::after,
.selectedcta .linkarrow::after {
  display: none;
}
.blkcta .linkarrow::before,
.studiolinks .linkarrow::before,
.selectedcta .linkarrow::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: linear-gradient(115deg, transparent 30%, rgba(255, 255, 255, 0.22) 48%, transparent 66%);
  transform: translateX(-140%);
  transition: transform 0.7s cubic-bezier(0.2, 0.7, 0.2, 1);
}
.blkcta .linkarrow:hover::before,
.studiolinks .linkarrow:hover::before,
.selectedcta .linkarrow:hover::before {
  transform: translateX(140%);
}
.blkcta .linkarrow:hover,
.studiolinks .linkarrow:hover,
.selectedcta .linkarrow:hover {
  background: rgba(17, 17, 15, 0.74);
}
.blkcta .linkarrow:focus-visible,
.studiolinks .linkarrow:focus-visible,
.selectedcta .linkarrow:focus-visible {
  outline: 2px solid var(--ink, #11110F);
  outline-offset: 3px;
}
@media (max-width: 680px) {
  .blkcta .linkarrow,
  .studiolinks .linkarrow,
  .selectedcta .linkarrow {
    font-size: 14px;
    padding: 12px 22px;
  }
}

/* ===================================================================
   2026-08-04 (twenty-second pass) - correction against nine fresh
   throughourlens.com.au screenshots: no hover-only captions anywhere,
   captions sit below the photo (never on top of it), always centered
   -------------------------------------------------------------------
   Checked directly this time, not assumed. Every card on the reference
   site - the homepage's tabbed Featured Collections/Recent Additions
   rows and every shop grid view - shows its title and price in plain
   view below the image at all times. Nothing is ever revealed only on
   hover, and nothing is ever printed over the photo itself. Two things
   on this page did both of those:

   1. .cc-hovertag (fifth pass above) was built on an assumption about
      the reference rather than a direct check, and invented a hover-
      reveal pill centered over the photo that the reference doesn't
      have. It's now switched off sitewide from cc-sections.css (the
      file that defines it) - this page's .tile elements just fall
      back to their own always-visible .t/.s/.pr caption below the
      photo, which was already there and already correct in principle.

   2. .rcard (the range/tab-switch shelf cards - the actual match for
      the reference's Featured Collections tabs) had the opposite
      problem: no below-image caption at all. Its only label was
      .rtag, an absolutely-positioned span sitting directly on top of
      the photo's bottom-left corner against a dark gradient scrim
      (.rcard::after). That's a real structural mismatch, not a taste
      difference - the reference never overlays text on the image.

      Fixed without touching the liquid markup: .rcard already has
      box-sizing:border-box (global reset in cc-v3-home.css) and
      overflow:hidden, so adding padding-bottom carves out a reserved
      strip at the bottom of the box that the image (height:100% of
      the *content* box) naturally shrinks to leave clear - the photo
      no longer fills that strip, and text placed in it via
      position:absolute isn't clipped by overflow:hidden because
      padding is inside the border box. .rtag moves into that strip,
      full-width and centered, plain ink-on-paper text instead of
      white-on-scrim, and the scrim itself is removed since nothing
      needs to sit legibly on top of the photo anymore.

   3. The "selected" grid's own always-visible caption (.t/.s/.pr,
      defined in cc-v3-home.css) was left-aligned - the reference
      centers every caption under every card. Centered here without
      touching the base file.
   =================================================================== */
.selected .tile .t,
.selected .tile .s,
.selected .tile .pr {
  text-align: center;
}

.rcard {
  padding-bottom: 30px;
  position: relative;
}
.rcard::after {
  display: none;
}
.rtag {
  left: 0;
  right: 0;
  bottom: 4px;
  text-align: center;
  color: var(--ink, #11110F);
  text-shadow: none;
  font-weight: 500;
  text-transform: none;
  letter-spacing: 0.01em;
  font-size: 13px;
  white-space: normal;
}

/* ===================================================================
   2026-08-04 (twenty-third pass) - Shop mega-menu invisible behind
   the pinned hero
   -------------------------------------------------------------------
   Real bug, homepage only. Dawn's own mega-menu content carries the
   generic `.global-settings-popup` class (also used for the language/
   country switcher popups), and base.css hard-codes that class to
   z-index:-1 with no [open] override anywhere in the theme. That is
   harmless on every other page - nothing else on those pages
   establishes a competing stacking context at the same screen
   position, so a z-index:-1 popup still paints in front of ordinary
   in-flow page content below it.

   The homepage is different: cc-v3-home.js pins `.stage` (the hero)
   with GSAP ScrollTrigger the moment the page loads (start:'top top'),
   switching it to position:fixed, which creates a new stacking context
   covering the same region as the header. A fixed element at the
   implicit z-index:0 stacks above anything at z-index:-1 in the same
   ancestor stacking context, so once the hero's pin engages, the Shop
   dropdown still opens correctly (confirmed directly: the `open`
   attribute is set on the <details>, and the panel's display/opacity/
   visibility/size are all correct) but paints entirely behind the
   pinned hero and is invisible and unclickable. Reproduced consistently
   once the pin has engaged; only worked in the brief window right at
   page load before GSAP's first ScrollTrigger refresh applies the pin,
   which is why this looked intermittent rather than a flat "never
   works" until traced with getComputedStyle.

   Fix: a modest z-index, scoped to the open state so it does nothing
   while closed, on the mega-menu content itself - it only needs to
   beat `.stage`'s implicit 0, and 60 leaves headroom under the cursor
   ring (70) and other higher homepage overlays without needing to
   touch base.css or any other page's header. */
header details.mega-menu.cc-mega[open] .global-settings-popup {
  z-index: 60;
}

/* ===================================================================
   2026-08-04 (twenty-fourth pass) - tagline under the wordmark, not
   inline beside it, homepage only
   -------------------------------------------------------------------
   Direct instruction: move "Fine Art for walls, strategy & visuals for
   brands." under the "Charles Chevojon" wordmark on the homepage, to
   match how it already sits on Studio/Perspective. Root cause of the
   difference: sections/header.liquid only wraps the wordmark link and
   the tagline span in an <h1 class="header__heading"> on the index
   template (`{%- if request.page_type == 'index' -%}`) - every other
   page renders the same link and span with no such wrapper. That h1 is
   a shared theme file, so it can't be changed here without touching
   every page; instead this scopes the fix to the wrapped (homepage-
   only) case: `.header__heading` is the selector that only exists on
   index, so it's a safe hook without touching header.liquid itself.
   Stacked block layout, tagline dropped onto its own line under the
   link with a small top margin instead of running inline beside it. */
.header__heading .header__heading-link {
  display: block;
}
.header__heading .cc-header-tagline {
  display: block;
  margin-left: 0;
  margin-top: 6px;
}

/* ===================================================================
   2026-08-04 (twenty-fifth pass) - restore real v/h alternation on the
   "selected" (What ends up on walls) grid
   -------------------------------------------------------------------
   Direct new instruction this session: the grids on the homepage must
   alternate more horizontal and vertical photos. This reverses part of
   the nineteenth/twentieth passes above, which deliberately forced
   every tile to the same uniform shape - that was correct for the
   direction given at the time, but the direction has now changed back
   the other way, so the tile classes in sections/cc-home-v3.liquid
   were reset to alternate v/h/v/h/v/h/v/h and this pass gives that
   alternation something to actually show.

   Specificity note: cc-sections.css (loaded sitewide via theme.liquid)
   carries a deliberately-boosted `body .selected .tile .ph` rule (its
   own seventh-pass-part-b) that forces every tile to a flat 4:5 box -
   that rule exists specifically to out-rank this file regardless of
   load order. To win here without touching that shared file, this
   uses the same `body` prefix plus the .v/.h class for one extra step
   of specificity (0,4,1 vs cc-sections.css's 0,3,1), so it applies
   sitewide-safely without needing an !important war.
   =================================================================== */
body .selected .tile.v .ph { aspect-ratio: 2 / 3; }
body .selected .tile.h .ph { aspect-ratio: 3 / 2; }

/* ===================================================================
   2026-08-04 (twenty-sixth pass) - real V, H, V rhythm on the
   "selected" (What ends up on walls) grid
   -------------------------------------------------------------------
   Direct feedback on the twenty-fifth pass: alternating v/h/v/h at one
   grid column each made every horizontal photo look squeezed and small
   next to the verticals, not "fitting between" them. Fix: the .h tile
   in the middle of each V,H,V trio (see the reordered tiles and the
   new liquid comment in sections/cc-home-v3.liquid) now spans two grid
   columns instead of one, so a V,H,V trio is exactly 1+2+1 = 4 column
   units - a clean, gapless full row at the >=1200px tier where the
   grid is 4 columns wide.

   Sizing the .h box: rather than hardcoding a second fixed aspect
   ratio (which would either mismatch the row height set by the .v
   tiles or need fragile column-width math), the .v tiles keep their
   fixed 2:3 aspect-ratio from the twenty-fifth pass above - that's
   what establishes each row's height, since CSS grid auto-sizes a row
   to its tallest item. The .h tile's .ph then drops its fixed aspect
   ratio and instead flexes to fill whatever vertical space is left in
   its (grid-stretched) tile after the title/subtitle/price text block,
   via flex:1 on a flex-column tile - landing it at effectively the
   same photo height as its two .v neighbours, with object-fit:cover
   doing the crop. The width is already fixed by the 2-column span, so
   the resulting aspect ratio is whatever the row's real height makes
   it - "sized by the two verticals around it" per direct instruction,
   not a guessed ratio.

   grid-auto-flow:dense keeps this looking right at the narrower
   750-1199px tier too (2 columns): a .h tile can't fit a 2-column span
   next to a lone .v with only one column-unit of a 2-column row left,
   so without dense flow it would leave an empty gap. Dense flow pulls
   a later .v into that gap instead, which pairs verticals two-up
   between full-width horizontals - not the same 4-col trio look as
   desktop, but no dead space and the horizontal/vertical alternation
   still reads. Below 750px the grid drops to a single column, so the
   span numbers stop mattering (everything is full width, stacked).

   Specificity/override notes: the twentieth pass's
   `.selected .tile.h, .selected .tile.v { grid-column: span 1
   !important; ... }` needs matching !important to override, since a
   later same-specificity rule without !important loses to an earlier
   one that has it. The .h .ph aspect-ratio override right above (this
   same twenty-fifth pass) is not !important, so this pass's `body
   .selected .tile.h .ph` rule at the same specificity just needs to
   come later in the file to win, which it does here.
   =================================================================== */
.selected .grid { grid-auto-flow: dense; }
.selected .tile.v { grid-column: span 1 !important; grid-row: span 1 !important; }
.selected .tile.h { grid-column: span 2 !important; grid-row: span 1 !important; }
body .selected .tile.h .ph { aspect-ratio: auto; flex: 1; }
body .selected .tile.h .ph img { height: 100%; }
@media (max-width: 749px) {
  .selected .tile.h { grid-column: span 1 !important; }
}

/* ===================================================================
   2026-08-04 (twenty-seventh pass) - stop cropping the selected grid
   photos, no exceptions
   -------------------------------------------------------------------
   Direct instruction: every photo in "What ends up on walls" must show
   its real, original aspect ratio - no cropping, no distortion, no
   alteration of any kind. The twenty-fifth/twenty-sixth passes above
   both used object-fit:cover inside a box with a forced aspect-ratio
   (2:3 for .v, or the row's stretched height for .h) - cover never
   distorts, but it does crop whatever doesn't fit the box, which is
   exactly what's no longer allowed.

   Fix: stop forcing a box shape entirely. .ph drops its aspect-ratio
   and its flex-stretch (both were only there to make a uniform crop
   target), and the <img> itself goes back to plain width:100%;
   height:auto - i.e. it renders at its own real, native aspect ratio
   at whatever the grid column width is. Nothing is cropped, letterboxed,
   or stretched. The V,H,V column-span rhythm from the twenty-sixth pass
   is untouched (still 1 col / 2 col / 1 col) - that's a positional
   layout choice, not a crop, so it stays; what changes is that each
   tile's height now simply follows its own photo instead of being
   forced to match its row-mates. Rows may end up visually uneven in
   height as a result - that is the accepted trade-off for "no
   cropping, no alteration", not a bug to fix with a forced box again.

   object-fit:contain is kept as a no-op safety net (it only matters if
   height:auto ever fails to size the box exactly to the image, e.g. a
   slow-loading image before its intrinsic size is known) - it can never
   crop, only pad, so it does not conflict with the "no cropping" rule.
   Specificity matches the passes being overridden (`body .selected
   .tile.v .ph` / `.h .ph`, both 0,4,1) and this block loads later in
   the file, so it wins without an !important war.
   =================================================================== */
body .selected .tile .ph,
body .selected .tile.v .ph,
body .selected .tile.h .ph {
  aspect-ratio: auto;
  flex: none;
  height: auto;
}
body .selected .tile .ph img,
body .selected .tile.v .ph img,
body .selected .tile.h .ph img {
  width: 100%;
  height: auto;
  object-fit: contain;
}

/* ===================================================================
   2026-08-05 (twenty-eighth pass) - fan strips and statement block no
   longer paint over the site header
   -------------------------------------------------------------------
   Reported: the print/paper strips in Block 2 scroll up over the
   header menu bar instead of passing underneath it.

   Cause, confirmed in the source rather than guessed: cc-v3-home.js
   sets an inline `zIndex: 10 + i` on every .strip (gsap.set in the
   BLOCK 2 fan reveal, so 10/11/12). Neither .fan nor .fanstage
   establishes a stacking context, so those values are not local to the
   section - they resolve against the ROOT stacking context, the same
   one Dawn's header group lives in. Dawn puts the header at
   `.section-header.shopify-section-group-header-group { z-index: 3 }`
   and `.shopify-section-group-header-group { z-index: 4 }` (base.css,
   ~line 2313). 10-12 beats 3-4, so the strips win and paint on top.

   .fanstage's `overflow:hidden` does not help: it clips the strips to
   the section box, but the section box itself scrolls underneath a
   sticky header, and clipping has no say in paint order.

   The same flaw applies to .blk, which the sixth pass above gave
   `z-index: 30` so the "No stock. No batch." text would survive being
   pulled up into the fan's tail by .blockgap's negative margin. 30
   also beats the header, so that block covers the menu bar too.

   Fix, two independent halves so neither is load-bearing on its own:

   1. `isolation: isolate` on .fan makes the section its own stacking
      context, so the GSAP inline 10-12 becomes local to the fan and
      can never again be compared against the header - this is the
      actual root cause and it stops the whole class of bug, including
      any future value the JS might set.
   2. Raise the header group above every z-index the homepage uses.
      This keeps the sixth pass's .blk:30 working exactly as intended
      (still above the strips, so the statement text stays readable)
      while putting the header out of its reach. 50 is deliberate: it
      clears .blk's 30 with headroom, and stays below the discount
      popup's 60 (see the popup pass above) so the popup still covers
      the header when open, as it should.

   This file only loads on the homepage section, so half 2 is scoped to
   the homepage and cannot affect the header anywhere else in the theme.
   =================================================================== */
.fan {
  position: relative;
  z-index: 0;
  isolation: isolate;
}
.section-header.shopify-section-group-header-group,
.shopify-section-group-header-group {
  z-index: 50;
}

/* ===================================================================
   2026-08-05 (twenty-ninth pass) - "What ends up on walls": the photos
   were never rendering at all
   -------------------------------------------------------------------
   RE-DIAGNOSIS. Every pass from the nineteenth onward argued about
   which shape the tiles should be. That was the wrong question: the
   grid looked wrong because there were no photographs in it.

   Measured on the deployed draft, not inferred: all eight
   `.selected .tile img` reported `complete: false` and
   `naturalWidth: 0`, and every `.ph` box measured exactly 0px tall.
   The grid was three rows of ~133px containing nothing but the title,
   subtitle and price text.

   The deadlock, and why the previous pass caused it:

   1. The twenty-seventh pass (immediately above) set
      `.ph { aspect-ratio: auto; height: auto; flex: none }` and
      `img { height: auto }` to guarantee photos are never cropped.
   2. That leaves the box with no reserved height of its own - its
      height is derived entirely from the image's intrinsic height.
   3. Every one of those images carries `loading="lazy"`.
   4. An unloaded lazy image has an intrinsic height of 0, so the box
      collapses to 0, so all eight tiles collapse into a sliver.
   5. A sliver never meaningfully enters the viewport, so the lazy
      loader never fetches the images, so they never gain an intrinsic
      height, so the box never expands. Nothing ever breaks the loop.

   Proof: forcing `loading="eager"` on the eight images in the live
   page made all eight load immediately (780x1170, 1100x733, 1400x933,
   900x1350, 1100x733, 900x600, 900x1350, 1400x933) and the first .ph
   went from 0px to 336px tall. The assets were never missing or 404 -
   they were simply never requested.

   Reserving height is therefore not a stylistic choice here, it is
   what makes lazy-loaded images load at all.

   Second, independent bug found in the same measurement: two tiles
   were wearing the wrong orientation class. Val d'Isere (1400x933)
   and Golden Afternoon / Hollywood (900x600) are both landscape
   photographs but were classed `.v`. Under `object-fit: contain` a
   landscape photo in a portrait box letterboxes into thick grey bands
   down both sides, which is a large part of what "looks wrong" even
   once the images load. Both are now `.h` in the section markup.

   The fix keeps the no-cropping rule intact:

   - `.ph` gets a fixed height, identical for every tile, so all eight
     boxes reserve space before their image loads (killing the
     deadlock) AND every photo baseline, title and price lines up
     across a row. `object-fit: contain` still means no photograph is
     ever cropped.
   - The box background is transparent rather than the old #cfcabf, so
     the small amount of padding `contain` leaves around a photo whose
     ratio doesn't exactly match its cell is invisible instead of
     reading as a grey border.
   - Landscapes span two columns and portraits one, which is the
     "landscapes fit between the portraits instead of being squeezed
     into a single column" direction from the third pass.

   Tiling: 3 portraits + 5 landscapes at 1 and 2 columns is 13 column-
   units, which cannot fill rows of 4 evenly no matter how it is
   ordered - that is why every previous attempt left a hole or a
   stranded tile in the last row. Resolved by giving exactly one
   landscape (Hollywood, the smallest source file at 900x600) a single-
   column `.hs` cell, which brings the total to 12 units and lets the
   authored order tile perfectly:

     row 1   Bahia (v) | Rome (h, 2 cols) | Fingal (v)
     row 2   Val d'Isere (h, 2 cols) | Umbrellas (h, 2 cols)
     row 3   Colosseum (v) | Hollywood (h.hs, 1 col) | Opera (h, 2 cols)

   Because the order now tiles exactly, `grid-auto-flow: dense` is
   switched back to `row`. Dense was reordering the tiles to plug gaps,
   which is what destroyed the deliberate V/H/V rhythm the third pass
   authored - the markup order and the rendered order were never the
   same.
   =================================================================== */
.selected .grid {
  grid-template-columns: repeat(4, 1fr);
  grid-auto-flow: row;
  align-items: start;
}
.selected .tile.v { grid-column: span 1 !important; grid-row: span 1 !important; }
.selected .tile.h { grid-column: span 2 !important; grid-row: span 1 !important; }
.selected .tile.h.hs { grid-column: span 1 !important; }

body .selected .tile .ph,
body .selected .tile.v .ph,
body .selected .tile.h .ph {
  aspect-ratio: auto;
  flex: none;
  height: clamp(200px, 21vw, 340px);
  background: transparent;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}
body .selected .tile .ph img,
body .selected .tile.v .ph img,
body .selected .tile.h .ph img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/* Two columns: spans collapse to one cell each, so the 12-unit layout
   above is irrelevant and every tile simply takes a cell. Height stays
   uniform and stays explicit, so the lazy-load deadlock cannot return. */
@media (max-width: 989px) {
  .selected .grid { grid-template-columns: repeat(2, 1fr); }
  .selected .tile.v,
  .selected .tile.h,
  .selected .tile.h.hs { grid-column: span 1 !important; }
  body .selected .tile .ph,
  body .selected .tile.v .ph,
  body .selected .tile.h .ph { height: clamp(150px, 30vw, 280px); }
}

/* Single column: each tile is its own row, so nothing needs to line up
   and each photo can take its own true shape. aspect-ratio (rather than
   a fixed height) still reserves the box before the image loads. */
@media (max-width: 680px) {
  .selected .grid { grid-template-columns: 1fr; }
  body .selected .tile .ph,
  body .selected .tile.h .ph { height: auto; aspect-ratio: 3 / 2; }
  body .selected .tile.v .ph { height: auto; aspect-ratio: 2 / 3; }
}

/* ===================================================================
   2026-08-12: scoped accent-script test.
   This is now the REAL "Tempting" typeface (RGB Studio), loaded via a
   PERSONAL-USE-ONLY demo file at the client's explicit request, for
   private preview/evaluation ONLY (see layout/theme.liquid for the
   loader and licensing note). DO NOT PUBLISH THIS THEME LIVE with this
   font in place - before going live, the client must buy the commercial
   license (https://crmrkt.com/0P8oMy) and swap in the licensed webfont.
   Sacramento was the fallback here until 8 Sep 2026. Tempting is now
   self-hosted and preloaded with font-display: block, so no fallback
   script renders at all and the extra Google request was dropped.
   in case the Tempting FontFace ever fails to load. Applied to exact
   words/phrases only (wordmark "Chevojon", tagline "Fine art" / "strategy
   & visuals", hero sub "Distance is a choice.", stageline "Closer" and
   "Everything below was made at this distance.") - everything else on
   the page stays default Switzer.
   =================================================================== */
.cc-accent-script{
  font-family:'Tempting',cursive;
  font-weight:400;
  font-style:normal;
}
/* Script faces render visually smaller than a sans at the same font-size,
   so nudge legibility up slightly in the two smallest contexts it's used. */
.cc-header-tagline .cc-accent-script{font-size:1.15em;}
.stagesub.cc-accent-script{font-size:1.1em;}

/* 2026-08-13: header tagline font test, per direct feedback - keep the
   tagline's existing mixed-weight split ("Fine art" regular / "strategy &
   visuals" bold via .cc-tagline-mixed .wd-lg in cc-nav-clarity.css)
   untouched, but swap the script typeface for Switzer here as a test.
   Scoped to .cc-header-tagline only, so the wordmark "Chevojon" and the
   hero captions (.sub, .stageline h2/.stagesub) keep using the script
   face via the shared .cc-accent-script rule above - only this element
   overrides font-family back to the body font. Also resets the +1.15em
   size bump above, since that was compensating for the script face
   reading smaller, which no longer applies once this renders in Switzer. */
.cc-header-tagline .cc-accent-script{
  font-family:"Switzer","Helvetica Neue",Arial,sans-serif;
  font-size:1em;
}

/* 2026-08-13: slight rounded corners on the homepage's photo grids, per
   direct feedback ("Grid must have slight rounded corner for the
   photos"). Two grids on this page show photos in a repeating card
   layout: the "What ends up on walls" selected-prints grid (.selected
   .tile .ph, already carried a chunky 1.4rem radius from the nineteenth
   pass - dialed back here to a slighter 10px) and the by-place/by-look
   range shelf (.rcard, had no radius at all before this). Declared at
   the end of the file with the same body-prefixed selector used by the
   later cascade passes above so it wins on specificity without an
   !important war; overflow:hidden is required alongside border-radius
   on any box whose child img fills it edge-to-edge, or the image's
   square corners show through. */
body .selected .tile .ph,
body .selected .tile.v .ph,
body .selected .tile.h .ph {
  border-radius: 10px;
  overflow: hidden;
}
.rcard {
  border-radius: 10px;
  overflow: hidden;
}

/* 2026-08-13: restore Tempting on "Closer" and the sl4 stagesub caption
   ("Everything below was made at this distance."). Both already carry
   .cc-accent-script (which sets font-family: Tempting/Sacramento), but
   cc-v3-home.css's blanket .stageline h2 / .stageline .stagesub rules
   (added 2026-08-12 to fix a different, unrelated Switzer-inheritance
   bug on "Arrived.") have equal-or-higher specificity and were winning
   the cascade, so these two elements were silently rendering in Switzer
   instead of the intended script face. These two new selectors add one
   extra class each over the originals specifically so they win here,
   without touching "Arrived." (h2, no .cc-accent-script class) which
   correctly stays Switzer. */
.stageline h2.cc-accent-script,
.stageline .stagesub.cc-accent-script {
  font-family: 'Tempting', cursive;
}

/* ===================================================================
   2026-08-19 - prev/next arrows for the By place / By look shelf
   -------------------------------------------------------------------
   Direct feedback: this row was "missing a arrow to navigate in by
   place by look" - the product carousel above it already has one
   (snippets/cc-product-carousel.liquid, .ccar__nav/.ccar__arrow,
   styled in cc-carousel.css). New markup in sections/cc-home-v3.liquid
   wraps the existing .rangetabs in a .rangehead container and adds a
   .rangenav pair of buttons reusing .ccar__arrow directly for the
   identical look, rather than inventing a second arrow style.

   .rangehead only needs position:relative so .rangenav can anchor to
   its right edge without taking any of the existing .rangetabs rules
   (display, gap, centering) along for the ride - .rangetabs still
   centers its two tabs exactly as before, this just overlays the
   arrows at the row's right edge on top of that, same corner the
   product carousel puts its own arrows in.

   Hidden below the same 989px breakpoint the product carousel's own
   .ccar__nav already hides at, for the same reason: touch already
   scrolls the strip natively below that width, so a duplicate tap
   target there is clutter, not an affordance. */
.rangehead {
  position: relative;
}
.rangenav {
  display: flex;
  gap: 0.8rem;
  position: absolute;
  right: 0;
  top: 50%;
  transform: translateY(-50%);
}
@media (max-width: 989px) {
  .rangenav {
    display: none;
  }
}

/* ===================================================================
   2026-08-21 - mobile fixes from real-device (iPhone) screenshots
   -------------------------------------------------------------------
   Three screenshots of the draft theme on an actual phone surfaced two
   separate issues; both fixed here, not in the base sheet, per this
   file's usual override-only convention. */

/* (1) Black bars on Take one home (.cta-scene) and The start of a
   conversation (.portal) on mobile. Root cause: cc-v3-home.css's own
   @media(max-width:680px) block sets .cta-scene .bg img and .portal
   .pbg to object-fit:contain (undocumented, no dated comment there),
   while both sections sit on a solid var(--ink) background underneath
   - .cta-scene .bg{background:var(--ink)} in that same mobile block,
   .portal{background:var(--ink)} at the base level. On a narrow
   viewport, "contain" letterboxes the photo instead of filling the
   frame, and the dark background shows through as bars above/below -
   exactly what both screenshots show. Overridden back to the sitewide
   default (object-fit:cover, same as the global img rule at the top of
   cc-v3-home.css) so the photo fills the mobile frame edge to edge
   again, same as it already does on desktop. */
@media (max-width: 680px) {
  .cta-scene .bg img,
  .portal .pbg {
    object-fit: cover;
  }
}

/* (2) Client work (.studiocarousel) reported as not scrolling
   laterally on mobile. See the dated comment in
   sections/cc-home-v3.liquid directly above .studiocarousel for the
   full diagnosis - summary: scroll-snap-type:x mandatory (base sheet)
   likely snaps a partial swipe straight back to the start. Softened to
   proximity here so a swipe that doesn't clear a snap point just stays
   put instead of springing back, and paired with a guaranteed-to-work
   arrow pair (.studionav, styled below) as a fallback that doesn't
   depend on touch-scroll behaviour at all. */
.studiocarousel {
  scroll-snap-type: x proximity;
}

/* .studionav: prev/next arrows overlaid on the carousel itself, using
   .ccar__arrow (cc-carousel.css) for the same circular look as the
   product carousel and the Range shelf's own arrows, styled here with
   a frosted dark backing so they stay legible sitting on top of a
   photo instead of on a plain background. .studiovisual is already
   position:relative (cc-v3-home.css), so this anchors to the carousel
   itself with no extra wrapper needed. Deliberately visible at every
   breakpoint - see the liquid comment for why this one needs to stay
   on for mobile/tablet unlike .ccar__nav's own hide-below-989px rule. */
.studionav {
  position: absolute;
  top: 50%;
  left: 0;
  right: 0;
  display: flex;
  justify-content: space-between;
  padding: 0 12px;
  transform: translateY(-50%);
  pointer-events: none;
  z-index: 4;
}
/* 2026-08-28 (Round 37): switched to the WHITE glass variant of the
   two-colour button system, per the direct instruction to apply one
   button treatment across the whole site "with two colours, black and
   white, according to the background". The rule is: black glass on the
   paper background, white glass on photography and dark scenes. These
   arrows sit directly on top of the carousel's photos, so they take
   the white variant - matching .cc-studio-linksrow__cta (cc-studio.css)
   and the .liquid buttons over the CTA photo (cc-v3-home.css), which
   are the site's other on-photo controls. Blur/saturate raised to the
   same blur(16px) saturate(1.5) used everywhere else in this pass so
   all glass surfaces on the site now share one recipe. */
/* 2026-08-28 (Round 37, second pass): fill raised and a rim added after
   a live look at the first pass. White at 42% was correct against the
   carousel photos, but these arrows are anchored to the left and right
   edges of .studiovisual, where they can overhang the section's own
   near-white background (#FCFCFA) - and white-on-white at 42% with a
   white border read as a barely-visible pale disc with only the chevron
   glyph legible. A control the visitor cannot find is worse than one
   that is slightly less pure.
   Fixed so it reads on BOTH grounds without abandoning the white glass
   variant: fill up to 62%, and the border swapped from white to a soft
   ink rim, which draws the edge against a light background while
   staying invisible against a dark photo. The drop shadow does the same
   job from the other side. Blur/saturate unchanged, so it is still the
   same glass recipe as every other button in this pass. */
.studionav .ccar__arrow {
  pointer-events: auto;
  background: rgba(255, 255, 255, 0.62);
  backdrop-filter: blur(16px) saturate(1.5);
  -webkit-backdrop-filter: blur(16px) saturate(1.5);
  border-color: rgba(17, 17, 15, 0.14);
  color: var(--ink, #11110F);
  box-shadow: 0 6px 18px -8px rgba(17, 17, 15, 0.32);
}
.studionav .ccar__arrow:hover,
.studionav .ccar__arrow:focus-visible {
  background: rgba(255, 255, 255, 0.82);
  border-color: rgba(17, 17, 15, 0.2);
}

/* ===================================================================
   2026-08-21: new statement section ("Fine Art for walls / Strategy
   and visuals for brands"), added directly after the hero. Structure
   mirrors .cta-scene (BLOCK 9, cc-v3-home.css) - .bg wraps a full-
   bleed photo, an absolutely-positioned .cc-statement__inner sits on
   top, centred. Kept in this override file per this project's own
   convention for new homepage components (see .cc-float-tagline
   above), rather than edited into the original cc-v3-home.css pass.

   Text is real markup, not pixels baked into the photo - see this
   section's own comment in sections/cc-home-v3.liquid. Script spans
   reuse the sitewide .cc-accent-script (Tempting, defined in
   cc-nav-clarity.css); caps spans use the same Switzer Semibold
   treatment just adopted today for .cc-tb-caps and
   .cc-float-tagline__caps, so all three tagline-style components on
   the site now share one font pairing.

   This photo is a bright, warm golden-hour scene (unlike .cta-scene's
   dark photo + white text), so this uses dark ink-coloured text with
   a soft light text-shadow for separation, plus a faint white wash
   over the image rather than the dark scrim .cta-scene uses.
   object-position is tuned toward the window/ocean band (up and
   slightly right of centre) so the laptop and horizon stay in frame
   after cover-cropping - the source photo is a very wide 2.36:1
   panorama, so a narrow mobile viewport crops it hard either way.
   Mobile height is deliberately shorter than .cta-scene's own mobile
   height (62vh vs 74vh) because a shorter box actually shows MORE of
   this particular image's width under cover-crop math, not less - the
   opposite of the usual instinct. Not yet checked against a real
   device, same as everything else from today's pass; object-position
   and the section height are the two values most likely to need a
   nudge once seen on an actual phone. */
/* 2026-09-09 (breathability pass): the statement gets silence.

   "Fine Art for walls / Strategy and Visuals for brands" is the one line that
   tells a stranger what this business is, and it sat at 4,577px with 0px of
   margin, butted against the section above and below. It is also the section
   chosen to carry the audience split, so it needs to read as a deliberate
   pause rather than another full-bleed slide going past.

   Margin, not padding: the section is an aspect-ratio image box, so padding
   would eat into the photograph. Margin puts the air outside it. */
/* 2026-09-09 (corrected same day): the top margin is gone and the bottom one
   is halved.

   The first attempt put 144px of paper above AND below this section to give
   the positioning line "silence". That was wrong for this element: it is a
   FULL-BLEED photograph, so paper on both sides does not read as a pause, it
   reads as a bleed that failed - the photo stops looking deliberate and
   starts looking mis-sized. The pause the statement needs comes from the fork
   below it, which is paper anyway. */
.cc-statement { margin-block: 0 clamp(36px, 4.5vw, 64px); }

/* Portal capped below full viewport so it reads as a passage rather than a
   destination. It keeps every element it had - this only stops it demanding
   a whole screen. It has overflow:hidden already, so the cap crops the scene
   cleanly (about 37px at 1440x900) rather than spilling.

   The statement is deliberately NOT capped: it is the fork, so it is the one
   section that should hold the screen.

   Fan is not capped either, and the cap that was briefly here has been
   removed: measured, Fan is 666px at a 900px viewport - 0.74 of a screen -
   so it was never one of the oversized sections, and it has overflow:visible,
   which means a cap that DID bind on a shorter viewport would have spilled
   its strips over the next section instead of clipping them. */
.portal .pstage2 { max-height: 82vh; }

.cc-statement { position: relative; aspect-ratio: 6336 / 2688; height: auto; overflow: hidden; display: flex; align-items: center; justify-content: center; text-align: center; }
.cc-statement .bg { position: absolute; inset: -4%; }
.cc-statement .bg img { width: 100%; height: 100%; object-fit: cover; object-position: 50% 50%; }
/* 2026-08-25 (second pass, corrects the first pass above): direct
   follow-up after seeing the halved-white-wash edit live - "The text
   must be fully visible. The opacity must be on the photo, not on the
   text. Use the two references as examples." The two named references
   are .cta-scene ("Take one home.") and .portal ("The start of a
   conversation.") - checked both directly in assets/cc-v3-home.css.
   Neither ever reduces its TEXT'S opacity: .cta-inner/.portal .ptype
   are plain var(--paper), always fully opaque. Both instead darken the
   PHOTO itself - .cta-scene .bg::after uses a dark ink gradient,
   linear-gradient(180deg,rgba(17,17,15,.34),rgba(17,17,15,.52)); .portal
   takes the other valid approach, opacity:.42 on .pbg over the section's
   own var(--ink) background. This block's first pass above was still a
   WHITE wash, just a lighter one - exactly backwards per this feedback,
   since a white wash on a white-brightened photo works against text
   contrast rather than for it, regardless of how faint it's made.
   Replaced outright with .cta-scene's own dark gradient, verbatim (this
   photo is the brighter of the two - the CTA photo is already fairly
   moderate - so matching rather than exceeding its darkening keeps this
   block from reading heavier than the reference it's matching). The
   text-side opacity cap is fixed separately, in the GSAP tween that
   controls it (see assets/cc-v3-home.js and
   sections/cc-studio-statement.liquid) - not this rule's job either
   way, called out here only so both halves of the fix are cross-
   referenced from one place. */
/* 2026-08-25 (third pass): direct follow-up after seeing the dark
   gradient above live - "the opacity from the photo... is good. You
   can make it a bit lighter. It's tiny bit too opaque." Both stops
   trimmed down modestly (not re-architected - the gradient itself, and
   the reasoning in the comment above for using .cta-scene's own dark-
   gradient approach rather than a white wash, both stay exactly as
   they are): 0.34->0.28 and 0.52->0.44, roughly an 18% cut on each
   stop, matching "a little bit" rather than a bigger swing. */
/* 2026-08-26 (Round 31): reverted from a dark ink gradient (built to
   support the round-22 white-text treatment) back to a light,
   paper-tinted wash - dark ink text (restored above) needs the photo
   LIGHTENED for contrast, not darkened, the same logic the original
   pre-round-22 wash used (see this section's own history: a white
   wash was originally tuned here 2026-08-21, before white text +
   dark scrim replaced it round 22). Kept the same two-stop, top-lighter/
   bottom-heavier shape rather than a flat single value, since the
   caption sits in the lower half of the frame and needs the most
   contrast there. */
/* 2026-08-27 (Round 33): per direct instruction ("opacity must be toward
   darker and lighter to get the white text popping out"), reverted from
   the light paper-tinted wash (Round 31) back to a dark scrim - white
   text needs the photo DARKENED for contrast, not lightened. Restored
   the exact values this block already proved out on 2026-08-25 (third
   pass) before Round 31's rollback undid them: dark ink gradient,
   0.28/0.44 two-stop. */
/* 2026-09-11: scrim deepened through the middle band, because the tagline
   now zooms (see ccStatementFill in cc-v3-home.js). At rest the type was
   small and sat over the dark timber and desk on the left of the frame,
   which the old 0.28 -> 0.44 top-to-bottom ramp covered comfortably. At
   full zoom it is 2.4x wider on a phone and 2.5x on a laptop, so
   "Strategy and Visuals" now runs straight across the brightest thing in
   the photograph - the window and the lit ocean behind it - and the thin
   cursive washes out against it. Visible in a phone recording of the
   draft.

   Fixed on the photo, not on the type, which is the direct instruction
   this block already carries from 2026-08-25: "the text need to be
   brighter and full white, and the opacity only on a photo." The text
   stays flat #fff with no shadow, exactly as .cta-inner and
   .portal .ptype do - see the long note further down for why a dark halo
   on cursive reads duller, not brighter.

   Three stops rather than two so the darkening lands where the type
   actually is. The middle is where both rows sit at full zoom; the top
   and bottom edges stay close to their old values so the desk, the chair
   and the sky keep their depth and the block does not read as a grey
   panel.

   2026-09-11, later the same day: LIGHTENED, and past where it started.
   Direct instruction after seeing the above on a phone - "reduce the
   opacity from desk block too, we can't see much the creatives as it's
   bit too dark." 0.30/0.52/0.48 -> 0.18/0.34/0.32, so the middle now
   sits below even the pre-zoom 0.28 -> 0.44 ramp this block carried
   before today.

   Recorded rather than quietly rewritten, because it is a real reversal
   and the two goals genuinely pull against each other: the darkening
   above was my own call to protect white cursive crossing the brightest
   part of the photograph, and the instruction is to favour the
   photograph instead. The instruction wins. What makes that affordable
   is the other change of the same day - the block now carries one row
   instead of two, so the type covers 30% of the block's height rather
   than 72% and simply meets less of the bright window than the version
   that prompted the darkening. If "Strategy and Visuals" ever reads
   washed out again, this ramp is the dial, not the type: the standing
   instruction on this block is that the text stays fully white with no
   shadow and the opacity lives on the photo. */
.cc-statement .bg::after { content: ""; position: absolute; inset: 0; background: linear-gradient(180deg, rgba(17, 17, 15, 0.18) 0%, rgba(17, 17, 15, 0.34) 45%, rgba(17, 17, 15, 0.32) 100%); }
/* 2026-08-23 (seventh pass, desktop): a real desktop-browser screenshot
   showed "Strategy and Visuals for brands" crossing two window mullions
   at left:56% - the same class of problem the third pass fixed on
   mobile below, just never carried over to this base/desktop rule,
   which had never been checked against an actual browser render.
   Pixel-measured the screenshot directly (accounting for its 2x device
   pixel ratio): the clear glass pane runs from roughly 25.3% to 58.7%
   of the photo's width - matching the mobile pane found in the third
   pass (25.5%-59.3%) almost exactly, since it's the same photo. Also
   matches the very first pass's own note above (pane centred "at
   roughly 41% across the section width") - so 56% was a drift away
   from an already-correct earlier measurement, introduced somewhere in
   the second pass below and never re-checked. Moved back to left:42%,
   the pane's centre, which puts the current desktop text width inside
   the pane with roughly 23px of clearance on each side. No font-size
   change needed on desktop - this was a pure positioning miss, not a
   sizing one. */
/* 2026-08-26 (Round 32): repositioned against the actual Higgsfield
   reference mockup ("text do not used just reference -
   hf_20260820_195630...(2).jpg", Google Drive mockup folder) - the
   original source this whole block was meant to match, never actually
   checked pixel-for-pixel until now. Measured directly off that file:
   the caption sits centred at roughly left 44% / top 38% of the photo's
   own frame, over the bright window/glass area between the TV and the
   laptop - NOT top 20%, which has had the caption crowded up near the
   top edge/wall this entire time regardless of how many other things
   got tuned around it. This is the actual root of the standing
   "alignment isn't right" feedback - a straight measurement correction
   against the source file, not another visual guess. */
/* 2026-08-27 (Round 34): raised per direct instruction ("bring it up,
   match with the same placement on the home page") - the round 33
   caption resize (2 lines -> 4, bigger) made the box noticeably taller,
   and centring a taller box on the same 38% point pushed its bottom
   edge down into the laptop screen, worst on Studio (see below). Moved
   up to 30% on both axes/pages. */
/* 2026-08-28 (Round 37): repositioned by real pixel measurement of the
   photograph itself, per the direct instruction: "centred in the left
   window, not touching any frames, ceiling, screen, or laptop."
   Measured live in the browser, reading raw image luminance off a
   canvas at 0.25% steps rather than eyeballing a screenshot. Note the
   photo is NOT fully visible: .cc-statement .bg{inset:-4%} plus
   object-fit:cover inflates it to 108% and centre-crops it, so the
   visible frame is the central 92.593% of the file. All figures below
   are percentages of the SECTION box, already converted.
     - left window frame post (solid dark mullion)  x 22.5-25.5%
     - LEFT WINDOW PANE, one clean opening          x 26.0-59.0%
     - paired sliding-door mullions                 x 59.25-61.5%
     - ceiling soffit underside, clear from         y 17.0%
     - top of the laptop screen bezel               y 52.6%
   Clear rectangle: x 26.0-59.0%, y 17.0-52.5%. Its centre is
   (42.5%, 34.75%). `top` is set to 36%, not 34.75%, because the
   Tempting script's swashes overflow their own line box by ~3.2%, so
   the box centre this rule positions is NOT the ink centre - on the
   homepage the ink sits ~1.67% above the box centre, which the extra
   1.25% here corrects for. See the font-size note on
   .cc-statement__row .cc-accent-script below: the sizes had to come
   down as well, because no left/top value can satisfy this
   instruction while the text is wider than the pane. */
/* 2026-09-12: centred, per "the strategy block tagline must be center in the
   middle at the same level, do not drop or put it up". Was left:42.5%/top:36%,
   pixel-measured in August against the desk in the photograph. Now 50/50 -
   dead centre of the block on both axes, with translate(-50%,-50%) already
   doing the centring, so the lockup sits on the middle line and does not ride
   high or low. ccStatementFill() in cc-v3-home.js measures the anchor at
   runtime rather than assuming it, so the zoom re-fits to the new centre with
   no second change - and being centred actually gives it MORE room, since the
   nearer edge is no longer the left one. */
/* 2026-09-12, third position pass: top 50% -> 34%.
   "It must be sitting above the laptop." Dead centre put the lockup
   across the laptop screen and the desk, which is the busiest, brightest
   part of the photograph and the worst place for white script. 34% sits
   it over the glass and the horizon instead - above the laptop, still on
   the horizontal centre line. Horizontal stays 50%: the instruction was
   about height, and centred is also what gives the zoom the most room. */
.cc-statement__inner { position: absolute; left: 50%; top: 34%; transform: translate(-50%, -50%); z-index: 2; padding: 0 24px; }
/* 2026-08-21 (annotated-screenshot feedback, round 1): "the text must
   blend with motion above the laptop in the horizon in the red area" -
   moved the block up into the sky band with margin-top: -80px. Direct
   follow-up after seeing that on the real draft preview: "I don't like
   it much it must be in the empty space no overlaying any black frame,
   must be fully readable and visible." The sky band itself is crossed
   by several dark vertical window mullions (the sliding glass door
   frame structure) - moving the text up alone wasn't enough, since at
   its original width (~870px box, ~565px of actual glyphs on the long
   row) it still spanned across at least one mullion regardless of
   vertical position.
   Re-measured the actual source photo pixel by pixel (canvas sampling,
   not eyeballing): the widest clear, mullion-free, clutter-free pane is
   natural-image x ~700-1502 out of 2560, directly right of the
   concrete wall/TV and above the desk. Converted through this section's
   actual object-fit:cover crop math (bg img renders at object-position:
   56% 42%, inset:-4% around the section) into section-relative
   coordinates: that pane centres at roughly 41% across the section
   width and 35% down its height. margin-right (not a transform, same
   reasoning as margin-top below - GSAP owns transform on this element
   for the scrub tween) shifts the block's horizontal centre from the
   flex-centred default to that 41% point; margin-top does the same on
   the vertical axis. Both values below were calibrated empirically
   in-browser (inject a trial margin, measure the resulting box centre
   via getBoundingClientRect, adjust), not computed blind - a live
   screenshot at these values shows the full text block sitting cleanly
   inside the clear pane with real margin on both sides, no mullion
   crossed, no ceiling or desk-clutter contact. Sized down alongside the
   reposition (see the two font-size rules below) since the clear pane
   is materially narrower than the text's original width - fitting
   without crossing a mullion needed both a move and a trim, not a move
   alone. */
/* 2026-08-22 (second pass): the object-fit: contain + letterbox attempt above was superseded the same day by the aspect-ratio: 6336/2688 approach on .cc-statement itself (see that rule) - full photo, no crop, and no letterbox gap, since the box now matches the photo's own ratio exactly. .cc-statement__inner repositioned to left:56%/top:20% (see that rule below .cc-statement__caps) using real pixel measurement against the actual uncropped photo - not the flex-centre fallback this comment used to describe. */
/* 2026-09-05: line-height 1.15 -> 1.32. Measured: the .cc-accent-script span
   rendered with its glyph box starting 4px ABOVE the row's own top edge, so
   Tempting's ascenders and the capital flourishes were being cut, and its
   descenders ran into the caps line below. Same failure already documented on
   .cta-scene h2.mixedtype, where a sub-em line box clipped a script face.
   Safe to grow: the whole text block is ~194px inside an 807px centred flex
   box, so the extra height cannot reach the section's overflow:hidden edge -
   the 33px of scrollHeight overflow on .cc-statement is the background's
   deliberate inset:-4% bleed, not the type. */
.cc-statement__row { margin: 0; line-height: 1.32; }
/* 2026-08-21 (live-preview check, after the draft upload): real-device-
   style verification via a headless preview render caught a bug the
   local diff/checksum discipline couldn't - margin-top: 0.25em here
   was resolving against this element's own (small, inherited) font-
   size, not the ~64px the script text actually renders at, since
   .cc-statement__row itself sets no font-size of its own (only its
   .cc-accent-script/.cc-statement__caps children do). Measured live:
   a 4px gap between the two rows at desktop width, against ~74px line
   boxes - the two rows read as almost touching. Switched to a fixed-
   feeling, viewport-scaled clamp instead of an em value, so it can't
   fall into the same wrong-font-size trap again and still scales
   sensibly from mobile (clamps to a 12px floor) up through the same
   5.2vw curve the script text itself uses at desktop widths. */
.cc-statement__row + .cc-statement__row { margin-top: clamp(12px, 1.8vw, 22px); }
/* 2026-08-24: per direct instruction ("swap the white text"), the caption
   text on the desk-creative block (.cc-statement, used on both the
   homepage's #statement and the Studio page's #statement-studio via the
   .cc-statement--studio modifier) is inverted: white fill, dark glow -
   was dark ink fill with a white glow (needed against the brightened
   photo background, see .bg::after above). Left every other rule here
   untouched - no position, size, or font-size change, per instruction to
   lock in the current start size and position. Because .cc-statement--studio
   only overrides font-weight (see that rule below), this color swap
   applies automatically to both pages. */
/* 2026-08-25 (third pass): direct follow-up - "The text in white must
   be fully white and as bright as the... references sent earlier...
   The text need to be brighter and full white, and the opacity only
   on a photo." Checked both named references directly: .cta-inner and
   .portal .ptype are plain color:var(--paper), no text-shadow at all -
   contrast comes entirely from the dark photo/backdrop behind them
   (see the .bg::after gradient just above, and .portal .pbg's own
   opacity). This block's heavy dark text-shadow below (added back when
   the photo itself wasn't yet darkened - see the pre-2026-08-25
   comment above .cc-statement .bg::after) is exactly what's muddying
   the white against the reference: a large-blur, fairly opaque dark
   halo sitting on top of a thin cursive script reads as duller than a
   flat, shadow-free white fill, even though the color underneath was
   already literal #fff. Now that the photo carries its own dark
   gradient scrim (this block's second pass, directly above), the
   text-shadow's original job is redundant and actively working against
   this feedback. Text-shadow dropped entirely on both spans, matching
   the references' flat-color approach exactly; color stays #fff (not
   switched to var(--paper), which at #F7F5F0 is a touch warmer/less
   bright than pure white - "fully white" reads as the more literal
   ask). */
/* 2026-08-27 (Round 33): text back to white (matching the restored dark
   scrim above - white text needs a darkened photo, not a lightened one),
   and sized up per direct instruction ("caption must be a bit bigger").
   No text-shadow, matching this site's own .cta-inner/.portal .ptype
   reference approach (contrast from the photo scrim alone). */
/* 2026-08-28 (Round 37): sizes trimmed, and this DOES partly reverse
   the ~10% increase made in Round 35 on direct request. Recorded
   honestly rather than quietly: the two instructions are in genuine
   conflict, and the newer one wins. Measured constant on this
   typeface: the widest line renders 10.334px of ink per 1px of
   font-size. The old middle term of 4.6vw therefore made that line a
   fixed 47.5% of the section's width at every viewport below 1239px
   (where the 57px cap stopped binding) - against a window pane that
   is only 33% wide. So at 1440px, the commonest laptop width, the
   text overflowed the mullions on both sides no matter where it was
   positioned. 2.7vw keeps the ink at or under 28% of the section
   width at every size, which leaves real air inside the pane. The
   30px minimum came down to 18px for the same reason: at the 680px
   breakpoint a 30px floor is 45% of the section on its own. */
.cc-statement__row .cc-accent-script { font-size: clamp(18px, 2.7vw, 51px); color: #fff; }
/* 2026-08-27 (Round 33): per direct instruction, the caption goes from
   2 lines to 4 - script phrase and its caps tail each get their own
   line (Tempting / Switzer / Tempting / Switzer), instead of sitting
   inline on the same row. display:block on .cc-statement__caps forces
   that line break (each .cc-statement__row already has both spans as
   its only content, so the caps span starting a new block is enough -
   no markup change needed). margin-left swapped for margin-top since
   the caps span no longer sits beside the script span. Sized up and
   set to white alongside the script text above. */
.cc-statement__caps { display: block; margin-left: 0; margin-top: 0.22em; font-family: var(--font-body, 'Switzer', 'Helvetica Neue', Arial, sans-serif); font-weight: 600; font-size: clamp(7px, 1.1vw, 20px); letter-spacing: 0.1em; text-transform: uppercase; color: #fff; }
@media screen and (max-width: 680px) {
  /* 2026-08-22 (third pass): the second pass above was only checked
     against a JS aspect-ratio number, not a real phone - flagged as
     such at the time. The user sent a real iPhone screenshot of it and
     called it out: the text straddled a window mullion. Measured the
     actual clear glass precisely from that screenshot (dark-pixel
     column/row scanning): the TV sits at roughly x 5-20%/y 28-63%, a
     top frame beam runs y 4.5-8.4% across the whole window, and
     mullions land at approximately x 22.5-25.5%, 59.3-61.6%,
     65.0-65.7%, and 80.6-83.4%. That leaves one wide mullion-free pane,
     x 25.5-59.3% (~34% of the photo width), clear of glass down to
     about y 55% before the desk laptop's screen starts. The old
     left:56% anchor sat almost exactly ON the 59.3% mullion - exactly
     what put "Art" and "and Visuals" across it. Moved
     .cc-statement__inner to a mobile-only left:42%/top:26%, inside that
     pane with margin either side instead of centred on its edge; added
     a 150px max-width and trimmed the shared 24px side padding to 6px
     (24px was sized for the old wider position and was eating into an
     already-tight budget); shrunk the font clamps further so the
     widest wrapped sub-line ("and Visuals") lands under the new
     max-width with room instead of right at it. Better-grounded than
     the last pass - real screenshot, precise pixel measurement - but
     still not a live render on an actual phone, so treat this as a
     second, more informed estimate and confirm on a real device. */
  /* 2026-08-23 (fifth pass): real device screenshot after the fourth
     pass showed two things. One, 9px/5px read as too small - direct
     feedback: "a bit bigger". Two, and more useful: it let me measure
     where this centred, translate(-50%,-50%)-based positioning
     actually lands text on a real phone versus what the CSS values
     alone predict, which the fourth pass's DOM rig (run at desktop
     width in this sandbox, never on an actual phone) could not check.
     Measured directly off that screenshot: with top:34%, the rendered
     text block's vertical centre landed around 52% of the photo's
     height, not 34% - a ~18-point gap between the set value and the
     visible result that a translate(-50%,-50%) box's own height
     (script line-height/ascent metrics on real iOS rendering,
     apparently taller than this sandbox's own measurement of the same
     classes/fonts) accounts for, not a cascade bug - grepped the live
     file for every rule touching .cc-statement__inner and there's
     exactly one mobile override, no conflicting media query fighting
     it. Rather than keep guessing at that offset, switched the
     mechanism: transform is now translate(-50%, 0) - horizontal
     centring only - so top:X% is the block's actual TOP edge, not an
     unpredictable centre point. Set top:20%, clear of the top frame
     beam/ceiling texture (runs to roughly 17-18% of the photo's
     height per the same screenshot). Sized up to script
     clamp(10px,2.8vw,12px) / caps clamp(5.5px,1.5vw,6.5px) - roughly
     30% bigger than the fourth pass - and widened the pane budget
     slightly (132px -> 140px, still inside the ~136px-wide mullion-free
     pane identified in the third pass) so the extra size doesn't force
     more wrapped lines than before. left nudged 42% -> 44%, slightly
     closer to true centre, per "the text must be a bit more center".
     Budget check: top-anchored at 20%, this needs to stay under
     roughly 38% of the photo's height (~65px on a 402px-wide phone) to
     clear the laptop screen, which the same screenshot put at 58-59%.
     Real risk, stated plainly: this sandbox still can't render a true
     mobile viewport, so this is calculated from a screenshot, not
     watched happen live - if the bigger type wraps to more lines than
     expected and runs into the laptop, sizing back down or trimming
     top further are the two easy levers. */
  /* 2026-08-23 (sixth pass): fifth pass's screenshot showed the fix
     working (clear of the laptop) but the row-2 phrase ("Strategy and
     Visuals" + "for brands") was still wrapping to its own extra
     line, because .cc-statement__inner's 140px max-width forced the
     caps span onto a new line whenever the row's combined content
     didn't fit. Direct feedback: it must read as two lines, matching
     .cc-float-tagline elsewhere on this same page, which shows "Fine
     Art for walls" / "Strategy and Visuals for brands" as exactly two
     lines. Fixed the actual structural cause: added
     white-space:nowrap to .cc-statement__row (each row is already its
     own block-level <p> - this just stops the row's own inline
     content from wrapping inside itself) and dropped the 140px
     max-width, matching .cc-float-tagline's approach of no width
     constraint at all - the row's own auto width plus text-align:
     center (inherited from .cc-statement) is what centers it now.
     The real constraint: the mullion-free pane this block sits in is
     only about 136px wide (x 25.5%-59.3% of the photo, per the third
     pass's pixel measurement, cross-checked again this pass and
     consistent). Measured live (real classes/fonts, off-screen clone,
     document.fonts.ready awaited) that row 2 at the fifth pass's
     font size needs ~144px nowrap - wider than the pane, which would
     have reproduced the original mullion-crossing problem from the
     third pass, just with the whole phrase instead of half of it.
     There isn't room to keep the fifth pass's bigger size AND fit
     "Strategy and Visuals for brands" on one line inside this pane -
     sized back down to script clamp(6.8px,1.94vw,7.8px) / caps
     clamp(3.9px,1.07vw,4.3px), measured live at ~123px nowrap, inside
     the 136px pane with roughly 6px clearance each side against the
     TV on the left and the mullion on the right. Left/top/transform
     unchanged (44%/20%/translate(-50%,0)) - only the row's internal
     wrap behaviour and the size needed to honor it changed. Flagged
     to the user: this reads smaller than the fifth pass again, and
     that's a direct trade-off against fitting two full phrases into a
     136px-wide clear pane on this specific photo, not a measurement
     miss. */
  /* 2026-08-26 (Round 32): mobile mirrors the same measured correction
     as desktop above - .cc-statement keeps a fixed aspect-ratio equal to
     the photo's own (6336/2688) at every viewport width, so the
     reference mockup's measured position (left 44%, top 38%) applies
     unchanged on mobile too. Also switched to the same translate(-50%,-50%)
     centring desktop uses, matching the reference's true vertical
     center point instead of anchoring from the block's own top edge. */
  /* 2026-08-28 (Round 37): same clear-rectangle centre as desktop
     (see the base rule's measurement note). top is 34% rather than
     desktop's 36% because at mobile sizes the script's swash overflow
     is a much smaller share of the line box, so less correction is
     needed; 34% is also the shared value that works for the Studio
     instance without a second override. */
  .cc-statement__inner { left: 50%; top: 34%; padding: 0 4px; box-sizing: border-box; transform: translate(-50%, -50%); }
  .cc-statement__row { white-space: nowrap; }
  /* 2026-08-27 (Round 33): sized up per direct instruction - the 4-line
     stack (see the base .cc-statement__caps rule above) means each line
     now holds only one phrase instead of two, so the width budget that
     drove these very small mobile sizes is far less tight than before -
     room to size up without re-crossing the mullion-free pane. */
  /* 2026-08-28 (Round 37): 2.6vw measured as fitting the pane at
     390px with only ~1.8% air each side, and the 10px floor made it
     overflow the pane entirely below ~385px (at 320px the block
     measured wider AND taller than the whole opening). Trimmed to
     2.4vw with an 8px floor, which fits with air at every width from
     320px up. */
  .cc-statement__row .cc-accent-script { font-size: clamp(8px, 2.4vw, 12px); }
  .cc-statement__caps { font-size: clamp(4.4px, 1.29vw, 6.4px); }
  .cc-statement__row + .cc-statement__row { margin-top: 5px; }
}

/* 2026-08-23: Studio-page instance of .cc-statement (see
   sections/cc-studio-statement.liquid) - same photo, same
   .cc-statement/.cc-statement__inner positioning rules above (left/
   top/transform apply unchanged, since it's the same photo with the
   same TV/mullion obstacle geometry). This modifier only changes two
   things, per direct instruction: (1) caps text in Switzer Light
   (weight 300) instead of the homepage instance's Semibold (600), and
   (2) type sized down from the homepage instance's - "Photography and
   Strategy" / "in one place" run about 20% more characters than the
   homepage's longest row ("Strategy and Visuals" / "for brands"),
   which was already a tight one-line fit inside the same ~136px
   mobile / ~577px desktop clear pane (see that rule's own rounds
   7-10 history above). Scaled both the desktop base size and the
   mobile size down by roughly that same 20% (0.833x) rather than
   re-measuring the pane from scratch, since it's the same pane. First
   pass, not yet checked against a real screenshot of the Studio page -
   expect this needs the same kind of correction round the homepage
   version went through. */
/* 2026-08-27 (Round 33): scaled up alongside Home's own increase above,
   keeping the same ~0.833x ratio this modifier has used since it was
   first built (Studio's longer copy needs to stay proportionally
   smaller than Home's). */
/* 2026-08-28 (Round 37): Studio uses the SAME photograph and therefore
   the same clear rectangle, so it needs no left/top override - the
   shared left:42.5%/top:36% lands its ink centre at 34.78% against the
   pane's 34.75%. It does need its own size trim, because "Photography
   and Strategy" is a longer line than the homepage's "Strategy and
   Visuals": 12.765px of ink per 1px of font-size here, against the
   homepage's 10.334. At the old 3.8vw its ink already ran 1.08% PAST
   the right-hand mullion before this round. 2.2vw holds it inside. */
.cc-statement--studio .cc-statement__row .cc-accent-script { font-size: clamp(15px, 2.2vw, 43px); }
.cc-statement--studio .cc-statement__caps { font-weight: 300; font-size: clamp(6px, 0.9vw, 17px); }
@media screen and (max-width: 680px) {
  .cc-statement--studio .cc-statement__row .cc-accent-script { font-size: clamp(6.8px, 2.03vw, 10px); }
  .cc-statement--studio .cc-statement__caps { font-size: clamp(3.8px, 1.11vw, 5.3px); }
}

/* 2026-08-25: direct feedback comparing the two pages side by side -
   "the home page is a great position for the text, but the studio
   isn't centred on the laptop version." Home's left:42%/top:20% anchor
   (see .cc-statement__inner above) was pixel-measured against the
   homepage's own text metrics and never re-checked once Studio started
   sharing it (that gap was flagged as a risk in this modifier's very
   first pass, 2026-08-23 comment above: "not yet checked against a real
   screenshot of the Studio page"). Measured directly off the screenshot
   sent this round: horizontally, Studio's rendered rows land almost
   exactly on the same ~42% centre Home's do (same pane, same anchor,
   both fit under the 0.833x size reduction already in place - left is
   correctly unchanged). Vertically is where they diverge: Studio's
   text, being the smaller of the two, reads as stranded near the
   ceiling beam with a large stretch of empty glass beneath it before
   the desk - Home's larger block earns more of that same room by
   simply being bigger. Nudged top down from the inherited 20% to 27%
   on desktop only (scoped to min-width:681px, mirroring the existing
   680px mobile boundary above, so this can't touch the mobile rule) -
   gives the block more presence in the middle of the pane without
   coming close to the laptop screen (measured laptop top at roughly
   54% of the photo's height, comfortable clearance even accounting for
   the block's own height). First-pass estimate from screenshot
   measurement, same caveat every position pass on this photo has
   carried - Chrome browser control wasn't reachable this round (needs
   macOS Screen Recording permission on the connected device), so this
   is not yet checked against a live render the way every other round's
   final value normally is. Flagged to confirm on the next real
   screenshot rather than treated as settled. */
/* 2026-08-27 (Round 34): Studio's own top override removed entirely, per
   direct instruction ("bring it up, match with the same placement on
   the home page") - Studio's caption reached furthest into the laptop
   screen of the two pages (round 33's 36% centring a now-much-taller
   4-line box), and rather than tune a third separate Studio value, it
   now inherits the same 30% Home uses above (both pages share the same
   photo and obstacle geometry, so one shared value is the more direct
   fix this feedback actually asked for). */

/* 2026-08-26 (Round 31) - removed entirely, not patched again.
   This whole block (2026-08-23 unscoped aspect-ratio pairing for
   .cta-scene/.portal .pstage2, plus round 30's mobile override that
   tried to out-cascade it) is the block that chased the Portal/CTA
   black-gap bug across rounds 26, 28, 29, and 30. Round 30 fixed the
   cascade conflict cleanly and verified it live, byte-for-byte - and
   the black gap was still reported unchanged on a real device
   afterward. That rules out this specific mechanism as the actual
   cause, so there is no more reason to keep the aspect-ratio pairing
   around at the cost of the complexity it kept adding. Removed outright
   per direct instruction to roll Portal/CTA back to a simpler, earlier
   state rather than continue patching forward. .cta-scene and
   .portal .pstage2's mobile height is now governed only by
   cc-v3-home.css's own @media(max-width:680px) block, both set to
   100dvh - see that file's 2026-08-26 comment for the reasoning.
   Desktop is unaffected either way; this rule pair never had a mobile
   media query to begin with, and its removal only changes mobile
   sizing. If the two sections still need to visually pair up as
   photos again later, that should be revisited as its own scoped,
   carefully-tested change - not folded back into this same spot. */
/* ===================================================================
   2026-08-22: "for the block of the map, reduce the gap on each top
   and bottom." .pstage (the map's pinned box, .porthole section) holds
   a fixed viewport-height box on purpose while its GSAP pin plays out
   (cc-v3-home.js, ScrollTrigger.create on .pstage) - shrinking that box
   itself risks the sections before/after it peeking in around the
   pin's edges (documented risk, see the sixteenth/seventeenth-pass
   comments above on .pinmap-inner). The actual lever, per that same
   pair of passes, is sizing the map ITSELF off viewport height so it
   fills more of that fixed box - .pstage centres its content
   vertically (justify-content:center), so a taller map directly
   shrinks the leftover space split above and below it.
   Root cause the gap is still large on mobile: the seventeenth pass's
   own fix, "width: min(100%, 98vh)", almost never lets the 98vh side
   of that min() actually win on a normal tall phone - a portrait
   phone's 100% (container width, ~350px after .pinmap's 20px side
   padding) is nearly always smaller than 98% of its own (taller)
   viewport height, so the map stayed sized off width alone, same as
   before that pass. .pinmap-scroll is already a horizontal scroller
   with its own "Swipe to see every location" hint text
   (.pinmap-hint, markup already in sections/cc-home-v3.liquid) built
   for exactly this - a map wider than the viewport that scrolls. Fix:
   drop the 100% cap so the map is sized off viewport height alone (a
   sane absolute px ceiling added instead, for unusually short/wide
   viewports), letting it genuinely exceed the viewport width and
   scroll rather than staying capped at container width - and un-hide
   the existing swipe hint on mobile now that swiping is the expected
   way to see the rest of the map, not just physically possible. */
/* 2026-08-23: reverted - "I don't want to swap [swipe] to see a
   location even on mobile version. The map need to be shown in one."
   The 2026-08-22 fix above traded a vertical gap for a map wider than
   the viewport (width: min(104vh,640px), no 100% cap, plus un-hiding
   the swipe hint) - exactly the opposite of what's wanted now. Reverting
   width/aspect-ratio back to the third-pass mobile rule earlier in this
   file (width:100%; aspect-ratio:1000/560, the config the six pin
   positions below it were hand-checked against) rather than picking new
   numbers, since changing the map's aspect ratio without re-checking
   pin placement risks pins drifting off their correct spots - something
   this sandbox can't visually verify. .pinmap-hint gets no override
   here, so it falls back to its sitewide default (display:none in
   cc-v3-home.css) - correct once nothing needs swiping.
   Gap addressed differently this time: instead of growing the map,
   shrink .pstage's own pinned box. Checked cc-v3-home.js first - the
   ScrollTrigger.create({trigger:'.pstage', pin:true, end:function(){
   return '+='+Math.round(window.innerHeight*0.8)}, ...}) call ties the
   pin's scroll distance to a fixed 0.8*viewport-height, not to
   .pstage's own CSS height or scrollHeight - so shortening this box
   does not desync the pin's timing the way the "sections peeking in
   around the pin's edges" risk (noted in the sixteenth/seventeenth-pass
   comments above) originally warned about for a different kind of
   change. At width:100% and aspect-ratio:1000/560, the map itself
   renders about 203px tall on a 402px-wide phone (362px content width
   after .pinmap's 20px side padding); with the heading and its gap,
   total content is roughly 245px. 42vh keeps a reasonable top/bottom
   margin without needing the box to be much taller than its content -
   down from 86vh, whose ~751px height was leaving the bulk of the
   original gap complaint. */
@media (max-width: 680px) {
  .pstage {
    /* 2026-08-26 (Round 31): 42dvh was a deliberate, well-reasoned
       2026-08-23 fix for an even taller 86vh that was leaving large
       symmetric margins above/below the centred map content. But at
       42dvh, while GSAP holds this section pinned for its own scroll
       buffer (cc-v3-home.js, ScrollTrigger.create on .pstage, currently
       a further 0.6x-viewport-height hold after the pins finish
       cycling), the pinned box only ever covers the top ~42% of the
       screen - the remaining ~58% simply shows .porthole's own plain
       background (var(--paper)) for the whole hold, which reads as a
       large, unexplained blank gap even though nothing is technically
       broken. Neither extreme solves both problems at once with the
       information available this round. 68dvh is a considered middle
       point, not a re-guess at either prior number: enough to shrink
       that visible blank stretch noticeably without reintroducing the
       oversized-margin symptom the 2026-08-23 fix addressed. Left the
       JS pin buffer (0.6x, scrub 0.35) completely untouched - that
       value is tied to a directly-verified, tick-by-tick-tested fix
       for a real skip-stop bug (see cc-v3-home.js's 2026-08-24 comment
       on this same ScrollTrigger.create call), and re-tuning it
       without the ability to verify live on this session risks
       reopening that specific bug blindly. Please confirm on a real
       device whether this reads better; if the gap is still too large,
       the buffer is the next, more carefully-tested lever, not another
       guess at this height value. */
    height: 68dvh;
  }
}

/* 2026-08-28 (Round 37, second pass): the product carousel's arrows and
   the Range shelf's arrows were both missed by the sitewide glass pass -
   cc-carousel.css sets `.ccar__arrow{background:none}` and only
   `.studionav .ccar__arrow` above ever overrode it, so these two sets
   rendered as bare glyphs with no button surface at all. Both sit on the
   paper background rather than on a photo, so they take the BLACK glass
   variant, at the same .62 fill used by every other black pill on the
   site after the contrast correction. Specificity (0,2,0) beats
   cc-carousel.css's (0,1,0) regardless of load order. */
.ccar__nav .ccar__arrow,
.rangenav .ccar__arrow {
  background: rgba(17, 17, 15, .62);
  backdrop-filter: blur(16px) saturate(1.5);
  -webkit-backdrop-filter: blur(16px) saturate(1.5);
  border: 1px solid rgba(255, 255, 255, .16);
  color: var(--paper, #F7F5F0);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.16), 0 6px 18px -8px rgba(17,17,15,.32);
  transition: background .3s ease;
}
.ccar__nav .ccar__arrow:hover,
.ccar__nav .ccar__arrow:focus-visible,
.rangenav .ccar__arrow:hover,
.rangenav .ccar__arrow:focus-visible {
  background: rgba(17, 17, 15, .74);
}
.ccar__nav .ccar__arrow[disabled],
.rangenav .ccar__arrow[disabled] {
  opacity: .35;
}

}


/* 2026-09-09: .bridge neutralised, not removed.
   It is a 4.5px gradient from rgb(213,200,187) to paper, drawn to fade the
   dark Portal section into the paper of Selected. After the reorder Portal no
   longer precedes Selected, so that warm band now appears between two paper
   sections for no reason. Kept in the DOM at its own height so nothing
   shifts; it just no longer paints a transition that has nothing to
   transition. Restore the gradient if Portal is ever moved back above it. */
.bridge{ background:var(--paper,#FAFAF8); }

/* ===================================================================
   HERO CAPTIONS - layer promotion, restored deliberately  2026-09-10

   The 9 Sep compositor pass stripped will-change from everything, which
   was right for .cc-fade-in and the strip cards - dozens of elements
   holding a layer for the life of the page after a one-shot transition.

   It was wrong for these four. They are driven by gsap.set on EVERY
   scroll frame for the whole length of the pinned hero, which is the
   one case will-change exists for. Without it, iOS Safari left them
   painted after their opacity reached zero: a screen recording on a real
   iPhone showed "Closer" and "Arrived" both fully opaque at once, and
   "Distance is a choice" ghosted over the following photograph. The
   timing was provably disjoint at those moments, so it was stale paint.

   Kept to four elements on purpose. Do not widen this back out.
   =================================================================== */
.headline,
.sub,
.stageline {
  will-change: opacity, transform;
}

/* --- Desk block, 2026-09-11 -----------------------------------------
   Two direct instructions, both scoped to #statement so the Studio
   page's own .cc-statement--studio block (different copy, its own
   sizing at lines 2091/2094 above) is not touched by either.

   1. "Remove the fine art tagline for the desk block, we will keep only
      strategy and visual for brands." Done in the markup - the
      "Fine Art / for walls" row is gone from
      sections/cc-home-v3.liquid. The line itself is not lost from the
      page: the sticky tagline bar (sections/cc-tagline-bar.liquid) and
      the floating .cc-float-tagline both still carry both halves.

   2. "The text a bit too spaced, you can bring the bottom section up in
      order to reduce the gap." With the first row gone the only gap
      left inside the block is between the script line and its caps
      line, which is .cc-statement__caps' own margin-top. 0.22em was set
      when the type was small and static; now that the block zooms, that
      margin scales with it and reads as a gap rather than a pairing.
      Tightened to 0.08em, which keeps the two lines reading as one unit
      at full zoom without the caps touching the script's descenders. */
#statement .cc-statement__caps { margin-top: 0.08em; }

/* --- 2026-09-12, second pass: reverted, and why ---------------------
   The first attempt at this section added three things that measured badly
   on the rendered page and were reported as glitches. All three are removed
   rather than tuned, because each was wrong in kind, not in degree:

   1. A differential drift on .selected .tile .ph. Measured on the page, the
      five frames sat up to 56 px apart against a 22 px grid gutter, and they
      were at full offset the moment the section entered the viewport rather
      than at rest. Frames sliding independently of the captions beneath them
      read as a rendering fault, not as depth. If this returns it belongs
      INSIDE .ph, where overflow:hidden masks it and the frame stays locked to
      its caption - and at a fraction of that travel.

   2. A staggered entrance on .range .rcard. gsap.fromTo set opacity:0 on all
      fifteen cards immediately, and the tween then failed to play even with
      its own ScrollTrigger reporting isActive and progress 0.39 - so the
      whole shelf stayed invisible. A section with no motion is strictly
      better than a section with no content; the shelf goes back to having no
      entrance at all until there is a pattern here that cannot fail open.

   3. Sticky on the Selected heading, and 14vh of padding on .portal. Neither
      is broken on its own, but both landed in the same unverified bundle as
      the two faults above, so they come out together and go back one at a
      time with a look at each.

   What stays from that pass is only what was measured as an improvement on
   the rendered page: the art-directed hero crop and the lazy Studio video. */

/* The hero's <picture> wrapper must not exist as far as layout is concerned.
   Left alone it computes to display:inline, which puts an inline box around
   an absolutely-sized block image inside .layer and can introduce a baseline
   gap under it. display:contents removes the wrapper from the box tree
   entirely, so .l1 img lays out exactly as it did before art direction was
   added, with no other rule needing to change. */


/* --- Hero wipe, 2026-09-13 ------------------------------------------
   The mask that used to live here is gone. It set its gradient stops with
   calc() over a CSS custom property, which Chromium resolves and iOS WebKit
   does not - on a real phone the base layer was feathered away and the
   section's own #04202a ground showed through the bottom of the hero.
   Sampled from a recording at #091E25.

   cc-hero-stepped-v2.js now writes the whole gradient as a literal string,
   so nothing here has to compute anything. No mask is declared at rest,
   which also means a layer is whole if the script never runs - the no-JS
   case is still handled by .l3/.l4 sitting at opacity 0 in cc-v3-home.css. */
@media (prefers-reduced-motion: reduce){
  .stage .layer{ -webkit-mask-image: none !important; mask-image: none !important; }
}

