/** Shopify CDN: Minification failed

Line 4118:42 Expected "}" to go with "{"

**/


/* Published as an Artifact the page runs inside an iframe. On a phone that iframe can
   report a desktop-width viewport while being displayed narrow, so viewport media
   queries chose the two-column desktop layout and the phone just scaled it down --
   "a squished desktop". Container queries ask how wide THIS PAGE actually is, which is
   correct in the iframe and identical to the old behaviour in a normal tab. */

.npc-root {
  container-type: inline-size;
  container-name: np;
}

/* ═══ wall preview ═══ */

.npc-root {
  --bg: #EFEDE8;
  --wall: #E6E2DA;
  --surface: #FBFAF7;
  --line: #DAD5CB;
  --ink: #26241F;
  --soft: #7A7466;
  --accent: #3F5C46;
}

.npc-root, .npc-root *, .npc-root *::before, .npc-root *::after {
  box-sizing: border-box;
}

.npc-root {
  margin: 0;
  -webkit-font-smoothing: antialiased;
}

.wrap {
  margin: 0 auto;
}

.cols {
  display: grid;
  grid-template-columns: 1fr 380px;
  gap: 28px;
  align-items: start;
}

@container np (max-width:900px) {
  .cols {
    grid-template-columns: 1fr;
  }
}

.wall {
  height: 640px;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
}

.art {
  position: relative;
  transition: width .25s ease, height .25s ease, padding .25s ease;
  display: flex;
  margin-bottom: 40px;
}

.mat {
  background: #FCFBF6;
  width: 100%;
  height: 100%;
  transition: padding .25s ease;
  display: flex;
  position: relative;
}

.bevel {
  width: 100%;
  height: 100%;
  position: relative;
  display: flex;
  transition: all .25s ease;
}

.bevel::before {
  content: "";
  position: absolute;
  inset: -1px;
  pointer-events: none;
  border: 1px solid #E4E0D2;
  box-shadow: inset 1px 1px 2px rgba(120,110,90,.35);
  z-index: 2;
}

.img {
  width: 100%;
  height: 100%;
  background-size: cover;
  background-position: center;
  position: relative;
}

.opts {
  display: flex;
  flex-wrap: wrap;
}

.meta {
  margin-top: 20px;
  padding-top: 14px;
  font-size: 12.5px;
  line-height: 1.7;
}

.meta b {
  font-weight: 600;
}

/* ═══ rules for .bevel.flat::before ═══ */

.bevel.flat::before {
  display: none;
}

/* ═══ rules for .artwrap ═══ */

.artwrap {
  display: flex;
  gap: 33px;
  align-items: center;
}

.artwrap .art {
  margin-bottom: 0;
}

/* ═══ rules for media(max-width:900px) ═══ */

@container np (max-width:900px) {
  /* `position:sticky` here is DEAD -- a later rule forces .wall{position:relative
     !important}. What survived was `top:8px`, which on a relative box is a plain
     8px downward nudge: it opened an 8px band of gallery ground above the image
     and pushed the bottom edge into the thumbnail strip. The drop shadow went
     with it -- a full-bleed image has no card edge to cast one (29 Aug). */
  .wall {
    top: auto;
    z-index: 20;
    box-shadow: none;
  }
}

/* ═══ compact desktop: everything visible without scrolling ═══ */

.atc {
  width: 100%;
  margin-top: 16px;
  color: var(--surface);
  border: none;
  cursor: pointer;
  letter-spacing: .01em;
}

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

@container np (min-width:901px) {
        .meta {
    padding-top: 10px;
    line-height: 1.55;
  }
}

/* This page is a committed LIGHT design. Published as an Artifact, the claude.ai
   viewer stamps the user's app theme onto the root: on a phone in dark mode it sets
   an INLINE style="color-scheme:dark" on <html>, which flips every UA-rendered
   surface (transparent-background buttons, range sliders, scrollbars) to dark and
   made whole sections read differently from desktop. !important beats the inline
   style; nothing else in this page keys off data-theme. */


.npc-root {
  background: #fff;
  color: #1a1a1a;
  font-family: 'DM Sans',-apple-system,sans-serif;
}

.wall {
  border: none;
}

h1 {
  font-family: 'Lora',Georgia,serif;
  font-weight: 500;
  letter-spacing: 0;
  font-size: 30px !important;
  margin: 2px 0 4px;
}

.rating {
  font-size: 13px;
  color: #E8A33D;
  letter-spacing: 1px;
}

.rating span {
  color: #6e6e6e;
  letter-spacing: 0;
  margin-left: 4px;
}

.glabel {
  font-family: 'DM Sans',sans-serif;
  font-size: 12.5px !important;
  font-weight: 700;
  letter-spacing: .02em;
  text-transform: uppercase;
  color: #1a1a1a;
  margin: 16px 0 8px !important;
}

.gval {
  font-weight: 400;
  text-transform: none;
  color: #6e6e6e;
  letter-spacing: 0;
  margin-left: 6px;
}

.meta {
  color: #6e6e6e;
  border-top: 1px solid #E6E6E6;
}

.meta b {
  color: #1a1a1a;
}

.atc {
  background: #111;
  border-radius: 12px;
  font-family: 'DM Sans',sans-serif;
  font-weight: 600;
  font-size: 15px;
  padding: 16px 18px;
}

.atc:hover {
  opacity: .85;
}

/* ═══ rules for .glzctl ═══ */

.glzctl {
  position: absolute;
  left: 14px;
  bottom: 12px;
  display: flex;
  gap: 8px;
  z-index: 6;
  font-family: 'DM Sans',sans-serif;
  font-size: 12px;
  color: #1a1a1a;
}

.glzctl input {
  width: 110px;
  accent-color: #111;
}

.glzctl b {
  font-variant-numeric: tabular-nums;
  min-width: 26px;
  text-align: right;
}

/* ═══ rules for .glzctl ═══ */

.glzctl {
  flex-direction: column;
  align-items: flex-start;
  border-radius: 14px;
  padding: 0;
  overflow: visible;
  background: transparent;
  border: none;
}

.glzhead {
  font-family: 'DM Sans',sans-serif;
  font-size: 12px;
  background: #fff;
  border: 1px solid #DFDFDF;
  border-radius: 100px;
  padding: 7px 14px;
  cursor: pointer;
  color: #1a1a1a;
}

.glzpanel {
  display: none;
  margin-top: 8px;
  background: #fff;
  border: 1px solid #DFDFDF;
  border-radius: 12px;
  padding: 12px 14px;
  box-shadow: 0 12px 30px -14px rgba(0,0,0,.25);
}

.glzpanel.open {
  display: block;
}

.glzpanel label {
  display: grid;
  grid-template-columns: 70px 120px 34px;
  align-items: center;
  gap: 8px;
  font-size: 11.5px;
  color: #444;
  margin: 5px 0;
  font-family: 'DM Sans',sans-serif;
}

.glzpanel input {
  width: 120px;
  accent-color: #111;
  margin: 0;
}

.glzpanel b {
  font-size: 11px;
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.glzcopy {
  margin-top: 8px;
  width: 100%;
  font-size: 11.5px;
  font-family: 'DM Sans',sans-serif;
  background: #111;
  color: #fff;
  border: none;
  border-radius: 8px;
  padding: 7px;
  cursor: pointer;
}

/* ═══ Fy-density pass: bigger type, full column, descriptive cards ═══ */

.opts.sizegrid {
  display: grid;
  grid-template-columns: repeat(3,1fr);
  gap: 10px;
}

@container np (max-width:520px) {
  .opts.sizegrid {
    grid-template-columns: repeat(2,1fr);
  }
}

/* ═══ full-width layout pass ═══ */

.wrap {
  max-width: none;
  padding: 30px 3vw 70px;
}

/* ═══ rules for .opts.shapegrid ═══ */

.vth:focus-visible {
  outline: 2px solid #1a1a1a;
  outline-offset: 2px;
}

/* ═══ square image port, no rounding, #f6f6f6 ground ═══ */

.wall {
  border-radius: 0 !important;
}

@container np (max-width:900px) {
  .wall {
    min-height: 0 !important;
  }
}

/* ═══ max out the square on desktop ═══ */

@container np (min-width:901px) {
  .wrap {
    max-width: none;
    margin: 0;
  }

  h1 {
    font-size: 38px !important;
  }
  .rating {
    font-size: 14px;
  }
  .opts.shapegrid {
    max-width: 560px;
  }
    .meta {
    font-size: 13.5px !important;
  }
  .atc {
    border-radius: 10px !important;
  }
  .vth {
    min-width: 70px;
    padding: 9px 12px 7px;
  }
  .vth span {
    font-size: 11px;
  }
}

/* ═══ faux Nordic Park header ═══ */

.siteheader {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 30px;
  padding: 18px 2vw;
  background: #fff;
}

.logo {
  font-family: 'Lora',Georgia,serif;
  font-weight: 400;
  font-size: 24px;
  letter-spacing: .28em;
  color: #3a3a3a;
  white-space: nowrap;
}

.sitenav {
  display: flex;
  gap: 26px;
  font-family: 'DM Sans',sans-serif;
  font-size: 15px;
  color: #1a1a1a;
}

.sitenav span {
  cursor: pointer;
}

.hicons {
  display: flex;
  gap: 18px;
  align-items: center;
}

@container np (max-width:900px) {
  .sitenav {
    display: none;
  }
}

@container np (min-width:901px) {
  .wrap {
    padding: 0 2vw 60px;
  }
}

@container np (min-width:901px) {
  .glabel {
    font-size: 12.5px !important;
    margin: 17px 0 8px !important;
  }
  .gval {
    font-size: 13px;
  }
  .opts {
    gap: 8px;
  }
      .opts.shapegrid {
    gap: 8px;
  }
        .opts.sizegrid {
    gap: 8px;
  }
        .meta {
    margin-top: 14px !important;
  }
  .atc {
    padding: 16px 18px !important;
    font-size: 16px !important;
    margin-top: 14px !important;
  }
}

@container np (max-width:900px) {
  .opts.sizegrid {
    grid-template-columns: repeat(2,1fr);
    gap: 6px;
  }
    }

.opts.shapegrid {
  display: grid !important;
  gap: 10px !important;
  max-width: none !important;
}

@container np (max-width:900px) {
  .opts.shapegrid {
    gap: 6px !important;
  }
}

.opts.framegrid,
.opts.shapegrid,
.opts.sizegrid {
  gap: 10px !important;
}

.opts {
  gap: 10px !important;
}

/* ═══ On phones the preview should run edge to edge. `.wrap` carries a 3vw side padding for the  ═══ */

@container np (max-width:900px) {
  .wrap {
    padding-left: 0 !important;
    padding-right: 0 !important;
  }
  .cols {
    gap: 0 !important;
  }
  .cols>.optcol {
    padding: 14px 16px 0;
  }
  .wall {
    /* width:100% !important lived here too — dead: the mobile .wall block further
       down (square, aspect-ratio) re-declares it with the same importance. */
    border-radius: 0 !important;
  }
}

/* ═══ Size buttons: one line each. The set ladders carry "… each" and only have four entries, so ═══ */

.opts.sizegrid.wide {
  grid-template-columns: repeat(2,1fr) !important;
}

@container np (min-width:901px) {
      
  .opts.sizegrid {
    grid-template-columns: repeat(auto-fit,minmax(220px,1fr)) !important;
  }
  .opts.sizegrid.wide {
    grid-template-columns: repeat(2,1fr) !important;
  }
}

/* ═══ On a phone a 2-up size card is ~160px — too narrow for name + price on one row, and lettin ═══ */

@container np (max-width:900px) {
      
  .opts.sizegrid.wide {
    grid-template-columns: 1fr !important;
  }
  }

.opts.shapegrid {
  justify-content: start !important;
}

/* The .on treatment here used to be an INSET ::after ring — invisible against the
   dark moulding that fills the swatch edge on Walnut/Black/Espresso. Removed
   (Ollie 29 Aug evening) so the shared selection rule above applies: dark border
   + outer 2px ring, exactly like medium, shape and size. The scrollers get 3px
   of clearance below so the outer ring isn't clipped. */

.opts.sizegrid,
.opts.sizegrid.wide {
  grid-template-columns: repeat(auto-fit,minmax(180px,1fr)) !important;
}

@container np (max-width:900px) {
  .opts.sizegrid,
  .opts.sizegrid.wide {
    grid-template-columns: repeat(2,1fr) !important;
  }
      }

/* ═══ gallery column: image + thumbnail strip beneath ============ The Product / Size guide / Dr ═══ */

.gallery {
  display: flex;
  flex-direction: column;
  gap: 10px;
  min-width: 0;
}

@container np (min-width:901px) {
  .wall {
    position: static !important;
    top: auto !important;
    max-height: none !important;
  }
}

.viewthumbs {
  position: static !important;
  display: flex !important;
  top: auto !important;
  right: auto !important;
  z-index: auto !important;
}

.vth {
  display: flex !important;
  min-width: 0 !important;
  box-shadow: none !important;
  cursor: pointer;
}

.vth span {
  font-family: 'DM Sans',sans-serif;
  color: #6e6e6e;
  white-space: nowrap;
}

.vth.on span {
  color: #1a1a1a;
  font-weight: 600;
}

@container np (max-width:900px) {
  .vth span {
    font-size: 10px;
    margin-top: 4px;
  }
}

/* ═══ BUGFIX + alignment ================= BUG: `.wall{position:static}` (set when the thumbnail ═══ */

.wall {
  position: relative !important;
}

@container np (min-width:901px) {
  .cols {
    justify-content: stretch !important;
    align-items: start;
  }
  .gallery {
    margin: 0 auto !important;
    position: sticky;
    top: 12px;
  }
  .wall {
    width: 100% !important;
    max-width: none !important;
    height: auto !important;
    aspect-ratio: 1/1 !important;
    margin: 0 !important;
  }
}

/* ═══ BUG: .artwrap carried a 40px bottom margin left over from the old caption layout, pushing  ═══ */

.artwrap {
  margin-bottom: 0 !important;
}

@container np (max-width:900px) {
  .artwrap {
    margin-bottom: 0 !important;
  }
}

/* ═══ BUG: .gallery had a width derived only from viewport HEIGHT, so on short-and-narrow window ═══ */

@container np (min-width:901px) {
  .gallery {
    max-width: 100% !important;
  }

  .cols {
    margin: 0 auto !important;
    gap: 80px !important;
  }

  .gallery {
    margin-left: auto !important;
    margin-right: 0 !important;
  }

  .opts.framegrid {
    justify-content: start !important;
    gap: 8px !important;
  }
}

@container np (max-width:900px) {
  .opts.framegrid {
    grid-template-columns: repeat(4,minmax(0,1fr)) !important;
    gap: 6px !important;
  }

  .opts.shapegrid,
  .opts.framegrid {
    gap: 6px !important;
  }
}

/* ═══ BUG: the thumbnail strip was an overflow container, so the selected tile's OUTER ring (box ═══ */

.viewthumbs {
  overflow: visible !important;
  padding: 3px 3px 0 !important;
  margin-left: -3px;
}

@container np (max-width:900px) {
  .viewthumbs {
    /* the 3px 16px padding is dead: the mobile PDP dressing block re-declares
       it (0 12px 0 !important) in the same container context. */
    margin-left: 0;
  }
}

/* ═══ Gallery filled only 90vh-150px, leaving ~66px of dead space under the thumbnail strip, and ═══ */

@container np (min-width:901px) {
  .cols {
    max-width: 1700px !important;
  }

  .opts.shapegrid {
    grid-template-columns: repeat(auto-fill,126px) !important;
  }
  .opts.framegrid {
    grid-template-columns: repeat(auto-fill,126px) !important;
  }

  .cols {
    grid-template-columns: minmax(220px,1fr) minmax(528px,min(720px,45vw)) !important;
  }
}

/* 3. The glass reflection is inset:0 + scale(1.15), so it overflowed the mat on every
      side and read as a dark line under the frame. Clip it to the glass, where it belongs. */

.mat {
  overflow: hidden;
}

/* 6. The "1. Choose Medium  Framed Canvas" echo is redundant -- the selected tile already
      carries the black ring. Number + label only. */

.gval {
  display: none !important;
}

/* 1 + 2. Frame choices: ONE row that scrolls horizontally instead of wrapping to two,
          and both frame and shape tiles come down 126px -> 104px. */

.opts.framegrid {
  display: flex !important;
  flex-wrap: nowrap !important;
  grid-template-columns: none !important;
  overflow-x: auto;
  overflow-y: hidden;
  gap: 8px !important;
  /* 3px top/side clearance: the selected tile's ring is an OUTER box-shadow now
     and this row is its own scroll container, which clips at the content edge */
  padding: 3px 3px 4px;
  scrollbar-width: thin;
  -webkit-overflow-scrolling: touch;
}

.opts.shapegrid {
  grid-template-columns: repeat(4,104px) !important;
}

/* 5. Thumb strip was a 90px-tall stack (icon over label) costing ~104px of gallery height.
      Laid out as a 34px pill instead -- icon and label side by side -- and the freed height
      goes straight into the image, which is sized off the viewport. */

.viewthumbs {
  gap: 6px !important;
  padding-bottom: 0 !important;
}

.vth {
  flex-direction: row !important;
  align-items: center !important;
  /* border died when the image-thumbnail pass set .vth{border:none !important}
     below; the ring is an inset shadow now. */
  background: #fff !important;
}

.vth.on {
  border-color: transparent !important;
  box-shadow: none !important;
}

.vth:hover {
  border-color: transparent !important;
}

.vth span {
  margin-top: 0 !important;
  font-size: 11.5px !important;
}

@container np (min-width:901px) {
  .gallery {
    /* The desktop gallery is a SQUARE sized off the viewport height, so it has to
       know how much vertical room everything else takes. That budget was a
       hardcoded 180px; the chrome and the thumbnail strip actually measure 226,
       which is exactly why the strip hung below the fold. fitGallery() measures
       it now and writes --galres, so it cannot drift again when the chrome
       changes. The fallback is the measured figure for the current layout. */
    width: min(calc(100vh - var(--galres,214px)),1000px,100%) !important;
    gap: 4px !important;
  }
}

@container np (max-width:900px) {
  .vth {
    width: auto !important;
  }
  .opts.shapegrid {
    grid-template-columns: repeat(4,minmax(0,1fr)) !important;
  }
}

/* .optcol is a grid item with the default min-width:auto, so a nowrap flex row of
   7x104px tiles reported a 776px min-content size and STRETCHED the whole options
   column past its track. min-width:0 lets the track hold and the row scroll instead. */

.optcol {
  min-width: 0 !important;
}

.framescroll {
  position: relative;
  width: 100%;
  max-width: 100%;
  min-width: 0;
  overflow: hidden;
}

.framescroll .opts.framegrid {
  width: 100% !important;
  max-width: 100% !important;
  min-width: 0 !important;
}

/* NO scroll-behavior:smooth here -- with it, scrollLeft assignments animate
   asynchronously and were observed never to land. The arrows tween in JS instead. */

.framescroll .opts.framegrid {
  scroll-behavior: auto !important;
  scroll-snap-type: none !important;
}

.fnav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  z-index: 4;
  width: 30px;
  height: 30px;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid #DAD5CB;
  border-radius: 50%;
  background: #fff;
  color: #1a1a1a;
  cursor: pointer;
  box-shadow: 0 2px 7px rgba(40,35,25,.20);
  transition: opacity .16s ease, border-color .16s ease, background .16s ease;
}

.fnav.prev {
  left: -6px;
}

.fnav.next {
  right: -6px;
}

.fnav:hover {
  border-color: #1a1a1a;
  background: #fff;
}

.fnav:active {
  background: #F2F0EB;
}

.fnav:focus-visible {
  outline: 2px solid #1a1a1a;
  outline-offset: 2px;
}

/* faded at the end of travel: also keeps it off the first/last tile */

.fnav.off {
  opacity: 0;
  pointer-events: none;
}

.fnav[hidden] {
  display: none;
}

@container np (max-width:900px) {
  .fnav {
    width: 27px;
    height: 27px;
  }
  .fnav.prev {
    left: 10px;
  }
  .fnav.next {
    right: 10px;
  }

  .cols {
    display: block !important;
  }
  .gallery {
    width: 100% !important;
    margin: 0 !important;
    /* plain white, not the cream --bg: on a phone the gallery ground is only ever
       visible as the strip behind the thumbnails, and a cream band between a white
       header and a white options column read as a rendering artefact (29 Aug).
       (Its padding-bottom:8px is dead — the mobile dressing block re-declares
       padding-bottom:0 !important in the same context.) */
    background: #fff;
  }
  .wall {
    /* SQUARE, and the width comes from the carousel slide, not the viewport.
       The caps this replaces -- min(100%, 430px, 46dvh) -- were both HEIGHT caps
       in disguise: 46dvh binds on every phone once Safari's toolbars are showing
       (~317px of a 402px viewport), so the stage sat in the middle of the gallery
       ground with a cream band down each side. Sizing now lives on .stageslide;
       the wall just fills it and holds its square. */
    height: auto !important;
    aspect-ratio: 1/1 !important;
    width: 100% !important;
    max-width: none !important;
    max-height: none !important;
    margin: 0 !important;
  }
  .optcol {
    width: min(100%, 560px) !important;
    margin: 0 auto !important;
  }
}

/* A real wall is never a flat fill: there is a light side, a shaded side, a darker
   band under the ceiling, soft corner falloff, and plaster micro-texture (which also
   kills gradient banding). All values come from CSS vars set by applyWall(), tuned
   live in the "Wall light" panel. The same angle lights the artwork overlay and slides
   the drop shadow, so wall and piece agree about where the window is. */

.wall {
  background: var(--wl-base,#f6f6f6) !important;
}

.wallshade {
  position: absolute;
  inset: 0;
  pointer-events: none;
  /* shade tints were warm brown (56,47,38) — with the base at 95% they summed to a beige wall.
     Near-neutral since 2026-09-29 so the wall reads WHITE (art.com reference). */
  background: linear-gradient(calc(var(--wl-angle,105)*1deg), rgba(255,255,255,var(--wl-hi,.3)) 0%, rgba(255,255,255,0) 58%),
    linear-gradient(calc(var(--wl-angle,105)*1deg + 180deg), rgba(58,56,53,var(--wl-sh,.07)) 0%, rgba(58,56,53,0) 55%),
    linear-gradient(180deg, rgba(58,56,53,var(--wl-top,.03)), rgba(58,56,53,0) 26%),
    radial-gradient(135% 100% at 38% 40%, rgba(0,0,0,0) 52%, rgba(58,56,53,var(--wl-vig,.045)) 100%);
}

.walltex {
  position: absolute;
  inset: 0;
  pointer-events: none;
  mix-blend-mode: overlay;
  background-size: 420px 420px;
}

/* scenes ride above the overlays; they only need the z-index. */

.artwrap {
  position: relative;
  z-index: 2;
}

.closeupscene {
  z-index: 2;
}

.closeupscene {
  display: none;
  position: absolute;
  inset: 0;
  overflow: hidden;
  background: #F6F4EF;
}

/* zoomed detail: FULL BLEED, caption over a bottom scrim (Ollie 10 Sep — the inset box and its
   hairline were eating the crop, and this slide exists to show the surface at size) */

.cuimg {
  position: absolute;
  inset: 0;
  background-position: center;
  background-repeat: no-repeat;
}

/* the acrylic on a framed print — the SAME two bands the dimension drawing draws in SVG, so the
   product, rooms, tiles and zoomed detail all show the reflection the drawing shows. Each band is
   a hard-edged parallelogram (12% wide, offset 24% top-to-bottom) with a soft white gradient
   across its bounding box, screen-blended. Lit top/left edge on the container. */

/* image 1 (the product view) shows NO glass reflection on a framed print (Ollie 2026-10-04); rooms, the corner zoom and
   the dimension drawing keep the two bands. The class is set per view in paintWall(). */
.wall.productview .glz { display: none; }

.glz {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 3;
  overflow: hidden;
  box-shadow: inset 1px 1px 0 rgba(255,255,255,.45);
}

.glzb {
  position: absolute;
  top: 0;
  height: 100%;
  width: 36%;
  display: block;
  mix-blend-mode: screen;
  background: linear-gradient(90deg, rgba(255,255,255,0) 0%, rgba(255,255,255,.14) 50%, rgba(255,255,255,0) 100%);
  clip-path: polygon(0 0, 33.34% 0, 100% 100%, 66.66% 100%);
}

/* the caption sits ON the artwork as a small pill — its own ground is the pill, not a scrim over
   the crop (the old bottom gradient dimmed a third of the slide). */
.cucap {
  position: absolute;
  left: 50%;
  bottom: 6%;
  transform: translateX(-50%);
  z-index: 5;
  padding: .5em 1.1em;
  border-radius: 999px;
  background: rgba(255,255,255,.9);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  box-shadow: 0 1px 4px rgba(0,0,0,.14);
  font-family: 'DM Sans',sans-serif;
  font-weight: 500;
  letter-spacing: .01em;
  color: #1a1a1a;
  white-space: nowrap;
}

.zoomlabel {
  display: none !important;
}

/* internal repricing note — lives under the COGS table, same audience */

.repnote {
  margin-top: 14px;
  padding: 13px 14px 11px;
  border: 1px solid rgba(26,26,26,.1);
  border-radius: 10px;
  background: #FBFAF7;
  font-family: 'DM Sans',sans-serif;
  font-size: 12px;
  color: #3a382f;
  line-height: 1.55;
}

.repnote > b {
  display: block;
  margin-bottom: 8px;
  font-size: 12.5px;
  color: #1a1a1a;
}

.reprow {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  padding: 3px 0;
}

.reprow span {
  color: #6f6a5e;
}

.reprow b {
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

.reprow.tot {
  margin-top: 4px;
  padding-top: 6px;
  border-top: 1px solid rgba(26,26,26,.1);
}

.reprow.tot b {
  color: #2f6b45;
}

.repnote p {
  margin: 9px 0 0;
}

.repnote .cav {
  color: #8a8578;
  font-size: 11px;
}

/* the per-variant "was" rows, so a price change reads at a glance */

.mrow.waschg b {
  color: #2f6b45;
}

/* frame-detail view: Luma's corner macro of the selected moulding, and its name.
   The name sits in its own row BELOW the photo, never over it — an overlaid band
   covered part of the frame this slide exists to show (Ollie, 1 Sep). */

.framescene {
  display: none;
  position: absolute;
  inset: 0;
  z-index: 3;
  flex-direction: column;
  background: #EDEAE3;
}

.fdcorner {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}

/* the macro is a SQUARE photo and the mitre sits mid-frame in it, so a `cover`
   fill of anything but a square crops the corner — the one thing this slide
   exists to show — straight out of view. Square box, cover inside it. */

.fdcorner::before {
  content: "";
  display: block;
  height: 100%;
  aspect-ratio: 1;
  max-width: 100%;
  background-image: var(--fdimg);
  background-size: cover;
  background-position: center;
}

.fdband {
  flex: 0 0 auto;
  padding: 14px 16px 15px;
  text-align: center;
  background: #F7F5F0;
  border-top: 1px solid rgba(26,26,26,.07);
}

.fdcap {
  font-family: 'DM Sans',sans-serif;
  font-size: 14px;
  font-weight: 600;
  color: #1a1a1a;
  line-height: 1.4;
}

/* the thumbnail is a 92px miniature of this slide — at that size the name is an
   unreadable smear, so the tile shows the corner macro alone */

.vthwall .fdband {
  display: none;
}

/* photo-integration pass on room mock-ups (see photoPass in the script) */

.npc-photo .art {
  filter: brightness(var(--ph-b, 1)) contrast(var(--ph-c, 1)) blur(var(--ph-blur, 0));
  box-shadow: var(--ph-shadow, none);
}

.npc-photo .csb,
.npc-photo .csr,
.npc-photo .csl {
  display: none !important;
}

.npc-photo .ph {
  position: absolute;
  display: block;
  pointer-events: none;
  z-index: 6;
}

.npc-photo .ph-tint {
  mix-blend-mode: multiply;
}

.npc-photo .ph-grain {
  mix-blend-mode: overlay;
  background-size: 140px 140px;
}

/* dimensions slide: paper background, artwork layer, SVG drawing on top, spec sheet below */

.dimscene {
  display: none;
  position: absolute;
  inset: 0;
  z-index: 3;
  overflow: hidden;
  background: #F6F4EF;
}

.dimpaper {
  position: absolute;
  inset: 0;
  background-image: radial-gradient(rgba(26,26,26,.085) 1px, transparent 1.2px);
  background-size: 18px 18px;
}

.dimart {
  position: absolute;
  background-repeat: no-repeat;
}

/* the photographed moulding on the dimension drawing (front view only) */
.dimframe {
  position: absolute;
  box-sizing: border-box;
  pointer-events: none;
}

.dimsvg {
  position: absolute;
  inset: 0;
  display: block;
  pointer-events: none;
}

.dimspec {
  position: absolute;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-end;
  overflow: hidden;
  font-family: 'Lora',Georgia,serif;
  color: #1a1a1a;
}

.dimback {
  width: 100%;
  overflow: hidden;
}

.dimcap {
  font-size: .95em;
  line-height: 1.3;
  color: #6b6b6b;
  margin-top: .5em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
}

/* a strip tile holds the real slide at full size, scaled down (see paintWall) */

/* social proof slide */

.socialscene {
  display: none;
  position: absolute;
  inset: 0;
  overflow: hidden;
  background: #F6F4EF;
  font-family: 'Lora',Georgia,serif;
  color: #1a1a1a;
}

.soc {
  position: absolute;
  inset: 0;
}

.soc-head {
  position: absolute;
  left: 0;
  right: 0;
  text-align: center;
}

.soc-h1 {
  font-size: 2.1em;
  font-weight: 500;
  letter-spacing: .12em;
  text-transform: uppercase;
  line-height: 1.15;
}

.soc-h2 {
  font-size: .95em;
  letter-spacing: .24em;
  text-transform: uppercase;
  margin-top: .55em;
}

.soc-grid {
  position: absolute;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: repeat(2, 1fr);
}

.soc-tile {
  background-size: cover;
  background-position: center;
  background-color: #EAE6DD;
  border: 1px solid rgba(26,26,26,.12);
}

.soc-rate {
  background: #FFFDF9;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  gap: .25em;
}

.soc-stars {
  display: flex;
  gap: .12em;
  font-size: 1.25em;
  line-height: 1;
  margin-bottom: .3em;
}

.soc-rate b {
  font-size: 2.2em;
  font-weight: 500;
  line-height: 1;
}

.soc-rate span {
  font-size: .85em;
  letter-spacing: .06em;
}

.soc-stats {
  position: absolute;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  align-items: center;
  border-top: 1px solid rgba(26,26,26,.16);
}

.soc-stats > div {
  text-align: center;
  padding: .4em .5em;
}

.soc-stats > div + div {
  border-left: 1px solid rgba(26,26,26,.16);
}

.soc-stats b {
  display: block;
  font-size: 1.75em;
  font-weight: 500;
  line-height: 1.15;
  white-space: nowrap;
}

.soc-stats span {
  display: block;
  font-size: .78em;
  letter-spacing: .2em;
  text-transform: uppercase;
  margin-top: .35em;
  white-space: nowrap;
}

.soc-small .soc-stats b {
  font-size: 1.25em;
}

.soc-small .soc-stats span {
  font-size: .62em;
  letter-spacing: .1em;
}

.soc-small .soc-stats > div {
  padding: .3em .2em;
}

.vth .vthwall .socialscene {
  text-align: left;
}

.vth .vthwall .socialscene span {
  font: inherit !important;
  color: inherit !important;
  white-space: inherit !important;
  margin: 0 !important;
}

.dimmini {
  position: absolute;
  left: 0;
  top: 0;
}

/* the tile is a <button> (centred text) with its own caption rules on every <span>
   (DM Sans, 11.5px !important, grey, nowrap) — none of that may leak into the miniature */

.vth .vthwall .dimscene {
  text-align: left;
}

.vth .vthwall .closeupscene span,
.vth .vthwall .dimscene span {
  font: inherit !important;
  color: inherit !important;
  white-space: inherit !important;
  margin: 0 !important;
}

/* imported-room view: real photo replaces the synthetic wall; its own light wins */

.roomscene {
  display: none;
  position: absolute;
  inset: 0;
  z-index: 1;
  background-size: cover;
  background-position: center;
}

.wall.roomview .wallshade,
.wall.roomview .walltex {
  display: none;
}

.rmempty {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  font-family: 'DM Sans',sans-serif;
  font-size: 13px;
  color: #8a8578;
  line-height: 1.6;
}

/* directional drop shadow, slid by the same light; exposure sits the piece IN the
   room light -- a dim wall with a full-brightness artwork is the giveaway of a mockup */

.art {
  box-shadow: var(--wl-shadow, 0 18px 40px -18px rgba(40,35,25,.45), 0 4px 10px rgba(40,35,25,.18));
  filter: brightness(var(--wl-artbr,1)) saturate(var(--wl-artsat,1));
}

.art .img {
  z-index: 2;
  filter: brightness(var(--wl-artbr-inv,1)) saturate(var(--wl-artsat-inv,1));
}

/* uniform near-black float gap over the photographed one (which reads grey on the lit
   corner); sits under .artlight so the directional light still grades it slightly */

.cvgap {
  position: absolute;
  pointer-events: none;
  z-index: 1;
  border-style: solid;
  border-color: rgba(13,12,10,.92);
}

/* cast shadow bands: sharp at the frame edge, decaying with distance (see applyWall).
   Each band is end-masked so it TAPERS at the corners instead of stopping in a hard
   rectangle, and slightly blurred; where the bottom and side bands both taper they
   merge into one soft corner shadow. */

.csb,
.csr,
.csl {
  position: absolute;
  pointer-events: none;
  z-index: 0;
  filter: blur(1.2px);
}

.csb {
  -webkit-mask-image: linear-gradient(90deg, transparent 0%, #000 9%, #000 91%, transparent 100%);
  mask-image: linear-gradient(90deg, transparent 0%, #000 9%, #000 91%, transparent 100%);
}

.csr,
.csl {
  -webkit-mask-image: linear-gradient(180deg, transparent 0%, #000 14%, #000 88%, transparent 100%);
  mask-image: linear-gradient(180deg, transparent 0%, #000 14%, #000 88%, transparent 100%);
}

.csb {
  background: linear-gradient(180deg,
        rgba(18,15,12,var(--wl-csb,0)) 0%,
        rgba(18,15,12,calc(var(--wl-csb,0)*0.35)) 42%,
        rgba(18,15,12,0) 100%);
}

.csr {
  background: linear-gradient(90deg,
        rgba(18,15,12,var(--wl-csr,0)) 0%,
        rgba(18,15,12,0) 100%);
}

.csl {
  background: linear-gradient(270deg,
        rgba(18,15,12,var(--wl-csl,0)) 0%,
        rgba(18,15,12,0) 100%);
}

/* frame-lip inner shadow on framed prints (sits above the artwork by REQUEST -- it is
   the physical occlusion of the frame overhanging the print under the glass, not a
   tonal grade; the artwork-purity rule covers grading only) */

.matshadow {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 3;
}

.artlight {
  position: absolute;
  z-index: 1;
  pointer-events: none;
  background: linear-gradient(calc(var(--wl-angle,105)*1deg), rgba(255,250,240,var(--wl-arthi,.08)), rgba(255,250,240,0) 50%),
    linear-gradient(calc(var(--wl-angle,105)*1deg + 180deg), rgba(30,24,18,var(--wl-artsh,.06)), rgba(30,24,18,0) 55%);
}

/* the WL panel lives top-left; #ggCtl owns top-right */

#wlCtl {
  bottom: auto;
  top: 12px;
}

/* gold tuner: top-right, away from the wall-light panel */

#ggCtl {
  left: auto;
  right: 14px;
  bottom: auto;
  top: 12px;
  align-items: flex-end;
}

.wlpresets {
  display: flex;
  gap: 5px;
  margin-bottom: 8px;
  flex-wrap: wrap;
}

.wlpresets button {
  font-family: 'DM Sans',sans-serif;
  font-size: 10.5px;
  padding: 4px 8px;
  border: 1px solid #DAD5CB;
  border-radius: 999px;
  background: #fff;
  color: #1a1a1a;
  cursor: pointer;
}

.wlpresets button:hover {
  border-color: #1a1a1a;
}

/* ═══ 28 Aug: view strip = image thumbnails (no labels) ═══ */

/* ONE source of truth for the tile size. The tile CONTENT is laid out by JS in
   absolute pixels on a hardcoded TS=92 basis (room cover-crop offsets, piece
   anchor, ppi), so the child must always BE 92x92 and is transform-scaled to fit
   --vtn. Never resize the box alone -- that misaligns every composite. */
.vth {
  --vtn: 92;
  position: relative !important;
  width: calc(var(--vtn) * 1px) !important;
  height: calc(var(--vtn) * 1px) !important;
  padding: 0 !important;
  gap: 0 !important;
  border: none !important;
  border-radius: 12px !important;
  overflow: hidden !important;
  flex: none;
}

/* The 92x92 child is taken OUT OF FLOW. As a flex item it was shrinkable, and
   `width:92px` sets the hypothetical size only -- inside a 62px content box it
   shrank to 62, then the 64/92 scale took it to 43px, and `align-items:center`
   parked it at y=-14. That was the "weird crop with white space": every room
   thumbnail was squashed to 43x64, cover-cropping at the wrong aspect and
   throwing the composited piece off its anchor. Absolute + flex:none fixes the
   used size at 92 so the scale is the ONLY transform (29 Aug). */
.vth > * {
  position: absolute !important;
  left: 0;
  top: 0;
  flex: none !important;
  width: 92px !important;
  height: 92px !important;
  transform: scale(calc(var(--vtn) / 92));
  transform-origin: 0 0;
}

/* Rings are drawn INSIDE the box so they cost no layout: a border would eat 2px
   of the content box and put the scale back out of true, and an outer box-shadow
   gets clipped by the strip's overflow. */
.vth::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  box-shadow: inset 0 0 0 1px #E0DED9;
  pointer-events: none;
  z-index: 4;
}

.vth.on::after {
  box-shadow: inset 0 0 0 2px #1a1a1a;
}

.viewthumbs {
  position: relative;   /* offsetLeft of a tile is measured against this strip */
  overflow-x: auto !important;
  flex-wrap: nowrap !important;
  scrollbar-width: thin;
}

/* ═══════════════════════════════════════════════════════════════════════════
   MOBILE-ONLY TWEAKS (29 Aug 2026)
   1. View tiles ~30% smaller — one variable now; the geometry that makes that
      safe lives with the .vth base rules above.
   2. The strip runs edge to edge under a full-bleed image, so it gets the same
      white ground and no side padding beyond the scroll inset.
   3. Hide the calibration cog buttons (wall light / rooms / gold frame /
      glass) — desktop-only tooling. #ggCtl gets an inline display:flex
      from JS, hence !important.
   ═══════════════════════════════════════════════════════════════════════════ */

@container np (max-width:900px) {
  .vth {
    --vtn: 64;
    border-radius: 9px !important;
  }
  .viewthumbs {
    background: #fff;
    justify-content: flex-start;
    /* no scrollbar gutter under a full-bleed strip -- iOS overlays its own */
    scrollbar-width: none;
  }
  .viewthumbs::-webkit-scrollbar {
    display: none;
  }
  #wlCtl,
#ggCtl {
    display: none !important;
  }
}

/* ═══════════════════════════════════════════════════════════════════════════
   MOBILE PDP DRESSING (29 Aug 2026) — announcement bar, phone header, the
   name/reviews block above the image, the swipeable media stage and the
   "See it on your wall" placeholder. All of it exists so the configurator can
   be judged against the live nordicpark.co PDP on a phone.
   ═══════════════════════════════════════════════════════════════════════════ */

.annbar {
  background: #F4EFE9;
  color: #1a1a1a;
  text-align: center;
  /* 46px -> 39px: on a laptop the desktop gallery is sized off whatever vertical
     space the chrome leaves, so every row above the image is height the artwork
     could have had. */
  padding: 10px 16px;
  font-family: 'DM Sans',sans-serif;
  font-size: 14.5px;
}

.annbar span {
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}

/* hamburger + search: the phone header's left cluster, absent on desktop */
.hburger {
  display: none;
  gap: 18px;
  align-items: center;
}

.siteheader {
  border-bottom: 1px solid #EFEDE9;
}

@container np (max-width:900px) {
  /* Phone chrome: burger + search left, wordmark dead centre, account + bag
     right. The wordmark is centred on the HEADER, not on what is left over
     between the two clusters, so it is absolutely positioned. */
  .siteheader {
    position: relative;
    justify-content: space-between;
    padding: 16px 16px;
    gap: 0;
  }
  .hburger {
    display: flex;
  }
  .logo {
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
    font-size: 21px;
    letter-spacing: .3em;
    /* the letter-spacing adds a trailing gap that reads as an off-centre word */
    text-indent: .3em;
    pointer-events: none;
  }
  .hicons {
    gap: 20px;
  }
  .hicons .hsearch {
    display: none;
  }
  .annbar {
    font-size: 14.5px;
    padding: 11px 16px;
  }
}

/* ── name + reviews UNDER the image (mobile only; Ollie 29 Aug evening) ─────
   Desktop keeps the real <h1> in the options column; exactly one of the two is
   ever displayed, which is why this one is a div. Sits inside .gallery between
   the thumbnail strip and the quick rail, so on a phone the order reads image →
   carousel → name/reviews → decisions. The shape sub-line was removed with the
   move — name + reviews only. */
.mobhead {
  display: none;
  padding: 6px 16px 0;
}

.mobtitle {
  font-family: 'Lora',Georgia,serif;
  font-weight: 500;
  font-size: 20px;      /* was 27px above the image — too big under it */
  line-height: 1.2;
  color: #1a1a1a;
  margin: 3px 0 0;
}

@container np (max-width:900px) {
  .mobhead {
    display: block;
  }
  /* Three gaps measured against the live PDP and tightened (29 Aug): header ->
     reviews was 18px, image -> thumbnails 10px, thumbnails -> price 28px. The
     page reads as one block of media now instead of four floating bands. */
  .wrap {
    padding-top: 0 !important;
  }
  /* thumbnails share the stage's 12px inset so the two rows have one left edge.
     The old 3px top was clearance for an OUTER selection ring; the ring is drawn
     inset now, so it bought nothing but a gap. */
  .viewthumbs {
    padding: 0 12px 0 !important;
  }
  .gallery {
    gap: 7px !important;
    padding-bottom: 0 !important;
  }
  .optcol {
    padding-top: 12px !important;
  }
  /* the options column now starts at the price */
  .optcol > .rating,
  .optcol > h1 {
    display: none !important;
  }
}

/* ── the media stage ───────────────────────────────────────────────────────
   Desktop: an inert wrapper. Mobile: a snap track holding [prev][live][next],
   parked on the middle slide, with the next one peeking in from the right so
   the media reads as swipeable at a glance. */
.stage {
  display: block;
}

/* Desktop draws into the authored #wallEl, exactly as v1 always has; the track's
   slides exist but are never shown. */
.wallslide {
  display: none;
}

@container np (max-width:900px) {
  .stage {
    display: flex;
    /* gap == scroll-padding-left is not a coincidence. Parked, the previous
       slide's right edge lands at (scroll-padding-left - gap) on screen, so any
       other pairing leaves a sliver of the previous image down the left edge. */
    gap: 12px;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    /* The 46px right pad is not decoration: with mandatory snapping, a snap point
       the scroller cannot REACH is simply ignored, so without it the last slide's
       start position sits past scrollWidth and a forward swipe springs back to
       the live slide instead of committing. 46 >= clientWidth - slideWidth - 12,
       which is the exact deficit. It is invisible while parked. */
    /* Right pad so the LAST slide's start position is reachable — under mandatory
       snapping a snap point the scroller cannot reach is ignored, and the final
       swipe springs back instead of landing. Needs >= clientWidth - slideWidth
       - scroll-padding-left = 44px; 52 for margin. */
    padding: 0 52px 0 12px;
    /* Must EQUAL padding-left. Neither the resting position of a slide nor the
       previous slide's edge depends on padding-left — but the scroll COORDINATE
       does: a snap point sits at i*step + (padding-left - scroll-padding-left).
       With 14 vs 12 every index the JS computed was two pixels out. */
    scroll-padding-left: 12px;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
    /* the track is swiped, so it must not also pan the page sideways */
    overscroll-behavior-x: contain;
  }
  .stage::-webkit-scrollbar {
    display: none;
  }
  .stage > .wall {
    flex: none;
    /* !important, and it has to be: the mobile .wall rule sets width:100% with a
       flag of its own, and a flex item at 100% of the track would leave nothing
       of the next slide showing. 56px = 12px inset + 12px gap + ~32px peek.
       Measured in cqw (the body container = the page width), NOT %, because a
       percentage resolves against the track's CONTENT box — which shrinks as the
       right pad above grows, making slide width and scroll extent circular. */
    width: calc(100cqw - 56px) !important;
    scroll-snap-align: start;
    border-radius: 2px;
  }
  /* On a phone the TRACK is the image: the generated slides come on and the
     authored wall — which carries the desktop-only tuning panels — steps out of
     the flex row entirely. Exactly one of the two is ever displayed. */
  .wallslide {
    /* flex, NOT block: .wall centres its scene with align/justify-center, and
       overriding the display to block left the artwork sitting at the top-left
       of the port instead of in the middle of it. */
    display: flex;
  }
  #wallEl {
    display: none !important;
  }
}

/* ── "See it on your wall" ────────────────────────────────────────────────
   PLACEHOLDER: styled like the live PDP's button, deliberately inert. */
/* Sized and styled to match .glzhead (the "Wall light ⚙" chip) exactly — same
   12px DM Sans, same 7px/14px padding, same 100px radius, same white fill and
   #DFDFDF hairline, and mirrored on the opposite corner with the same 14/12
   inset as .glzctl. One rule for both breakpoints: it should read identically
   on a phone and on a desktop. */
.wallcta {
  position: absolute;
  right: 14px;
  bottom: 12px;
  z-index: 7;
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 7px 14px;
  border: 1px solid #DFDFDF;
  border-radius: 100px;
  background: #fff;
  font-family: 'DM Sans',sans-serif;
  font-size: 12px;
  color: #1a1a1a;
  white-space: nowrap;
  pointer-events: none;
}

/* ═══════════════════════════════════════════════════════════════════════════
   V2 — THE QUICK RAIL (29 Aug 2026)                       .npc-root.v2, mobile only
   Same build, two URLs: "?v2" (or /v2) adds the class. On desktop v2 and v1 are
   identical — the image and the options are already side by side there, so there
   is nothing to fix.
   The four decisions move directly under the image, one group at a time, so the
   image never leaves the screen when you change one. The detailed option column
   below is hidden rather than duplicated: two live control surfaces for the same
   state is how they drift apart.
   ═══════════════════════════════════════════════════════════════════════════ */

.quickbar {
  display: none;
}

.vswitch {
  position: fixed;
  left: 10px;
  bottom: 10px;
  z-index: 60;
  padding: 7px 11px;
  border-radius: 999px;
  background: #1a1a1a;
  color: #fff;
  font-family: 'DM Sans',sans-serif;
  font-size: 12px;
  font-weight: 600;
  text-decoration: none;
  opacity: .4;
}

.vswitch:hover {
  opacity: 1;
}

@container np (max-width:900px) {
  .npc-root.v2 .quickbar {
    display: block;
    padding: 4px 12px 0;
    /* THE tile size, derived so the four SHAPE tiles exactly fill the rail —
       same 12px inset as the image and the thumbnail strip, three 8px gaps.
       Medium (2) and Frame (5+) inherit it, so a frame swatch is the same size
       as a shape swatch and the row simply scrolls when there are more of them.
       cqw, not %, because the rail's own padding must not feed back into it. */
    --qt: calc((100cqw - 48px) / 4);
  }
  .npc-root.v2 .optgroups {
    display: none;
  }
  /* the rail carries the choices now, so the column below is price -> buy */
  .npc-root.v2 .atc {
    margin-top: 4px;
  }
}

.qbrow {
  display: flex;
  position: relative;      /* offsetLeft of a tile is measured against this row */
  /* flex-start, not center: every tile is the same height now, and centring is
     what put the short size pills lower than the frame tiles. */
  align-items: flex-start;
  gap: 8px;
  /* One tile geometry for all four groups, so the row height cannot change when
     you switch tabs — that shoved the whole page under your thumb. */
  min-height: calc(var(--qt, 62px) + 50px);
  /* 3px side clearance for the selected tile's outer ring (the row clips) */
  padding: 8px 3px 0;
  overflow-x: auto;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}

.qbrow::-webkit-scrollbar {
  display: none;
}

/* Size has no photograph, so its square carries the dimensions as type — but it
   is the SAME square, in the same place, as every other group's swatch. White
   rather than the image ground, so it reads as a control and not a picture. */

.pdetails {
  margin-top: 18px;
  padding-top: 16px;
  border-top: 1px solid #E6E6E6;
  font-family: 'DM Sans',sans-serif;
  font-size: 13.5px;
}

.pdrow {
  display: flex;
  gap: 14px;
  padding: 4px 0;
}

/* ── internal cost table (Ollie 29 Aug evening) ────────────────────────────
   The COGS meta used to be one long sentence; now the numbers are rows —
   Price / Product cost / Shipping / Total cost / COGS% / Margin — with the
   COGS row set off, since that is the figure being read. The descriptive
   materials line stays as prose above it. */
.mtable {
  margin-top: 8px;
  border: 1px solid #E6E6E6;
  border-radius: 8px;
  overflow: hidden;
}

.mrow {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  padding: 5px 10px;
  border-top: 1px solid #EFEFEF;
  font-variant-numeric: tabular-nums;
}

.mrow:first-child {
  border-top: none;
}

.mrow b {
  font-weight: 600;
  color: #1a1a1a;
}

.mrow.cogs {
  background: #F6F4EF;
}

.mrow.cogs b {
  font-size: 14px;
}

.pdk {
  flex: none;
  width: 74px;
  color: #6e6e6e;
}

.pdv {
  color: #1a1a1a;
  font-weight: 500;
}

@container np (max-width:900px) {
  /* Measured 59px of dead space between the last swatch label and this block on
     V2: a fixed tile height (which has to reserve two label lines so the row
     cannot change height between tabs) plus four separate paddings stacked under
     it. The reserved line is structural; the padding chain was not. */
  .pdetails {
    margin-top: 10px;
    padding-top: 12px;
    font-size: 13px;
  }
  .npc-root.v2 .pdetails {
    margin-top: 0;
  }
  .npc-root.v2 .optcol {
    padding-top: 2px !important;
  }
}

/* the zoomed crop is unreadable as a product shot without saying what it is */
.zoomlabel {
  position: absolute;
  left: 50%;
  bottom: 14px;
  transform: translateX(-50%);
  z-index: 6;
  display: none;
  padding: 6px 14px;
  border-radius: 100px;
  background: rgba(26,26,26,.72);
  color: #fff;
  font-family: 'DM Sans',sans-serif;
  font-size: 12px;
  font-weight: 500;
  letter-spacing: .02em;
  white-space: nowrap;
  pointer-events: none;
}

/* ═══ 29 Aug: thumbnails are miniatures of the real views ══════════════════
   Each tile holds a .wall laid out at THUMB_PX and scaled down, painted by the
   same paintWall() as the main port. Two nested transforms: the existing
   `.vth > *` rule scales a 92px box to the tile size (--vtn), and this scales
   the wall inside it to 92. Keeping both means the tile-size machinery did not
   have to change.
   ═══════════════════════════════════════════════════════════════════════════ */

.vth .vthport {
  position: relative;
  overflow: hidden;
  background: #f4f2ee;
  pointer-events: none;
}

.vth .vthwall {
  position: absolute;
  left: 0;
  top: 0;
  /* 320px is a realistic port, so every layout that keys off the measured width
     — the room scale, the size guide, the dimension type — composes exactly as
     it does on a phone, then shrinks uniformly. */
  width: 320px !important;
  height: 320px !important;
  aspect-ratio: auto !important;
  max-width: none !important;
  max-height: none !important;
  min-height: 0 !important;
  margin: 0 !important;
  transform: scale(0.2875);   /* 92 / 320 */
  transform-origin: 0 0;
}

/* The one thing a tile must NOT copy: "See it on your wall" is a control, not
   part of the image, and at 64px it is an unreadable smudge. */
.vth .vthwall .wallcta {
  display: none !important;
}

/* beats `.opts.framegrid .framecard{flex:0 0 104px!important;width:104px!important}`
   on specificity — the cell owns the track width now, the button fills it.
   SHRINKABLE since the row was cut to five frames plus the studio door: six
   cells at a rigid 104 overrun a 648px column by 16px, which brought back the
   scroll arrows the truncation exists to remove. 104 is the ceiling, not the
   width; below ~76 the arrows return, which is the right answer on a narrow
   window. */

/* ═══ dev tuning panels: kept, not shown (29 Aug) ═════════════════════════════
   Ollie: the wall-light and gold-frame editors come off the page, but the
   machinery stays so the effects can be retuned later. Nothing was deleted —
   WL, GG, applyWall(), applyGold(), the presets and the panel markup are all
   still here and still wired. Only this rule hides them.
   TO BRING ONE BACK: delete its selector below. The panel appears in the corner
   of the image port exactly as before, and render() still drives #ggCtl's own
   show/hide (canvas + gold frame), which is why this needs !important.
   ═══════════════════════════════════════════════════════════════════════════ */

#wlCtl,
#ggCtl {
  display: none !important;
}

/* ═══ 29 Aug: square thumbnails ═══════════════════════════════════════════════
   The tile, its clipping port and the selection ring all have to lose the radius
   together — the ring is an inset shadow on .vth and the port clips the wall, so
   rounding any one of them alone leaves a curved edge over a square picture.
   ═══════════════════════════════════════════════════════════════════════════ */

.vth,
.vth::after,
.vth .vthport {
  border-radius: 0 !important;
}

/* ═══ rendered frame swatches (prints) ════════════════════════════════════════
   The piece is drawn at full size and the box shows only its top-left corner —
   the crop IS the swatch. Everything is anchored to that corner so the moulding
   starts exactly at the tile edge in every tile.
   ═══════════════════════════════════════════════════════════════════════════ */

.fswbox {
  display: block;
  position: relative;
  width: 100%;
  height: 100%;
  overflow: hidden;
  background: #F2F2F2;
}

.fswbox .art {
  position: absolute !important;
  left: 0;
  top: 0;
  margin: 0 !important;
  /* the cast-shadow bands hang off the piece's outer edge, which here is the tile
     edge — they would darken the corner they are cropped against */
  box-shadow: none !important;
}

.fswbox .csb,
.fswbox .csr,
.fswbox .csl,
.fswbox .artlight {
  display: none !important;
}

/* ═══ FRAME STUDIO ═══════════════════════════════════════════════════════════
   The modal behind the row's "See all" cell. Two render layers per tile; the
   panel's data-mode picks which is on top and :hover shows the other, so the
   segmented control locks a view rather than being the only way to switch. */

.fslock, .fslock body { overflow: hidden; }

.fstudio { position: fixed; inset: 0; z-index: 60; display: flex;
  align-items: center; justify-content: center; padding: 22px; }
.fstudio[hidden] { display: none; }
.fsback { position: absolute; inset: 0; background: rgba(24,22,18,.62);
  backdrop-filter: blur(3px); -webkit-backdrop-filter: blur(3px);
  animation: fsfade .16s ease; }
@keyframes fsfade { from { opacity: 0 } }
@keyframes fsrise { from { opacity: 0; transform: translateY(10px) } }

.fspanel { position: relative; display: flex; flex-direction: column;
  width: min(1180px, 100%); max-height: 100%; overflow: hidden;
  background: #FCFBF9; border-radius: 14px;
  box-shadow: 0 40px 110px -24px rgba(0,0,0,.55);
  animation: fsrise .2s cubic-bezier(.2,.7,.3,1) both; }

.fshead { display: flex; align-items: center; gap: 18px; flex: none;
  padding: 16px 18px 14px; border-bottom: 1px solid #E8E5DF; }
.fshl { min-width: 0; flex: 1; }
.fshl b { display: block; font-family: 'Lora',Georgia,serif; font-weight: 500;
  font-size: 19px; color: #1a1a1a; letter-spacing: 0; }
.fshl span { display: block; margin-top: 2px; font-size: 12px; color: #7C776E; }

.fsmode { display: flex; flex: none; padding: 2px; gap: 2px;
  background: #EFECE6; border-radius: 9px; }
.fsmode button { border: 0; cursor: pointer; padding: 7px 13px; border-radius: 7px;
  font: inherit; font-size: 12.5px; font-weight: 500; color: #6E695F;
  background: transparent; transition: background .14s, color .14s; }
.fsmode button.on { background: #fff; color: #1a1a1a;
  box-shadow: 0 1px 3px rgba(0,0,0,.10); }

.fsx { flex: none; width: 34px; height: 34px; display: flex; align-items: center;
  justify-content: center; border: 1px solid #E2DFD8; border-radius: 9px;
  background: #fff; color: #4A463F; cursor: pointer; }
.fsx:hover { border-color: #A8A49C; color: #1a1a1a; }

.fsbody { overflow: auto; padding: 4px 18px 18px; -webkit-overflow-scrolling: touch; }
.fsgrp { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
  margin: 20px 0 10px; padding-bottom: 7px; border-bottom: 1px solid #EDEAE4; }
.fsgrp b { font-size: 13px; font-weight: 600; color: #1a1a1a;
  letter-spacing: .04em; text-transform: uppercase; }
.fsgrp span { font-size: 12px; color: #8A857B; }

.fsgrid { display: grid; gap: 12px;
  grid-template-columns: repeat(auto-fill, minmax(148px, 1fr)); }

.fstile { display: block; text-align: left; padding: 0; border: 0; cursor: pointer;
  background: transparent; font: inherit; }
.fspic { position: relative; display: block; width: 100%; aspect-ratio: 1/1;
  overflow: hidden; border-radius: 10px; border: 1px solid #E0DED9;
  background: var(--wl-base,#f6f6f6);
  transition: border-color .13s, box-shadow .13s; }
.fstile:hover .fspic { border-color: #A8A49C; }
.fstile.on .fspic { border-color: #1a1a1a; box-shadow: inset 0 0 0 2px #1a1a1a; }
.fstile:focus-visible .fspic { outline: 2px solid #1a1a1a; outline-offset: 2px; }
.fstile.na { cursor: not-allowed; }
.fstile.na .fspic { opacity: .5; }

/* both layers always drawn; opacity decides, so there is no re-render on hover */
.fswall, .fscorner { position: absolute; inset: 0; display: flex;
  align-items: center; justify-content: center;
  transition: opacity .17s ease; }
.fstex { position: absolute; inset: 0; pointer-events: none;
  mix-blend-mode: overlay; background-image: var(--fstex);
  background-size: 130px 130px; }
.fswall .art { position: relative; z-index: 1; }
.fscorner { background: #F2F2F2; }
.fspanel[data-mode="wall"]   .fscorner { opacity: 0; }
.fspanel[data-mode="corner"] .fswall   { opacity: 0; }
@media (hover: hover) {
  .fspanel[data-mode="wall"]   .fstile:hover .fscorner { opacity: 1; }
  .fspanel[data-mode="wall"]   .fstile:hover .fswall   { opacity: 0; }
  .fspanel[data-mode="corner"] .fstile:hover .fswall   { opacity: 1; }
  .fspanel[data-mode="corner"] .fstile:hover .fscorner { opacity: 0; }
}

.fstick { position: absolute; left: 8px; top: 8px; z-index: 3; padding: 3px 8px;
  border-radius: 20px; background: #1a1a1a; color: #fff; font-size: 10.5px;
  font-weight: 600; letter-spacing: .03em; }

.fsname { display: block; margin-top: 8px; font-size: 13.5px; font-weight: 500;
  color: #1a1a1a; }
.fsmeta { display: flex; justify-content: space-between; gap: 8px; margin-top: 1px;
  font-size: 12px; color: #8A857B; }
.fsmeta b { font-weight: 500; color: #4A463F; }

.fsfoot { flex: none; padding: 11px 18px; border-top: 1px solid #E8E5DF;
  background: #F7F5F1; }
.fshint { font-family: 'IBM Plex Mono',monospace; font-size: 11px; color: #8A857B; }

@container np (max-width:900px) {
  .fstudio { padding: 0; }
  .fspanel { width: 100%; max-height: 100%; height: 100%; border-radius: 0; }
  .fshead { flex-wrap: wrap; gap: 10px 12px; padding: 13px 14px 11px; }
  .fshl { flex: 1 1 100%; order: 1; }
  .fsmode { order: 2; flex: 1; }
  .fsmode button { flex: 1; }
  .fsx { order: 3; }
  .fsbody { padding: 4px 14px 16px; }
  .fsgrid { gap: 10px; grid-template-columns: repeat(auto-fill, minmax(132px, 1fr)); }
}

/* ═══ LIGHTBOX (29 Aug evening) ═══════════════════════════════════════════════
   Tap/click the image to inspect it full screen. #lbWall is a real .wall painted
   by paintWall() at full-screen size; zoom/pan is a transform on top of that
   bigger render. The id selector out-guns the .wall !important sizing rules
   (id specificity beats class at equal importance), which is what lets one more
   wall exist at a size none of the layout rules anticipated. */

.lightbox { position: fixed; inset: 0; z-index: 80; display: flex;
  align-items: center; justify-content: center;
  background: rgba(24,22,18,.93);
  touch-action: none;               /* the viewer owns every gesture inside it */
  overscroll-behavior: contain; cursor: grab; }
.lightbox[hidden] { display: none; }

#lbWall { width: min(94vw,94vh) !important; height: min(94vw,94vh) !important;
  aspect-ratio: auto !important; max-width: none !important; max-height: none !important;
  margin: 0 !important; flex: none; will-change: transform; }

/* "See it on your wall" is a control, not part of the image — same rule as the
   thumbnail miniatures */
.lbwall .wallcta { display: none !important; }

.lbx { position: fixed; top: 14px; right: 14px; z-index: 82; width: 40px; height: 40px;
  display: flex; align-items: center; justify-content: center;
  border: none; border-radius: 50%; background: rgba(252,251,249,.92); color: #1a1a1a;
  cursor: pointer; box-shadow: 0 2px 10px rgba(0,0,0,.3); }
.lbx:hover { background: #fff; }

.lbhint { position: fixed; left: 50%; bottom: 16px; transform: translateX(-50%); z-index: 82;
  padding: 6px 14px; border-radius: 100px; background: rgba(26,26,26,.6); color: #fff;
  font-family: 'DM Sans',sans-serif; font-size: 12px; white-space: nowrap; pointer-events: none; }

/* the image ports advertise the tap */
#wallEl, .wallslide { cursor: zoom-in; }

/* ═══════════════════════════════════════════════════════════════════════════
   SELECTION CARDS (30 Aug 2026) — ONE card system for all four selectors on
   both viewports. Replaces the four separately-evolved tile systems (square
   frame swatches with a name pill overlaid on the photo, borderless shape
   tiles, horizontal medium cards, text-pill sizes) — their rules were REMOVED,
   not overridden, so this section is the single authority on option styling.
   Anatomy: .cardimg on top (the image never sits under text), .cardmeta strip
   with name + price below, warm ring + cream fill on the selected card.
   Desktop/v1 (.opt) and the V2 rail (.qtile) share every base rule.
   ═══════════════════════════════════════════════════════════════════════════ */

.npc-root {
  --card-line: #E4E1DB;   /* resting border */
  --card-sel: #C99B5B;    /* selected ring — warm oak from the frame palette */
  --card-selbg: #FBF4E9;  /* selected fill */
}

.opt,
.qtile {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  position: relative;
  padding: 0;
  margin: 0;
  background: #fff;
  border: 1px solid var(--card-line);
  border-radius: 12px;
  overflow: hidden;
  cursor: pointer;
  font-family: 'DM Sans', sans-serif;
  color: #1a1a1a;
  text-align: center;
  transition: border-color .13s, background-color .13s, box-shadow .13s;
}

@media (hover: hover) {
  .opt:hover,
  .qtile:hover {
    border-color: #B9B3A9;
  }
}

.opt:focus-visible,
.qtile:focus-visible {
  outline: 2px solid #1a1a1a;
  outline-offset: 2px;
}

/* POPULAR / NEW pills: inside the tile's top-right corner (the tile clips overflow, so a
   straddling badge like the live theme's would be cut), same ink and case as the live badge */

.npc-badge {
  position: absolute;
  top: 6px;
  right: 6px;
  z-index: 3;
  padding: 2px 7px;
  border-radius: 6px;
  background: #EFECE5;
  color: #3a3a3a;
  font-family: 'DM Sans', sans-serif;
  font-size: 10px;
  font-weight: 500;
  letter-spacing: .05em;
  text-transform: uppercase;
  line-height: 1.4;
  font-style: normal;
  pointer-events: none;
  white-space: nowrap;
}

/* a size tile is two centred lines, so the pill needs its own row: the tile grows and the
   rest of the row stretches to match (flex), keeping the ladder aligned */

.opts.sizegrid .sizecard:has(.npc-badge) {
  padding-top: 24px !important;
}

.opts.sizegrid .sizecard .npc-badge {
  top: 5px;
  left: 50%;
  right: auto;
  transform: translateX(-50%);
}

.npc-badge-new {
  background: #B8933F;
  color: #fff;
}

/* selected: 1px border + 1px ring reads as a crisp 2px with zero layout shift */
.opt.on,
.qtile.on {
  border-color: var(--card-sel);
  box-shadow: 0 0 0 1px var(--card-sel);
  background: var(--card-selbg);
}

.on .cardmeta b {
  font-weight: 700;
}

.framecard.navail,
.qtile.qna {
  opacity: .45;
  cursor: not-allowed;
}

/* ── image area: fills the card edge to edge ── */
.cardimg {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  aspect-ratio: 1 / 1;
  flex: none;
  overflow: hidden;
  background: #F5F4F1;
}

.cardimg > img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.cardimg .sw {            /* colour-chip fallback for a frame with no photo yet */
  width: 100%;
  height: 100%;
}

/* a live piece render inside a card gets a soft product shadow, nothing more */
.cardimg .art {
  margin: 0 !important;
  box-shadow: 0 2px 5px -2px rgba(40,35,25,.42), 0 1px 1px rgba(40,35,25,.14) !important;
}

.cardimg .bevel::before {
  box-shadow: inset 1px 1px 1px rgba(120,110,90,.25);
}

.qart {
  display: flex;
  align-items: center;
}

/* ── label strip ── */
.cardmeta {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  flex: 1;
  padding: 7px 6px 8px;
  line-height: 1.2;
}

.cardmeta b {
  font-size: 12.5px;
  font-weight: 600;
  overflow-wrap: anywhere;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* frame price difference: keeps its line even when empty so base-priced tiles stay the
   same height as "+$40" ones */
.cardmeta .fdelta { min-height: 1.2em; }
.cardmeta .fdelta:not(:empty) { color: #1a1a1a; font-weight: 500; }
.cardmeta span {
  font-size: 12px;
  color: #6e6e6e;
  font-variant-numeric: tabular-nums;
}

/* ── desktop / v1 column ── */

/* frame row: cards shrink a little before the scroll arrows appear */
/* ONE fixed width for every frame tile, whichever medium. With flex-grow the five
   canvas tiles filled the row at the 122px max while the fourteen print tiles were
   squeezed to the 96px min in their scroller — same tile, 27% different size, and
   since the image is width-driven the print frames read a third smaller (Ollie,
   2 Sep, with screenshots). Canvas gives up nothing: 5 x 122 + gaps ~ the row anyway. */
.opts.framegrid .framecard {
  flex: 0 0 122px;
  min-width: 122px;
  max-width: 122px;
}

.opts.shapegrid {
  grid-template-columns: repeat(4, minmax(0, 1fr)) !important;
}

@container np (min-width:901px) {
  .opts.shapegrid {
    max-width: 560px !important;
  }
}

/* medium: two wide cards; the render area is a SHORT landscape box so the card
   stays compact — the JS measure pass fits the piece to width AND height */
.medcard {
  flex: 1 1 0;
  min-width: 0;
}

.medcard .cardimg {
  aspect-ratio: auto;
  height: 96px;
}

.medcard .mdesc {
  font-size: 11.5px;
  font-weight: 400;
  line-height: 1.35;
  color: #6e6e6e;
  display: block;
}

/* size: text-only cards — dims + price centred, no fake image area */
.sizecard {
  justify-content: center;
  gap: 3px;
  padding: 13px 10px;
}

.sizecard b {
  font-size: 14.5px;
  font-weight: 600;
  line-height: 1.25;
}

.sizecard .sprice {
  font-size: 12.5px;
  color: #6e6e6e;
  font-variant-numeric: tabular-nums;
}

@container np (max-width:900px) {
  .sizecard b { font-size: 13px; }
  .sizecard .sprice { font-size: 12px; }
}

/* the frame row's last card: a live 2×2 of the next four mouldings behind the
   Frame Studio door, veiled with the count */
.fmgrid {
  position: absolute;
  inset: 0;
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: 1fr 1fr;
  gap: 1px;
  background: var(--card-line);
}

.fmgrid .fswbox {
  width: 100%;
  height: 100%;
}

.fmveil {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1px;
  background: rgba(252,251,249,.66);
  transition: background .13s;
}

@media (hover: hover) {
  .framecard.fmore:hover .fmveil {
    background: rgba(252,251,249,.46);
  }
}

.fmnum {
  font-size: 15px;
  font-weight: 600;
  line-height: 1;
}

.fmcap {
  font-size: 10px;
  font-weight: 500;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: #6E695F;
}

/* ── V2 rail ── */

/* tabs: a segmented control, active tab = raised white pill (no numbers) */
.qbtabs {
  display: flex;
  gap: 4px;
  padding: 3px;
  background: #F3F1ED;
  border-radius: 999px;
}

.qbtab {
  flex: 1;
  padding: 8px 4px;
  border: none;
  background: transparent;
  border-radius: 999px;
  font-family: 'DM Sans', sans-serif;
  font-size: 13px;
  font-weight: 500;
  color: #8B867C;
  cursor: pointer;
  transition: color .13s;
}

.qbtab.on {
  background: #fff;
  color: #1a1a1a;
  font-weight: 700;
  box-shadow: 0 1px 5px rgba(30,25,15,.10), 0 0 0 1px #ECE9E3;
}

/* one fixed card height for every group, so switching tabs cannot change the
   row height and shove the page under your thumb (the .qbrow min-height above
   is derived from this: image square + 42px strip + borders + row padding) */
.qtile {
  flex: none;
  width: var(--qt, 74px);
  height: calc(var(--qt, 74px) + 42px);
}

.qtile .cardimg {
  aspect-ratio: auto;
  height: calc(var(--qt, 74px) - 2px);
}

.qtile .cardmeta {
  padding: 4px 3px 5px;
  gap: 1px;
}

.qtile .cardmeta b { font-size: 11px; }
.qtile .cardmeta span { font-size: 10.5px; }

/* size tab: dims + price centred in the same fixed-height card */
.qtile.qsize {
  justify-content: center;
  gap: 3px;
  padding: 0 4px;
}

.qtile.qsize b {
  font-size: 12.5px;
  font-weight: 600;
  line-height: 1.25;
}

.qtile.qsize span {
  font-size: 11px;
  color: #6e6e6e;
  font-variant-numeric: tabular-nums;
}

/* ═══ theme integration (build-section.mjs) ═══ */
.npc-root { width: 100%; }
/* NB: .npc-root IS the np container, so it cannot be restyled from inside the np container query — an element
   never matches a query against its own container. Custom props the desktop rules read live out here. */
.npc-root { --npc-gal: min(calc(100vh - var(--galres, 140px)), 1400px); --npc-buy: 620px; --npc-buymin: 430px;
            --npc-rail: 88px; --npc-railgap: 12px;
            --npc-galfull: calc(var(--npc-gal) + var(--npc-rail) + var(--npc-railgap)); }   /* square + thumbnail rail */
.npc-root .wrap { max-width: 1600px; }
.npc-root .atc[disabled] { opacity: .45; cursor: default; }
/* Live-PDP geometry (measured 2026-09-07): 1600px centred container with 40px margins, the gallery is a
   square sized by the viewport height, the buy column takes the rest with a 430px content width. */
@container np (min-width:901px) {
  /* Geometry copied from the live PDP, measured 2026-09-07: a 1600px content column inside 40px
     margins — the SAME box the theme header uses, so the image can never overhang it. Inside that box
     the live page lets the media column take what the gallery needs and hands the remainder to the buy
     column, which flexes between 430 and 620 (live at 2560x1440: media 1170 / buy 430). Same rule here:
     the media track is the height-sized square, capped so the buy column always keeps its 430px floor. */
  .npc-root .wrap { max-width: 1680px !important; margin: 0 auto; padding: 0 40px 70px !important; }   /* flush under the header (Ollie, 2026-09-08) */
  /* Buy column = 32% of the 1600px container once the square is WIDTH-bound (Ollie, 2026-09-09: on a
     2560 monitor the media track grew to the 430px floor and the buttons were cramped while a laptop
     got 525px). 430px still holds below ~1345px wide; from 1680px up the column is 512px. Declared on
     .cols and .gallery, not .npc-root: cqw cannot be read by the container itself, and 100cqw is the
     SECTION width, so the wrap's 1680/40 box is restated here (same maths as --npc-track). */
  .npc-root .cols, .npc-root .gallery { --npc-buymin: max(430px, calc((min(1680px, 100cqw) - 80px) * 0.32)); }
  /* No "Size guide" button beside the Size label on desktop (Ollie 2026-09-19): the guide is its own
     slide in the carousel here, so the button was a second door to a room you are already looking at.
     It STAYS on phones — there the guide is not a slide, and that button is the only way into the
     full-screen sheet. Hiding it, not deleting it, for exactly that reason. */
  /* 2026-10-03 (Ollie): the button is BACK on desktop — it jumps the gallery to the size-guide slide (npcSzsGo). */
  .npc-root .cols { grid-template-columns: min(var(--npc-galfull), calc(100% - var(--npc-buymin) - 36px)) minmax(var(--npc-buymin), var(--npc-buy)) !important; gap: 36px !important; align-items: start; justify-content: start !important; max-width: none !important; }
  /* justify-content: START. The gallery has to line up with the theme header's container - the
     logo - and 'end' silently pushed the whole grid right on any screen where the tracks did not
     fill 1600 (short + wide, e.g. 1920x900: 51px of drift). Slack now sits to the right of the
     buy column, at the page edge, and the 36px gutter is unchanged either way. */
  /* Thumbnails move to a rail on the LEFT (2026-09-08). The strip used to eat ~96px of the
     square's height; as a column it costs width the media track was already given, so the
     artwork grows. .stage keeps its 1:1 by taking the flex remainder. */
  /* The square used to BE the gallery height and ran flush 81->834, while the rail's 6px inset
     put the top and bottom tiles at 87 and 828 — so the image overshot the thumbnails it sits
     beside by 6px at each end. The square now takes the same 6px inset and the rail is left
     exactly where it was (Ollie, 2026-09-08).
     --npc-sq restates the grid's own track maths (min of the height-sized square and what is
     left after the 430px buy floor + 36px gutter inside a 1680px wrap) because the square can
     no longer derive its size from the flex remainder — it has to be known BEFORE the rail
     stretches to it. Declared here rather than on .npc-root: cq units cannot be read by the
     container itself. */
  .npc-root .gallery { --npc-track: min(var(--npc-galfull), min(1680px, 100cqw) - 116px - var(--npc-buymin));   /* 116 = 36 gutter + 80 wrap padding; buymin was a hardcoded 430 inside 546 */
                       --npc-sq: calc(var(--npc-track) - var(--npc-rail) - var(--npc-railgap));
                       width: min(var(--npc-galfull), 100%) !important; max-width: none !important; margin: 0 !important;
                       height: var(--npc-sq);
                       flex-direction: row !important; align-items: flex-start; gap: var(--npc-railgap) !important; }
  .npc-root .gallery > .stage { order: 2; flex: 0 0 auto; width: auto; height: calc(var(--npc-sq) - 12px);
                                margin-block: 6px; min-width: 0; aspect-ratio: 1 / 1; }
  /* The rail stretches to the image's height and distributes its tiles across it, so it never
     stops short with a block of dead space under it. npcRail() below sizes the tiles to fill. */
  .npc-root .viewthumbs { order: 1; flex-direction: column !important; flex: 0 0 var(--npc-rail); width: var(--npc-rail);
                          align-self: stretch; justify-content: space-between;
                          overflow-y: auto; overflow-x: hidden; scrollbar-width: none;
                          padding: 6px 0 !important; margin: 0 !important; gap: var(--npc-railgap) !important; }   /* 6px so the top tile is not jammed against the header, while the image stays flush */
  .npc-root .viewthumbs::-webkit-scrollbar { display: none; }
  .npc-root .npc-nav { --npc-thumbblk: 0px; }   /* the square is the whole gallery height now */
  /* ...and the arrows are positioned against .gallery, which now starts at the RAIL. Without
     this the prev arrow sat over the thumbnails instead of over the image. */
  .npc-root .npc-nav-prev { left: calc(var(--npc-rail) + var(--npc-railgap) + 16px) !important; }   /* !important: the global .npc-nav-prev rule is later in the sheet */
  .npc-root .optcol { max-width: 620px; padding-top: 8px; }   /* the image runs flush to the header, the text should not */
  /* tile unit follows the viewport height so the whole stack fits under the fold on a laptop too */
  .npc-root { --npc-tile: clamp(56px, calc((100vh - 560px) / 4), 84px); }
  /* one step per line: shape chips (4-up), medium toggle, frame carousel, size cards */
  /* As many equal columns as there ARE shapes, and no 480px cap on either row: a hardcoded
     repeat(4) left a quarter of the shape row empty on a 3-shape product, and the cap left
     another 100px of the buy column unused on wide screens (Ollie, 2026-09-08). */
  .npc-root .opts.shapegrid { grid-template-columns: repeat(var(--npc-shpcols, 4), minmax(0, 1fr)) !important; grid-auto-flow: row !important; gap: 8px !important; max-width: none !important; }
  .npc-root #medOpts { max-width: none; }
  /* Desktop: FOUR equal columns on every product, filled left to right, chips never stretch — a short last
     row leaves space (Ollie 2026-09-29, "Idea 2": one chip format everywhere; 5 sizes = 4 + 1, 6 = 4 + 2, 8 = 4 + 4).
     Replaces the 2026-09-08 per-product column count (ceil(n / ceil(n/5)) + grow), which made a 5-size product
     look like a different page from a 6-size one. */
  .npc-root .opts.sizegrid { display: flex !important; flex-wrap: wrap !important; gap: 8px !important; --npc-szcols: 4; }
  /* frames: a horizontal carousel of big photo tiles (5 visible in the 620px column), arrows page it */
  .npc-root .opts.framegrid { flex-wrap: nowrap !important; overflow-x: auto !important; gap: 8px !important; }
  /* Fixed 116px, always: the strip SCROLLS (arrows page it). Letting them flex to fit turned the
     14 framed-print mouldings into unreadable slivers with truncated names — Ollie, 2026-09-07.
     88px so all five canvas frames still fit once the left thumbnail rail narrows the buy column. */
  /* FIVE tiles per view, always. Sizing by px made canvas tiles (5, so they grew to fill) larger
     than print tiles (14, so they stayed at the basis) — the same control changing size when you
     switched medium. A fifth of the strip is the same width whatever the frame count, so canvas
     and print now match at every column width; print simply scrolls. */
  .npc-root .opts.framegrid .framecard { flex: 0 0 calc((100% - 32px) / 5) !important; min-width: 0 !important; max-width: none !important; }
  /* The swatch stays SQUARE (Ollie's call — a landscape crop of a frame corner is wrong). The
     height came out of the caption instead: two reserved lines at 11px rather than a wrapping
     11.5px name, so 156px -> ~138px and every tile in the row matches. */
  .npc-root .opts.framegrid .framecard .cardmeta { padding: 4px 4px 6px !important; gap: 0 !important; }
  /* Two lines reserved for the name, always. nowrap fitted "Stretched Canvas" but would have
     truncated "Plein Air Espresso-Gold" on the print list, and letting it wrap freely makes the
     canvas row and the print row different heights. A fixed two-line box does neither. */
  .npc-root .opts.framegrid .framecard .cardmeta b { font-size: 11px; line-height: 1.25; letter-spacing: -.01em; min-height: 2.5em; display: flex; align-items: center; justify-content: center; text-align: center; }
  .npc-root .opts.framegrid .framecard .cardmeta span { font-size: 11px; line-height: 1.2; }
  .npc-root .glabel { margin: 14px 0 8px !important; }
  /* header like the live page: rating, title, then a hairline spacer before the steps */
  .npc-root .optcol h1 { font-size: 32px !important; line-height: 1.15; margin: 2px 0 0 !important; padding-bottom: 16px; border-bottom: 1px solid #ECE9E3; }   /* 32 = the live PDP's title size (2026-09-09, was 24) */
  .npc-root .optcol > .optgroups { margin-top: 6px; }
  .npc-root .np-top-review { margin-bottom: 2px; }
  .npc-root .vth { --vtn: var(--npc-thumb, 88) !important; }   /* set by npcRail() from the view count */

}
/* selection = dark outline; unselected = light grey line; no tinted fill */
.npc-root { --card-sel: #1a1a1a; --card-selbg: #ffffff; --card-line: #E4E1DB; }
.npc-root .opt.on, .npc-root .qtile.on { border-color: #1a1a1a !important; box-shadow: 0 0 0 1px #1a1a1a !important; background: #fff !important; }
/* rounded gallery + thumbnails, like the live media gallery */
/* thumbnails: rounded with a mask so the transform-scaled mini wall is clipped cleanly at the corners
   (overflow:hidden alone leaves notches); the selected ring is an inset overlay that follows the radius */
.npc-root .vth { position: relative; border-radius: 6px !important; overflow: hidden; isolation: isolate; -webkit-mask-image: -webkit-radial-gradient(#fff, #000); mask-image: radial-gradient(#fff, #000); box-shadow: none !important; outline: none !important; }
/* ONE thin ring, flush with the rounded edge (the base's 2px inset ring over a square mini-wall read as a square inside a rounded box) */
.npc-root .vth::after { content: "" !important; position: absolute !important; inset: 0 !important; border-radius: 6px !important; border: 0 !important; box-shadow: inset 0 0 0 1px #E0DED9 !important; background: none !important; pointer-events: none; z-index: 5; }
.npc-root .vth.on::after { box-shadow: inset 0 0 0 1.5px #1a1a1a !important; }
.npc-root .vth:hover::after { box-shadow: inset 0 0 0 1.5px #A8A49C !important; }
.npc-root .vth.on:hover::after { box-shadow: inset 0 0 0 1.5px #1a1a1a !important; }
/* Round arrows over the main image, on every screen. They hang off .gallery (see the
   markup note) and .gallery is flex-column: square, gap, thumb strip. So the square's
   midpoint is (gallery height - the thumb block) / 2, and --npc-thumbblk carries that
   block's height per breakpoint — the one number to update if the thumbnails change.
   z-index 30, not 8: the phone slides carry z-index 20 and buried them. */
/* NB no position declaration here: desktop keeps the base rule's sticky (already a containing
   block for the arrows). Forcing relative here also applied the base's top:12px unconditionally
   and pushed the whole gallery 12px down the grid. Phones get relative in their own block. */
.npc-root .npc-nav { position: absolute; top: calc((100% - var(--npc-thumbblk, 96px)) / 2); transform: translateY(-50%); z-index: 30; width: 38px; height: 38px; border-radius: 50%; border: 0; background: rgba(255,255,255,.55); -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px); color: #1a1a1a; box-shadow: 0 1px 6px rgba(0,0,0,.10); display: flex; align-items: center; justify-content: center; cursor: pointer; padding: 0; }
.npc-root .npc-nav svg { width: 16px; height: 16px; }
/* wall-preview pill: the prototype drew it inert (pointer-events:none); here it opens the modal */
.npc-root .npc-wallcta { pointer-events: auto; cursor: pointer; font-family: 'DM Sans', sans-serif; transition: background .15s ease, box-shadow .15s ease; box-shadow: 0 1px 6px rgba(0,0,0,.10); }
.npc-root .npc-wallcta:hover { background: #fff; box-shadow: 0 2px 10px rgba(0,0,0,.18); }
/* "New in" corner badge (2026-10-06, Ollie: option A hairline, flush to the picture's top-left). Lives inside #wallEl (position:relative),
   shown only on image 1 (.wall.productview, set by paintStageWindow), hidden while the picture is zoomed. Rendered by Liquid only while the product carries OCT26. */
.npc-root .npc-newin { display: none; position: absolute; top: 0; left: 0; z-index: 6; padding: 5px 8px 4px; font-family: 'DM Sans', sans-serif; font-size: 11px; font-weight: 500; line-height: 1; letter-spacing: .09em; text-transform: uppercase; color: #1a1a1a; background: rgba(255,255,255,.92); border: 1px solid rgba(26,26,26,.28); border-top: 0; border-left: 0; border-radius: 0 0 3px 0; pointer-events: none; }
.npc-root .wall.productview .npc-newin { display: block; }
#wallEl.npc-izoom .npc-newin { opacity: 0; }
.npc-root .vth .npc-newin, .npc-root .lbwall .npc-newin { display: none !important; }   /* SCENE_HTML also builds the thumbnail strip and the lightbox wall: no badge there */
@container np (max-width:900px) { .npc-root .npc-newin { padding: 4px 6px 3px; font-size: 10px; } }
/* desktop: the prototype's 12px chip read as a footnote on a 1100px picture (Ollie, 14 Sep) */
@container np (min-width:901px) {
  .npc-root .stage .npc-wallcta { font-size: 14px; font-weight: 500; padding: 10px 18px; gap: 8px; right: 16px; bottom: 16px; }
  .npc-root .stage .npc-wallcta svg { width: 16px; height: 16px; }
}
/* Lightbox browsing: dots always, arrows only where there is a pointer to hover with. */
.npc-root .lbdots, .lightbox .lbdots { position: fixed; left: 50%; transform: translateX(-50%); bottom: 44px; z-index: 82; display: flex; gap: 2px; }
.lightbox .npc-dot::before { background: rgba(255,255,255,.42); box-shadow: 0 0 0 1px rgba(0,0,0,.18); }
.lightbox .npc-dot.on::before { background: #fff; transform: scale(1.25); }
.lbnav { position: fixed; top: 50%; transform: translateY(-50%); z-index: 82; width: 44px; height: 44px; border: 0; border-radius: 50%; background: rgba(255,255,255,.62); -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px); color: #1a1a1a; display: none; align-items: center; justify-content: center; cursor: pointer; padding: 0; }
.lbnav:hover { background: #fff; }
.lbnav-prev { left: 18px; }
.lbnav-next { right: 18px; }
/* Desktop viewer, UX pass (Ollie 2026-09-24, 27" screenshot): the picture leaves a 72px strip below it for the dots
   and hint (they sat ON the artwork); arrows and close are placed beside the picture by lbChrome() (they sat at the
   screen edges, ~500px away on a wide monitor); the page behind no longer shows through; the grab hand only over the
   picture (a click on the backdrop closes, so it gets zoom-out); the hint fades after a few seconds. */
@media (min-width: 901px) {
  .lightbox { background: rgba(20,19,16,.96); -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px); padding-bottom: 72px; cursor: zoom-out; }
  #lbWall { width: min(94vw, calc(94vh - 72px)) !important; height: min(94vw, calc(94vh - 72px)) !important; cursor: grab; }
  .lightbox .lbdots { bottom: 34px; }
  .lbhint { bottom: 8px; background: none; color: rgba(255,255,255,.55); transition: opacity .6s ease; }
  .lbhint.gone { opacity: 0; }
  .lbx { transition: none; }
  .lbnav { background: rgba(252,251,249,.92); box-shadow: 0 2px 10px rgba(0,0,0,.3); }
  .lbzoom { position: fixed; z-index: 82; width: 40px; height: 40px; display: flex; align-items: center; justify-content: center; border: 0; border-radius: 50%; background: rgba(252,251,249,.92); color: #1a1a1a; cursor: pointer; box-shadow: 0 2px 10px rgba(0,0,0,.3); }
  .lbzoom:hover, .lbzoom.on { background: #fff; }
  .lbzoom.on svg path { d: path("M15.5 15.5L20 20M7.5 10.5h6"); }   /* minus when zoomed */
  #lbWall { cursor: zoom-in; }
  .lightbox.zoomed #lbWall { cursor: zoom-out; }   /* solid, like the close button: at .62 they read as disabled on the dark backdrop */
}
@media (hover: hover) and (pointer: fine) { .lbnav { display: flex; } }
/* desktop: no lightbox (2026-10-03) — click zooms the picture IN PLACE (npcIzoom) */
@media (hover: hover) and (pointer: fine) {
  html #wallEl { cursor: zoom-in; } html #wallEl.npc-izoom { cursor: zoom-out; } html #wallEl .sizescene { cursor: auto; }
  /* the WHOLE picture scales as one piece (its layers carry their own position transforms) and the stage, which is
     exactly the picture's box, clips it */
  html #wallEl { transition: transform .28s ease; transform-origin: var(--npc-zx, 50%) var(--npc-zy, 50%); }
  html #wallEl.npc-izoom { transform: scale(2); }
  .npc-izclip { overflow: hidden !important; }
  #wallEl .wallcta { transition: opacity .2s; } #wallEl.npc-izoom .wallcta { opacity: 0; pointer-events: none; }
}
.lbzoom { display: none; }
@media (hover: hover) and (pointer: fine) and (min-width: 901px) { .lbzoom { display: flex; } }
/* Phones get dots instead of arrows (Ollie, 2026-09-08): two 32px discs sat on top of the
   artwork, a dot row sits over its bottom edge and costs no height at all — the row is a
   zero-height flow element whose margin-top cancels the 6px gallery gap it would add. */
.npc-root .npc-dots { display: none; }
.npc-root .npc-dot { flex: 0 0 auto; width: 22px; height: 22px; padding: 0; border: 0; background: none; display: flex; align-items: center; justify-content: center; cursor: pointer; }
.npc-root .npc-dot::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: rgba(26,26,26,.26); box-shadow: 0 0 0 1px rgba(255,255,255,.55); transition: background .15s ease, transform .15s ease; }
.npc-root .npc-dot.on::before { background: #1a1a1a; transform: scale(1.25); }
.npc-root .npc-nav-prev { left: 16px; }
.npc-root .npc-nav-next { right: calc(16px + var(--npc-wallright, 0px)); }   /* measured by npcNavPlace(): same inset as the left arrow */
.npc-root .npc-nav:hover { background: rgba(255,255,255,.92); }
/* 6 (Ollie 2026-09-17): bigger, plainly interactive */
.npc-root .npc-nav { width: 46px; height: 46px; background: rgba(255,255,255,.85); box-shadow: 0 1px 8px rgba(0,0,0,.14); }
.npc-root .npc-nav svg { width: 20px; height: 20px; }
.npc-root .npc-nav:hover { background: #fff; box-shadow: 0 3px 14px rgba(0,0,0,.18); }
@container np (max-width:900px) {
  .npc-root .npc-nav { display: none !important; }
  /* the phone slide is inset 12px left and peeks 32px on the right, so the same padding here
     centres the row on the SLIDE rather than 10px off it, on the gallery's centre */
  /* Ollie 14 Sep: no position dots on the phone gallery at all (the lightbox keeps its own). The JS still
     writes them; they are simply never shown. */
  .npc-root .npc-dots { display: none !important; }
  /* smaller, tighter (Ollie, 9 Sep): 4px dots on 14px hit boxes, sitting 4px off the slide's edge */
  .npc-root .npc-dot { width: 14px; height: 14px; margin-bottom: 4px; }
  .npc-root .npc-dot::before { width: 4px; height: 4px; }
}
/* size cards like the live site: dimensions large on top, price underneath, every screen */
.npc-root .sizecard { flex-direction: column !important; height: auto !important; padding: 11px 6px !important; gap: 3px !important; justify-content: center; }
/* every screen: an equal share of the row, NO grow — every chip on the page is the same width (2026-09-29) */
.npc-root .opts.sizegrid .sizecard { flex: 0 0 calc((100% - (var(--npc-szcols, 4) - 1) * 8px) / var(--npc-szcols, 4)) !important; min-width: 0 !important; max-width: none !important; }
.npc-root .sizecard b { font-size: 15px !important; white-space: nowrap; }
.npc-root .opts.sizegrid.wide .sizecard b { font-size: 14px !important; letter-spacing: -.02em; }   /* sets: "25 × 34″ each" on ONE line in a 128px chip (16px folded "each" to a third line; the price is the set price and must stay plain) */
.npc-root .sizecard .sprice { display: block !important; font-size: 12.5px !important; }
/* buy-column tail: order facts, trust bar, accordions (markup mirrors the live template blocks) */
.npc-root .npc-facts { display: flex; flex-direction: column; gap: 6px; margin-top: 12px; padding: 0 2px; font-family: 'DM Sans', -apple-system, sans-serif; font-size: 13px; line-height: 1.5; color: #5f5b53; }
.npc-root .npc-fact { display: flex; align-items: flex-start; gap: 10px; }
.npc-root .npc-fact svg { flex: 0 0 16px; width: 16px; height: 16px; margin-top: .2em; }
/* ═══ Trust card (2026-09-09, v2): three cells on the buy column's surface colour, directly under
   Add to cart on every screen. Rated x/5 (the Loox numbers the top-of-column stars already computed),
   20,000+ happy customers, Made in the USA. Desktop: icon beside two lines, hairline dividers between
   equal thirds. Phones: icon above centred text so three fit across a 358px column. An earlier cut
   (same day) sat this under the TITLE as a bare icon row with "Free FedEx shipping" — moved down on
   Ollie's call, and the FedEx cell went because the button already prints the delivery date. */
/* columns weighted to their content (FedEx cell is the widest) so nothing runs under a divider at the 525px
   laptop column; the text wrapper has min-width:0 so a squeezed cell ellipsizes instead of overflowing */
.npc-root .npc-trust { display: grid; grid-template-columns: minmax(0, 1.3fr) minmax(0, 1.02fr) minmax(0, 1.1fr); margin-top: 16px; padding: 12px 4px; background: #F8F7F4; border-radius: 10px; font-family: 'DM Sans', -apple-system, sans-serif; }
.npc-root .npc-trust-item { display: flex; align-items: center; gap: 9px; min-width: 0; padding: 2px 8px; }
.npc-root .npc-trust-item > div { min-width: 0; flex: 1 1 auto; }
/* The buy column runs from 430px (tall monitors) to 620px. Below ~500px three icon-beside-text cells
   cannot hold "Free FedEx shipping" without cutting it, so the card queries ITS OWN width and switches
   to icon-over-text (the phone layout) with slightly smaller type. Rules target the children — an
   element never matches a query against its own container. */
.npc-root .npc-trust { container-type: inline-size; container-name: npctrust; }
@container npctrust (max-width: 540px) {
  .npc-root .npc-trust-item { flex-direction: column; align-items: center; text-align: center; gap: 6px; padding: 0 4px; }
  .npc-root .npc-trust-item b { font-size: 13px; }
  .npc-root .npc-trust-item span { font-size: 12.5px; }
}
.npc-root .npc-trust-item + .npc-trust-item { border-left: 1px solid #E0DDD6; }
.npc-root .npc-trust-item svg { flex: 0 0 24px; width: 24px; height: 24px; color: #1a1a1a; }
.npc-root .npc-trust-item b { display: block; font-size: 14px; font-weight: 600; line-height: 1.3; color: #1a1a1a; white-space: nowrap; }
.npc-root .npc-trust-item span { display: block; font-size: 13px; line-height: 1.3; color: #6b6b6b; white-space: nowrap; }
.npc-root .npc-trust-item b, .npc-root .npc-trust-item span { overflow: hidden; text-overflow: ellipsis; }   /* nowrap + ellipsis: a squeezed cell truncates rather than stacking "Made in the / USA" */
.npc-root .npc-desc { font-family: 'DM Sans', sans-serif; font-size: 14px; line-height: 1.65; color: #6b6b6b; }
.npc-root .npc-desc p { margin: 0 0 10px; }
.npc-root .np-top-review { margin-bottom: 6px; }
/* Review row = TWO FACTS on one line (Ollie 2026-09-29, "Idea 2"): "★ 5.0  14 reviews · this artwork | ★ 4.8  4,000+ reviews ·
   Nordic Park". The artwork fact at 2+ reviews, ANY average (2026-09-29, Ollie: no hiding); 1 = "1 review", 0 = "No reviews yet"; the brand
   fact stands alone. Phones: one line still — the artwork fact drops "· this artwork", the brand fact becomes
   "4,000+ Nordic Park reviews", a dot after each score, one size down (mock "B", 2026-09-29). Replaces the 09-23 bracketed
   run-on "★★★★★ 5.0 (14 reviews) · 4,000+ Nordic Park reviews". */
.npc-root .np-top-review.np-two { display: flex; align-items: center; flex-wrap: nowrap; gap: 0; }
.npc-root .np-two .np-fact { display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; text-decoration: none; color: inherit; }
.npc-root .np-two .np-fact:hover .np-count { color: #1a1a1a; }
.npc-root .np-two .np-fact + .np-fact { border-left: 1px solid #e8e6e1; margin-left: 12px; padding-left: 12px; }
.npc-root .np-two .np-star1 { color: #f0b93d; font-size: 15px; line-height: 1; }
.npc-root .np-two .np-score { font-family: 'DM Sans', sans-serif; font-size: 14px; font-weight: 500; color: #1a1a1a; }
.npc-root .np-two .np-count { font-family: 'DM Sans', sans-serif; font-size: 14px; color: #6b6b6b; }
.npc-root .np-two .np-b-short { display: none; }
.npc-root .np-two .np-note { font-family: 'DM Sans', sans-serif; font-size: 14px; color: #6b6b6b; }   /* 0 / 1 review: "No reviews yet · this artwork" / "1 review · this artwork" — the brand fact still follows, labelled (2026-09-29) */
@container np (max-width:900px) {
  .npc-root .np-two .np-note { font-size: 13px; }
  .npc-root .np-two .np-count-word, .npc-root .np-two .np-b-long { display: none; }
  .npc-root .np-two .np-b-short { display: inline; }
  .npc-root .np-two .np-count::before { content: "· "; }
  .npc-root .np-two .np-star1 { font-size: 13px; }
  .npc-root .np-two .np-score, .npc-root .np-two .np-count { font-size: 13px !important; }
  .npc-root .np-two .np-fact + .np-fact { margin-left: 10px; padding-left: 10px; }
  .npc-root .npc-titlerow .np-top-review.np-two { order: -1; flex: 0 0 100%; }
  .npc-root .np-top-review.np-two { flex-wrap: wrap; row-gap: 4px; }   /* safety: a count too long for one line (e.g. "1,216 reviews" on a 360px phone) wraps the brand fact under the artwork fact instead of overflowing */
  .npc-root .np-two.np-brand-wrap .np-fact + .np-fact { border-left: 0; margin-left: 0; padding-left: 0; }
  .npc-root .np-top-review.np-two.np-brand-wrap { column-gap: 10px; }   /* 2026-10-03: without the divider the two facts can still share a line ("38 reviews★ 4.8" ran together) — a gap between items on the SAME line only, so a real wrap starts flush left */   /* its own line ABOVE the title: ~320px wide, it cannot share the title's row without folding the title */
}
.npc-root .npc-acc-scope { display: block; padding: 0; margin: 0; width: 100%; }   /* .product-details only so the theme's accordion CSS applies */
/* Keyframes cannot live inside a container query, but cq units and custom properties inside them
   resolve against the element being animated, so these stay in step with --npc-slide. */
@keyframes npc-shrink { from { transform: scale(1); } to { transform: scale(.85); } }
@keyframes npc-dotdrop { from { transform: translate(0, 0); } to { transform: translate(calc(var(--npc-drop1) / -2), calc(0px - var(--npc-drop1))); } }
@container np (max-width:900px) {
  /* ═══ Phone gallery (Ollie 10 Sep, after the iamfy.co reference): no thumbnails, slides edge to edge with
     the next one peeking, and the gallery PINS to the top while the choices scroll under it — shrunk to 62% so
     it does not eat the screen (that was the objection that retired sticky on 7 Sep). The pin releases where
     .cols ends = the Add to cart button; the tail (trust card, accordions) scrolls normally. The shrink is a
     transform on the stage (scroll geometry stays in layout px — stageStep() reads offsetWidth for this), so
     every slide keeps its exact render and the next slide peeks in wider, exactly as the reference does. */
  /* The shrink is CONTINUOUS, driven by scroll position (JS sets --npc-p 0..1 every frame): the gallery gives
     up exactly one pixel of height per pixel scrolled, so the content under it holds still while the picture
     shrinks, then scrolls normally. A class toggle with a timed transition jumped the page by 132px at the
     threshold (Ollie, 10 Sep) — never go back to that.
     Scale is back to .62 and the strip stays flush LEFT (Ollie, 10 Sep pm — the bigger centred single slide
     was tried and rejected; the two-up look is the one he wants). One progress value drives it:
       --npc-s    strip scale                        1 -> .62
       --npc-vis  the live slide's visual size       (100cqw - 42) * s
     so the live slide ends ~216px wide on a 390px screen with the next one alongside it. */
  /* THE MARGIN IS THE WHOLE TRICK (Ollie, 10 Sep: "one logical scroll", not two speeds).
     The gallery reclaims height as it shrinks, and anything that reclaims height makes the content
     below travel faster than the finger — that is what read as a second, quicker scroll. So the
     height the picture gives up is handed straight back as margin BELOW it: height + margin-bottom
     is constant, so the flow position of everything under the gallery never moves at all, and the
     choices scroll at exactly 1x from the first pixel to the last.
     The margin is in flow, not stuck, so it sits off the top of the screen and is never seen; at the
     end of the shrink the choices are still exactly against the picture's bottom edge, no gap.
     Its partner is the LINEAR 1px-per-1px shrink in the JS below: one pixel of scroll buys one pixel
     of picture height, so the bottom edge of the picture and the top of the choices descend together
     as one object. An eased curve would still be 1x for the content but would slide the picture's
     edge against it — do not put the smoothstep back. */
  /* NOTHING THAT AFFECTS LAYOUT MAY CHANGE ON SCROLL (Ollie, 10 Sep: "it's like some container that
     keeps trying to change size"). He was describing lag, not arithmetic. The previous build had the
     travel exactly right on paper — text at a flat 1x — but it got there by animating the gallery's
     HEIGHT and MARGIN, and the stage's WIDTH and PADDING, every frame. On a phone the page scrolls on
     the compositor thread while style/layout/paint runs on the main thread, so a layout-driven effect
     lands one or two frames behind the scroll it is following: the picture visibly chases the page,
     which is the shudder. No amount of easing fixes that; the layout write has to go.
     So the box is now FIXED and only a transform moves:
       .gallery  height is always the full picture (--npc-slide) and never changes, so the flow below
                 it never moves and the text is at a flat 1x by construction, not by cancellation.
       .stage    scales; the area it vacates inside the gallery is TRANSPARENT (the white ground moved
                 onto the stage), so the choices scrolling up show through it and then pass behind the
                 picture, exactly as they did when the box was shrinking.
       .npc-dots ride down on a transform instead of a recalculated bottom/padding.
     The glue still holds: picture bottom = slide x s = slide - drop, and drop == pixels scrolled, so
     the picture's bottom edge and the top of the choices descend together. */
  /* SCALE .79 (Ollie, 10 Sep): the collapsed strip used to leave ~75% of the next slide showing, which
     read as two pictures rather than one picture and a hint of the next. Solving
     100cqw - 4s = 1.4 x 348s for "next slide shows 40% of the live one" gives s = .794, so the live
     slide goes 216px -> 275px on a 390px screen (+27%) and the peek drops to ~112px. Change .21 here,
     the .79 in the stage width/padding, the npc-shrink keyframe and K in the JS together or not at all.
     SCALE .85 (Ollie, 14 Sep): .79 "shrinks a little too much" — live slide now 296px on a 390px screen. */
  .npc-root .gallery { top: var(--npc-headh, 0px) !important; --npc-p: 0; --npc-s: calc(1 - .15 * var(--npc-p)); --npc-slide: 95cqw;   /* 2026-09-24 (Ollie): first image = 95% of the phone width (was 100cqw - 90px ~77%, then 85%, 90%) */ --npc-vis: calc(var(--npc-slide) * var(--npc-s)); --npc-drop: calc(var(--npc-slide) - var(--npc-vis)); --npc-drop1: calc(var(--npc-slide) * .15); position: sticky; top: 0; z-index: 30; background: none; padding-bottom: 0 !important; gap: 6px !important; overflow: hidden; height: var(--npc-slide); margin-bottom: 0 !important; }
  /* the stage keeps its UNSCALED layout size (it is a flex child of the shrinking gallery and would otherwise be
     squashed, cropping every slide) and the transform does the shrinking, so no slide ever re-renders — the
     paint cache keys on layout width. width tracks 1/s so the scrollport still reaches the screen edge. */
  /* The stage's layout width is CONSTANT at the widest it ever needs to be — 100cqw / the final scale.
     It is only a scrollport: the gallery clips everything at 100cqw either way, so a wider box changes
     nothing you can see at p=0, and at p=1 the scaled scrollport still reaches the screen edge so the
     next slide keeps peeking. Its right padding is constant for the same reason (it is what makes the
     LAST slide's snap point reachable). Both used to be recalculated every frame. */
  .npc-root .stage { transform-origin: 0 0; transform: scale(var(--npc-s)); will-change: transform; background: #fff; width: calc(100cqw / .85) !important; height: var(--npc-slide) !important; flex: 0 0 auto !important; }
  /* ── the shrink runs on the COMPOSITOR where the browser can (Ollie, 10 Sep: "still janky") ──
     v7 removed every layout write and it was still not smooth, because the remaining work — reading
     scroll in a listener, writing a custom property, recomputing two transforms — all happens on the
     MAIN thread, one rAF behind a scroll the compositor has already painted. A scroll-driven animation
     has no listener and no style recalc: the browser ties the transform straight to scroll offset on
     the compositor, so the picture cannot lag the page by construction. Chrome 115+ / Safari 26+; the
     JS path below is the fallback and turns itself off when this is supported.
     The range is two lengths down the page (where the gallery pins, and one drop later) — the only
     numbers JS has to supply, and it supplies them on resize, never on scroll. Until it does, the
     default range sits past the end of any document, so the animation holds its FROM state: a page
     restored mid-scroll by a back navigation paints at full size rather than at whatever phase 0-200px
     would have implied, and JS corrects it on the same tick. */
  @supports (animation-timeline: scroll()) {
    .npc-root .stage { animation: npc-shrink linear both; animation-timeline: scroll(root block); animation-range: var(--npc-r0, 1e7px) var(--npc-r1, 10000200px); }
    .npc-root .npc-dots { animation: npc-dotdrop linear both; animation-timeline: scroll(root block); animation-range: var(--npc-r0, 1e7px) var(--npc-r1, 10000200px); }
  }
  /* dots ride the LIVE slide's bottom edge at every scale: out of flow (the gallery's height is the picture's
     height, so an in-flow row is clipped the moment it shrinks — that is why they vanished on scroll), and
     padded to that slide's own visual box so the row stays on the picture rather than drifting to the middle
     of the two-up strip. */
  .npc-root .npc-dots { position: absolute; left: 0; right: 0; bottom: 0; height: auto; margin: 0 !important; padding: 0 42px 0 0 !important; transform: translate(calc(var(--npc-drop) / -2), calc(0px - var(--npc-drop))); will-change: transform; }
  /* TAPS UNDER THE PICTURE (Ollie, 14 Sep: "buttons right under the image port don't work"). The gallery's
     box keeps the FULL picture height while the stage scales inside it, so the strip the picture vacates
     (up to --npc-drop tall, ~50px on a phone) is transparent but still the sticky z-index-30 gallery for
     hit-testing: a choice scrolling through it is visible and untappable. Measured before this fix:
     elementFromPoint 10px under the shrunk picture returned DIV.gallery. The box itself now lets taps
     through and only its children (the stage, the arrows) take them. */
  .npc-root .gallery { pointer-events: none; }
  .npc-root .gallery > * { pointer-events: auto; }
  /* EDGE UNDER THE PINNED PICTURE (Ollie 2026-09-27): the choices slid under the photo with nothing between them
     and were sliced against its grey wall. Once pinned: a hairline, then a short white fade (~12px) so tiles fade
     out instead of being cut. A box-shadow, because .gallery's own overflow:hidden would clip a pseudo-element. */
  .npc-root .gallery { transition: box-shadow .15s; }
  .npc-root .gallery.npc-pinned { box-shadow: 0 1px 0 #E4E1DB, 0 6px 6px #fff; }
  /* ── (The "X" unpin control of 14 Sep is gone — Ollie 15 Sep: the picture always sits there. .npc-pinned
     is still toggled at the pin line by the scroll listener below; nothing reads it for an unpin now.) */
  /* NPC_SHRINK_ON_SCROLL=false (build flag): the stacked layout's picture pins at FULL size — the shrink
     animation and the JS fallback's transform are parked, nothing else changes */
  .npc-root.npc-noshrink .stage { animation: none !important; transform: none !important; width: 100cqw !important; padding-right: 0 !important; }   /* 2026-09-24 (Ollie): the last slide ends flush with the right edge — the old 90px pad (so it could scroll to the left edge) left a white strip. Index math still lands: max scroll is ~0.3 of a step short of the last slot, round() gives the last index. */
  .npc-root.npc-noshrink .npc-dots { animation: none !important; transform: none !important; }
  /* "See it on your wall" is the phone's biggest converter and read as a 12px chip (Ollie, 14 Sep):
     bigger type, taller hit box, and it is inside the stage so it still scales to 85% on scroll. */
  /* the pill ON the picture, one size down from 14 Sep (Ollie 15 Sep: "smaller — I like the overlay icon") */
  .npc-root .stage .npc-wallcta { font-size: 11.5px; font-weight: 500; padding: 6px 11px; gap: 6px; right: 10px; bottom: 10px; box-shadow: 0 1px 6px rgba(0,0,0,.14); }
  .npc-root .stage .npc-wallcta svg { width: 13px; height: 13px; }
  .npc-root .mobhead, .npc-root .quickbar { display: none !important; }

  .npc-root .npc-tail { padding: 0 16px; }

  /* phones: three cells across, icon over centred text */
  .npc-root .npc-trust { padding: 14px 2px 12px; }
  .npc-root .npc-trust-item { flex-direction: column; align-items: center; text-align: center; gap: 6px; padding: 0 4px; }
  .npc-root .npc-trust-item b { font-size: 13px; }
  .npc-root .npc-trust-item span { font-size: 12.5px; }
  .npc-root .optcol { padding-top: 8px; }
  /* 1+3. collapsed gallery: 60% wall, thumbnails hidden, back to full at the top of the page */
  /* phones: the live slide is CENTRED — equal 32px margins either side, the next slide peeking on the
     right (and the previous on the left once you've swiped). scroll-padding-left = padding-left keeps the
     JS parking maths exact; padding-right = the deficit so the last slide's snap point is reachable. */
  /* edge to edge: no left inset; 4px between slides; slide = 100cqw - 90px = an ~86px peek of the next slide
     (Ollie 15 Sep: a more horizontal port so the picture takes less of the screen; was 42px / 38px peek) and a
     reachable snap point for the last one */
  .npc-root .stage { padding: 0 calc(100cqw / .85 - var(--npc-slide)) 0 0 !important; scroll-padding-left: 0 !important; gap: 4px !important; }
  .npc-root .stage > .wall { width: var(--npc-slide) !important; border-radius: 0 !important; }
  .npc-root .viewthumbs { display: none !important; }   /* phones: no thumbnail strip (Ollie 10 Sep) */
  .npc-root .vth { --vtn: 56 !important; }   /* 84 was "way too big" (Ollie, 9 Sep) */
  .npc-root .optcol .np-top-review { margin-bottom: 4px; }
  .npc-root .optcol h1 { display: block !important; font-size: 24px !important; line-height: 1.2; margin: 0 0 16px !important; }   /* the prototype hides the column h1 on phones (it used .mobtitle instead) */
  .npc-root .opts.shapegrid { grid-template-columns: repeat(var(--npc-shpcols, 4), minmax(0, 1fr)) !important; }
  .npc-root .opts.sizegrid { display: flex !important; flex-wrap: wrap !important; gap: 8px !important; --npc-szcols: 3; }
  .npc-root .opts.sizegrid .sizecard { flex-grow: 0 !important; }   /* phones: every size tile the same width; a short last row leaves space (Ollie 10 Sep) — desktop keeps the grow */
  .npc-root .opts.sizegrid.wide { --npc-szcols: 2; }   /* sets: the label carries " each" too, so three across clips */
  .npc-root .opts.framegrid { gap: 8px !important; }
  .npc-root .opts.framegrid .framecard { flex: 0 0 var(--npc-tile) !important; min-width: var(--npc-tile) !important; max-width: var(--npc-tile) !important; }
}
/* ── chips + toggle, every screen ─────────────────────────────────────────── */
.npc-root #medOpts { grid-template-columns: repeat(2, 1fr) !important; }
.npc-root .medcard { height: 44px; justify-content: center; }
.npc-root .medcard .cardimg, .npc-root .medcard .mdesc { display: none !important; }
.npc-root .medcard .cardmeta { padding: 0 6px; }
/* Outline glyph beside the name, on one line: the proportion is the whole message, and it costs
   46px instead of the 82 a picture of the piece did. The set glyph (two panels) is in the same
   40x32 optical box, so a set product drops straight in. */
.npc-root .shapecard2 { height: 46px; flex-direction: row; align-items: center; justify-content: center; gap: 7px; padding: 0 8px; }
.npc-root .shapecard2 .cardimg { display: flex !important; align-items: center; justify-content: center; width: 25px !important; height: 20px !important; aspect-ratio: auto !important; margin: 0 !important; flex: none; background: none !important; border-radius: 0 !important; }   /* the base .cardimg paints a #f4f2ee plate behind the render — an outline needs no plate */
.npc-root .npc-shp { width: 25px; height: 20px; display: block; color: #A8A49C; transition: color .15s ease; }
.npc-root .shapecard2.on .npc-shp { color: #1a1a1a; }
.npc-root .shapecard2 .cardmeta { flex: none; flex-direction: row; align-items: center; justify-content: center; padding: 0; min-width: 0; }
.npc-root .shapecard2 .cardmeta b { font-size: 12px; white-space: nowrap; letter-spacing: -.01em; display: block; overflow: visible; -webkit-line-clamp: unset; }

.npc-root .gval { display: inline !important; }   /* "1. Choose Shape  Horizontal" — current value beside every step, like the live page */

/* ═══ compact, consistent option tiles (2026-09-07): one tile unit everywhere ═══
   4 shape tiles / N frame tiles = 1 unit square; medium and size cards = 2 units wide,
   so every row in the column shares the same 4-unit width (360px on desktop). */
.npc-root { --npc-tile: min(84px, calc((100cqw - 56px) / 4)); --npc-tile2: calc(var(--npc-tile) * 2 + 8px); }
.npc-root .opt, .npc-root .qtile { border-radius: 8px; }
.npc-root #medOpts { display: grid !important; grid-template-columns: repeat(2, var(--npc-tile2)) !important; gap: 8px !important; }
.npc-root .medcard { flex: none; min-width: 0; }
.npc-root .medcard .cardimg { height: var(--npc-tile) !important; aspect-ratio: auto !important; }
.npc-root .medcard .mdesc { font-size: 10.5px; line-height: 1.3; }
.npc-root .cardmeta { padding: 5px 4px 6px; gap: 1px; }
.npc-root .cardmeta b { font-size: 11.5px; }
.npc-root .cardmeta span { font-size: 11px; }
/* The dimensions are the thing you are choosing between — 16px, matching the live PDP's
   size cards, not the 13px the compact-tile pass had left them at. */
.npc-root .sizecard { padding: 10px 6px !important; gap: 2px; }
.npc-root .sizecard b { font-size: 16px !important; letter-spacing: -.01em; }
.npc-root .sizecard .sprice { font-size: 13px !important; }
.npc-root #metaEl { display: none !important; }
/* "Your selection" summary above the button — not a control, just what is being bought */
/* ═══ Vertical rhythm — one 4px scale, applied at the joints (2026-09-07) ═══
   Every ad-hoc margin the prototype set along the buy column is overridden here, so
   there is exactly ONE place that decides how far apart things sit:
     step label      -> its controls        8
     controls        -> next step label    16
     title hairline  -> first step label   16   (desktop; phones use .optcol padding)
     last controls   -> Add to cart        20   (a break, not a hole)
     Add to cart     -> the check list     16
     check list      -> rating strip       16
     rating strip    -> accordions         24
     image           -> thumbnail strip     8   (gallery gap + strip padding) */
/* Sentence case, not caps: "1. CHOOSE SIZE" shouted at a customer who is already reading
   four of them down the column. Tracking goes back to normal — .02em is there to open up
   capitals and only smears lowercase (Ollie, 2026-09-08). */
.npc-root .glabel { margin: 14px 0 8px !important; text-transform: none !important; letter-spacing: 0 !important; }
.npc-root .optgroups > .glabel:first-child { margin-top: 0 !important; }
.npc-root .framescroll { margin: 0 !important; }
.npc-root .atc { margin-top: 16px !important; }   /* a break after the last control, but 28 read as a hole (Ollie, 2026-09-08) */
.npc-root .npc-facts { margin-top: 16px !important; }
.npc-root .npc-acc-scope { margin-top: 24px !important; }
@container np (min-width:901px) {
  /* Ollie's call: the rule sits centred in the gap, 13 above / 14 below. Total space between
     the title and step 1 is unchanged at 27px, so nothing below it moves. 4px under the stars
     had glued the rating to the title (2026-09-08). */
  .npc-root .optcol h1 { margin: 0 !important; padding-bottom: 13px !important; }
  .npc-root .optcol > .optgroups { margin-top: 14px !important; }
  .npc-root .np-top-review { margin-bottom: 9px !important; }
}
@container np (max-width:900px) {
  .npc-root .optcol { padding: 12px 16px 0 !important; }   /* picture -> first step label (20 -> 12, Ollie 14 Sep: space under the pinned picture) */
  .npc-root .optcol .np-top-review { margin: 0 0 4px !important; }
  .npc-root .glabel { margin: 10px 0 8px !important; }   /* 14 -> 10 above every step label, same reason */
  .npc-root .optcol > .optgroups { margin-top: 0 !important; }
}

/* ═══ Medium = a mini comparison card, not a text toggle (2026-09-07) ═══
   Each button carries the real corner photograph of that build: the floating gap and canvas weave
   on one, the white mat under the moulding on the other. The photo is a near-square 7:6 thumbnail
   beside the text, NOT a banner across the top — that keeps the whole card ~88px instead of the
   240px a full-width photo cost, without cropping the corner into a letterbox slot. */
.npc-root #medOpts { display: grid !important; grid-template-columns: repeat(2, 1fr) !important; gap: 8px !important; }
.npc-root .medcard { height: auto !important; flex-direction: row !important; align-items: center !important; justify-content: flex-start !important; gap: 10px !important; padding: 8px !important; overflow: hidden; text-align: left; }
.npc-root .medcard .cardimg { display: block !important; flex: 0 0 84px !important; width: 84px !important; height: 72px !important; aspect-ratio: auto !important; margin: 0 !important; border-radius: 6px; overflow: hidden; background: #F5F4F1; }
.npc-root .medcard .cardimg img { display: block; width: 100%; height: 100%; object-fit: cover; object-position: 50% 58%; }
.npc-root .medcard .cardmeta { display: block !important; flex: 1 1 auto !important; min-width: 0; padding: 0 !important; }
.npc-root .medcard .cardmeta b { display: block; font-size: 12.5px; line-height: 1.25; }
.npc-root .medcard .mdesc { display: block !important; font-size: 10.5px; line-height: 1.3; color: #6b6b6b; margin-top: 3px; }
@container np (max-width:900px) {
  /* Two on one line, but STACKED inside each card: side by side in a row layout leaves ~65px
     for the text. Photo over label costs ~120px for both cards against 200px for two rows. */
  /* phones: text only — the corner photo cost ~70px of height for two cards the customer can already see rendered
     in the gallery above (Ollie, 10 Sep) */
  .npc-root .medcard { flex-direction: column !important; align-items: center !important; gap: 2px !important; padding: 10px 8px 11px !important; text-align: center; }
  .npc-root .medcard .cardimg { display: none !important; }
  .npc-root .medcard .mdesc { display: none !important; }   /* and no description line either (Ollie, 10 Sep): the name is the choice */
  .npc-root .medcard { padding: 13px 8px !important; }
  .npc-root .medcard .cardmeta { flex: none !important; text-align: center; }
  .npc-root .medcard .mdesc { margin-top: 2px; }
}

/* ═══ Store rating strip, under the check list ═══ */
.npc-root .npc-reviews { display: flex; align-items: center; justify-content: center; gap: 8px; margin-top: 16px; padding: 11px 16px; background: #F8F7F4; border-radius: 10px; font-family: 'DM Sans', sans-serif; }
.npc-root .npc-reviews-stars { display: flex; gap: 2px; line-height: 1; }
.npc-root .npc-reviews-stars svg { width: 16px; height: 16px; display: block; }
.npc-root .npc-reviews-score { font-size: 14px; font-weight: 700; color: #1a1a1a; }
.npc-root .npc-reviews-count { font-size: 14px; color: #6b6b6b; }

/* ═══ Thumbnail rail, tidied (2026-09-08) ═══
   Six equally-loud outlined boxes made the eye hunt for the live one. Now the tiles are
   pictures: unselected ones recede, only the current one is ringed, and the caption pill
   the full-size view draws ("Zoomed In Detail") is hidden at tile scale where it covered
   half the image. Radius drops to 4px to sit closer to the square main image, and the
   rail uses ONE gap value - the same 12px that separates it from the image. */
.npc-root .vth .zoomlabel, .npc-root .vth .wallcta { display: none !important; }
.npc-root .vth { border-radius: 4px !important; opacity: .78; filter: saturate(.8); transition: opacity .18s ease, filter .18s ease, transform .2s cubic-bezier(.22,.72,.28,1); }   /* the live gallery's own recipe, not a heavier fade */
.npc-root .vth::after { border-radius: 4px !important; box-shadow: none !important; }
.npc-root .vth.on { opacity: 1; filter: none; }
.npc-root .vth.on::after { box-shadow: inset 0 0 0 2px #1a1a1a !important; }
@media (hover: hover) { .npc-root .vth:hover { opacity: 1; filter: none; } .npc-root .vth:hover::after { box-shadow: inset 0 0 0 1.5px rgba(26,26,26,.35) !important; } .npc-root .vth.on:hover::after { box-shadow: inset 0 0 0 2px #1a1a1a !important; } }

/* ═══ Type scale (2026-09-08) ═══
   Was nine sizes (10.5 / 11 / 12 / 12.5 / 13 / 14 / 16 / 26) and five weights — nine separate
   decisions rather than a system, with the smallest text on the page at 10.5px. Nordic Park's
   buyer skews 45+, so the floor matters. Four steps now, two weights, and the old 16px size
   card is the new baseline rather than the exception:
     --t-xl 26  title            --t-lg 17  anything you choose between
     --t-md 15  labels + support  --t-sm 13  only where a 106px tile forces it
   Bought back from the medium card's photo (76x64 -> 66x56) and tighter tile padding, so the
   column carries bigger text without the button falling off the fold. */
.npc-root { --t-lg: 17px; --t-md: 15px; --t-sm: 13px; }
/* DESKTOP ONLY: phones stack the medium cards, so a 17px name over a 13px description turns a
   96px card into 150px there. Phone type is its own problem, worth doing on its own terms. */
@container np (min-width:901px) {
  .npc-root .glabel { font-size: var(--t-md) !important; font-weight: 600; }
  .npc-root .gval { font-size: var(--t-md); }
  .npc-root .shapecard2 .cardmeta b { font-size: var(--t-lg); font-weight: 600; }
  .npc-root .medcard .cardmeta b { font-size: var(--t-lg); font-weight: 600; }
  .npc-root .medcard .mdesc { font-size: var(--t-sm); line-height: 1.3; }
  .npc-root .medcard .cardimg { flex: 0 0 58px !important; width: 58px !important; height: 48px !important; }
  .npc-root .sizecard b { font-size: var(--t-lg) !important; font-weight: 600; }
  .npc-root .sizecard .sprice { font-size: var(--t-md) !important; }
  .npc-root .sizecard { padding: 6px 6px !important; }
  .npc-root .atc { font-size: var(--t-lg) !important; font-weight: 600; }
  .npc-root .npc-fact { font-size: var(--t-md); }
  .npc-root .npc-reviews-score { font-size: var(--t-md); font-weight: 600; }
  .npc-root .npc-reviews-count { font-size: var(--t-md); }
}
@container np (min-width:901px) {
  /* the frame tile is ~106px wide and reserves two lines for the longest moulding name, so it
     is the one place that cannot take 15px without either truncating or growing 20px taller */
  .npc-root .opts.framegrid .framecard .cardmeta b { font-size: var(--t-sm) !important; font-weight: 600; }
  .npc-root .opts.framegrid .framecard .cardmeta span { font-size: var(--t-sm) !important; }
  .npc-root .opts.framegrid .framecard .cardmeta { padding: 3px 4px 5px !important; }
}

/* ═══ Compare framed canvas vs framed print ═══
   The swatches on the cards are ~66px; this is where you actually look at the difference.
   Selectors are NOT scoped to .npc-root on purpose: .npc-root is a CSS container, and
   container-type makes it a containing block for position:fixed, so the overlay is moved to
   <body> on first open and would stop matching a scoped rule. */
.npc-root .glabel { display: flex; align-items: center; gap: 6px; }
.npc-root .glabel .gval { flex: 1 1 auto; min-width: 0; }
.npc-root .npc-cmp-open { float: none; margin-left: auto; display: inline-flex; align-items: center; gap: 5px; margin-top: 0; margin-bottom: 0; padding: 3px 9px; border: 1px solid #E4E1DB; border-radius: 999px; background: #fff; color: #1a1a1a; font-family: 'DM Sans', sans-serif; font-size: 12.5px; font-weight: 600; letter-spacing: 0; text-transform: none; cursor: pointer; }
.npc-root .npc-cmp-open:hover { border-color: #A8A49C; }
.npc-cmp { position: fixed; inset: 0; z-index: 2147483000; display: flex; align-items: center; justify-content: center; padding: 24px; }
.npc-cmp[hidden] { display: none; }
.npc-cmp-back { position: absolute; inset: 0; background: rgba(20,18,16,.55); -webkit-backdrop-filter: blur(2px); backdrop-filter: blur(2px); }
.npc-cmp-panel { position: relative; background: #fff; border-radius: 14px; padding: 22px; width: min(920px, 100%); max-height: calc(100vh - 48px); overflow: auto; box-shadow: 0 24px 60px rgba(0,0,0,.28); }
.npc-cmp-x { position: absolute; top: 12px; right: 12px; width: 34px; height: 34px; border: 0; border-radius: 50%; background: #F5F4F1; color: #1a1a1a; display: flex; align-items: center; justify-content: center; cursor: pointer; padding: 0; }
.npc-cmp-x:hover { background: #ECE9E3; }
/* Magnifier badge, bottom-right of each moulding photo. A span, not a button — the tile is
   already a <button> and the parser hoists a nested one out of it. */
.npc-root .framecard .cardimg { position: relative; }
.npc-root .npc-zm { position: absolute; right: 4px; bottom: 4px; width: 24px; height: 24px; border-radius: 50%; background: rgba(255,255,255,.92); color: #1a1a1a; display: flex; align-items: center; justify-content: center; box-shadow: 0 1px 4px rgba(0,0,0,.18); cursor: zoom-in; }
.npc-root .npc-zm:hover { background: #fff; }
.npc-root .framecard.navail .npc-zm { display: none; }
/* The zoom macro is ALWAYS square (every npc-zm-*.jpg is 900x900), so width is the only driver and
   aspect-ratio reserves the box before the file arrives. Without that the img had no intrinsic size,
   the modal opened as an empty sliver and then snapped to 620px — most of what read as "slow" was
   that jump, not the download. Now the #F5F4F1 tile is there from the first frame. */
.npc-zmwrap { display: flex; justify-content: center; }
.npc-zmwrap img { display: block; width: min(100%, min(70vh, 620px)); height: auto; aspect-ratio: 1 / 1; border-radius: 8px; background: #F5F4F1; }
.npc-cmp-title { font-family: 'Lora', Georgia, serif; font-weight: 500; font-size: 20px; line-height: 1.2; color: #1a1a1a; margin: 0 40px 16px 2px; }
.npc-cmp-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(148px, 1fr)); gap: 16px; }
.npc-cmp-grid.two { grid-template-columns: repeat(2, 1fr); gap: 20px; }
.npc-cmp-fig { margin: 0; border: 1px solid #E4E1DB; border-radius: 10px; overflow: hidden; background: #fff; cursor: pointer; transition: border-color .15s ease, box-shadow .15s ease; }
.npc-cmp-fig:hover { border-color: #A8A49C; }
.npc-cmp-fig.on { border-color: #1a1a1a; box-shadow: 0 0 0 1px #1a1a1a; }
.npc-cmp-grid img { display: block; width: 100%; aspect-ratio: 1 / 1; object-fit: cover; background: #F5F4F1; }
/* shapes are compared BY their proportion, so they are shown whole, not cropped square */
.npc-cmp-grid.shapes { grid-template-columns: repeat(auto-fill, minmax(170px, 1fr)); }
.npc-cmp-grid.shapes img { aspect-ratio: auto; height: 200px; object-fit: contain; padding: 10px; }
.npc-cmp-grid figcaption { font-family: 'DM Sans', sans-serif; padding: 10px 12px 12px; }
.npc-cmp-grid figcaption b { display: block; font-size: 15px; font-weight: 600; color: #1a1a1a; }
.npc-cmp-grid.two figcaption b { font-size: 17px; }
.npc-cmp-grid figcaption span { display: block; font-size: 13.5px; line-height: 1.45; color: #6b6b6b; margin-top: 2px; }
/* Sizes: ONE wall, one px-per-inch. Every size is nested about the same hanging point above
   the same reference object, because five boxes in a row compare with each other and not with
   a sofa 500px away. Selected is solid, the rest are hairlines you can still read through. */
.npc-ref-row { display: flex; gap: 8px; margin: 0 0 14px; flex-wrap: wrap; align-items: center; }
.npc-ref-lead { font-family: 'DM Sans', sans-serif; font-size: 13.5px; font-weight: 600; color: #1a1a1a; margin-right: 2px; }
.npc-ref { font-family: 'DM Sans', sans-serif; font-size: 13.5px; font-weight: 600; color: #1a1a1a; background: #fff; border: 1px solid #E4E1DB; border-radius: 999px; padding: 7px 14px; cursor: pointer; }
.npc-ref:hover { border-color: #A8A49C; }
.npc-ref.on { border-color: #1a1a1a; box-shadow: 0 0 0 1px #1a1a1a; }
.npc-scene { position: relative; margin: 0 auto; overflow: hidden; }   /* the room ends at the ceiling: a piece that does not fit is cut off there, not drawn over the fields above */
.npc-scene-ref { position: absolute; bottom: 0; }
.npc-scene-ref svg { width: 100%; height: 100%; display: block; fill: var(--npc-ref-fill, #f7f6f3); stroke: #6b6b6b; overflow: visible; }   /* hairline drawings (2026-10-05): fill = the wall behind, so a nearer part hides the lines behind it */
.npc-scene-ref svg .ln { fill: none; }
/* EVEN OUTLINES (Ollie 2026-10-03: "the edges of the drawing get thinned out"). The furniture SVGs are stretched to real
   inches (preserveAspectRatio="none"), so a user-unit stroke came out thicker one way than the other, and shapes touching
   the viewBox edge (the arms, the top of the back) lost the outer half of their stroke to the SVG's clip. vector-effect is
   NOT inherited — it was on the <svg> and did nothing — so it goes on every shape, the width is in screen px, and the SVG
   may paint past its box (overflow: visible). */
.npc-scene-ref svg * { vector-effect: non-scaling-stroke; stroke-width: 1.25px; stroke-linejoin: round; stroke-linecap: round; }
.npc-szs.car .npc-scene-ref svg * { stroke-width: 1.1px; }
.npc-scene-floor { position: absolute; left: 0; right: 0; bottom: 0; border-top: 1px solid #E4E1DB; }
/* Outlines only. Filling the unselected ones stacked five translucent panels on top of each
   other, so everything looked highlighted and nothing looked chosen. */
.npc-scene-art { position: absolute; border: 1px solid #DCD8D1; border-radius: 2px; background: none; pointer-events: none; }
.npc-scene-art.on { border: 2px solid #1a1a1a; }
.npc-scene-art.live { border: 0; background: none; overflow: visible; isolation: isolate; z-index: 0; }   /* own stacking context: the piece's inner z-indexes must not climb over the smaller outlines drawn after it */
.npc-scene-art.live ~ .npc-scene-art { border-color: rgba(26,26,26,.28); }   /* outlines that sit ON the picture need a touch more weight to read */
.npc-scene-art.live .artwrap { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; }
.npc-scene-art.live .art { flex: none; margin: 0; }
/* Exactly ONE tag is shown (.show) — the hovered size, else the selected one. Five corner
   labels on nested rectangles landed on top of each other and of the outlines they named. */
.npc-scene-tag { position: absolute; display: none; transform: translate(-50%, 0); font-family: 'DM Sans', sans-serif; font-size: 12.5px; font-weight: 600; line-height: 1; color: #1a1a1a; white-space: nowrap; pointer-events: none; background: #fff; padding: 3px 6px; border-radius: 4px; }
.npc-scene-tag.on { display: block; }
.npc-scene-note { text-align: center; font-family: 'DM Sans', sans-serif; font-size: 13px; color: #8A857B; margin-top: 8px; }
.npc-size-legend { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; margin-top: 14px; }
.npc-size-pick { font-family: 'DM Sans', sans-serif; background: #fff; border: 1px solid #E4E1DB; border-radius: 10px; padding: 8px 13px; cursor: pointer; text-align: center; line-height: 1.25; transition: border-color .15s ease, box-shadow .15s ease; }
.npc-size-pick b { display: block; font-size: 15px; font-weight: 600; color: #6b6b6b; }
.npc-size-pick span { display: block; font-size: 13px; color: #A8A49C; }
.npc-size-pick:hover { border-color: #A8A49C; }
/* the chosen one is the only black thing in the row, same language as the size cards */
.npc-size-pick.on { border-color: #1a1a1a; box-shadow: 0 0 0 1px #1a1a1a; }
.npc-size-pick.on b { color: #1a1a1a; }
.npc-size-pick.on span { color: #6b6b6b; }
/* Custom reference: two modes, one or two numbers, ceiling behind a link (2026-09-09) */
.npc-cus { margin: 0 0 14px; padding: 12px 14px 12px; border: 1px solid #E4E1DB; border-radius: 10px; background: #FBFAF8; }
.npc-cus-sum { display: none; }   /* the phone's collapsed one-liner; desktop always shows the fields */
/* size overlay: controls left, scene + sizes right on desktop (2026-09-10 — the stacked version left a
   920px panel mostly empty and 850px tall); phones stack, with the fields two to a row */
.npc-size-cols { display: grid; grid-template-columns: 300px 1fr; gap: 24px; align-items: start; }
.npc-size-left .npc-ref-row { margin-bottom: 12px; }
.npc-size-left .npc-cus { margin-bottom: 0; }
.npc-size-left .npc-cus-fields { flex-direction: column; gap: 10px; }
.npc-size-right { min-width: 0; }
.npc-size-right .npc-size-legend { margin-top: 12px; }
.npc-cus-fields { display: flex; flex-wrap: wrap; gap: 10px 18px; align-items: flex-start; }
.npc-cus-f { display: flex; flex-direction: column; align-items: flex-start; gap: 4px; font-family: 'DM Sans', sans-serif; font-size: 13px; font-weight: 600; color: #1a1a1a; }
.npc-cus-in { display: inline-flex; align-items: stretch; border: 1px solid #DCD8D1; border-radius: 8px; background: #fff; overflow: hidden; }
.npc-cus-in:focus-within { outline: 2px solid #1a1a1a; outline-offset: 1px; }
.npc-cus-num { position: relative; display: inline-block; }
.npc-cus-in input { width: 86px; padding: 8px 26px 8px 8px; border: 0; border-left: 1px solid #ECE9E3; border-right: 1px solid #ECE9E3; border-radius: 0; background: #fff; text-align: center; font-family: 'DM Sans', sans-serif; font-size: 15px; font-weight: 500; color: #1a1a1a; font-variant-numeric: tabular-nums; }
.npc-cus-in input:focus { outline: none; }
.npc-cus-in em { position: absolute; right: 7px; top: 50%; transform: translateY(-50%); font-style: normal; font-size: 11.5px; color: #8A857B; pointer-events: none; }
.npc-step { width: 36px; border: 0; padding: 0; background: #fff; color: #1a1a1a; font-family: 'DM Sans', sans-serif; font-size: 18px; line-height: 1; cursor: pointer; touch-action: manipulation; user-select: none; -webkit-user-select: none; }
.npc-step:hover { background: #F5F4F1; }
.npc-step:active { background: #ECE9E3; }
.npc-cus-guide { margin-top: 10px; font-family: 'DM Sans', sans-serif; font-size: 13px; line-height: 1.45; color: #6b6b6b; }
.npc-scene-ceil { position: absolute; left: 0; right: 0; top: 0; border-top: 1px dashed #C9C5BD; z-index: 2; }
.npc-scene-ceil i { position: absolute; left: 4px; top: 3px; font-style: normal; font-family: 'DM Sans', sans-serif; font-size: 11px; color: #A8A49C; background: #fff; padding: 1px 5px; border-radius: 3px; }
.npc-scene-wallref svg { display: none; }   /* no beige panel (Ollie, 2026-09-09): the wall's width reads from two hairline edges */
/* #8A857B, not #DCD8D1 (Ollie 2026-09-19: "barely visible"). On the guide's #f7f6f3 paper the old grey
   was 1.3:1 — LIGHTER than the #B9B4AA dimension lines pointing at it, and a dash carries about half the
   ink of a solid line of the same colour, so it read as nothing. 3.4:1 puts the wall's two edges a step
   above the dimension lines, which is the right order: on the Wall reference these ARE the measurement. */
.npc-scene-wallref { border-left: 1px dashed #8A857B; border-right: 1px dashed #8A857B; }
/* -- sizes-to-scale SLIDE (3rd image, Ollie 2026-09-17): the same drawing inside the gallery -- */
.sizescene { display: none; position: absolute; inset: 0; z-index: 3; overflow: hidden; background: #f7f6f3; }   /* a shade off white so the drawing's paper reads against the page (Ollie 2026-09-18) */
/* Size-guide TILE (Ollie 2026-09-19): a label, not a miniature. Authored at 92px because .vth > * is
   pinned to a 92x92 box and scaled by var(--vtn)/92 — at his 120px rail everything here renders 1.3x
   (11px label -> 14.3px). Paper is #f7f6f3, the same stock the guide slide is drawn on, so the tile
   reads as that slide rather than as a sixth photograph. */
.npc-szthumb { display: flex !important; flex-direction: column; align-items: center; justify-content: center; gap: 6px;
               background: #f7f6f3; box-sizing: border-box; padding: 0 8px; }
.npc-szthumb svg { width: 30px; height: 23px; display: block; overflow: visible; }   /* smaller (Ollie 2026-10-03) to give the label room */
.npc-szthumb i { font-style: normal; font-family: 'Lora', Georgia, serif; font-weight: 500; font-size: 14px; width: min-content; white-space: normal !important;   /* one word per line: "Product / details" was cut off at 14px (Ollie 2026-10-03) */
                 line-height: 1.1; letter-spacing: -0.01em; color: #1a1a1a; text-align: center; }   /* wraps: "Product details" is wider than the 76px of usable tile (Ollie 2026-09-19) */
.vth .vthwall .sizescene { text-align: left; }
.vth .vthwall .sizescene span, .vth .vthwall .sizescene button, .vth .vthwall .sizescene label { font: inherit !important; color: inherit !important; white-space: inherit !important; margin: 0 !important; }
.vth .vthwall .sizescene .npc-szs, .vth .vthwall .sizescene .npc-szs * { pointer-events: none !important; }   /* the miniature must not eat the thumbnail's click */
.npc-szs { position: absolute; inset: 0; box-sizing: border-box; display: flex; flex-direction: column; font-family: 'DM Sans', sans-serif; color: #1a1a1a; text-align: left; cursor: default; }
.npc-szs * { box-sizing: border-box; }
/* the sheet's title (phones) */
.npc-szs-title { flex: none; display: flex; align-items: flex-start; gap: 1em; color: #1a1a1a; }
.npc-szs-title > span { font-family: 'Lora', Georgia, serif; font-weight: 500; font-size: 1.5em; line-height: 1.2; }
/* desktop head (Ollie's mock, 2026-09-18 pm): big serif headline + grey subline, centred */
.npc-szs-head { flex: none; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; gap: .3em; }   /* centred in its slot: the subline that used to sit under the headline is gone (Ollie 2026-09-19) */
.npc-szs-h1 { font-family: 'Lora', Georgia, serif; font-weight: 500; font-size: 1.35em; line-height: 1.15; color: #1a1a1a; letter-spacing: -0.01em; }
.npc-szs-h2 { font-size: 1.05em; color: #6b6b6b; line-height: 1.3; }
/* Descriptions for the measurement fields: announced, never drawn. The slide has no room for six sentences
   and the visual style was explicitly to be preserved, so they live here rather than on the drawing. */
.npc-sr { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0; }
/* Reset: only rendered when the scene has edits to undo. Quiet by design — underlined grey text, no box,
   the same weight as "Clear all" on the collection filter bar. */
.npc-szs-reset { appearance: none; border: 0; background: none; padding: 0; margin-left: auto; font: inherit; font-size: .95em; color: #6b6b6b; text-decoration: underline; text-underline-offset: 3px; cursor: pointer; white-space: nowrap; touch-action: manipulation; }
.npc-szs-reset:hover { color: #1a1a1a; }
/* room buttons: the PAGE's tiles (white hairline box, 6px, chosen = black hairline + corner tick) — desktop */
.npc-szs-chips { flex: none; display: flex; gap: 6px; }
.npc-szs-chip { appearance: none; position: relative; flex: 1 1 0; min-width: 0; border: 1px solid #e8e6e1; border-radius: 6px; background: #fff; padding: 0 .3em; font: inherit; color: #1a1a1a; cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; touch-action: manipulation; }
.npc-szs-chip:hover { border-color: #b9b4aa; }
.npc-szs-chip.on { border-color: #1a1a1a; font-weight: 500; overflow: visible; }
.npc-szs-chip.on::after { content: "✓"; position: absolute; top: -6px; right: 5px; width: 16px; height: 16px; border-radius: 50%; background: #1a1a1a; color: #fff; font-family: 'DM Sans', sans-serif; font-size: 10px; font-weight: 500; line-height: 16px; text-align: center; z-index: 4; box-shadow: 0 0 0 2px #f7f6f3; pointer-events: none; }
.npc-szs-main { flex: 1 1 auto; min-height: 0; display: flex; align-items: stretch; }
.npc-szs-scene { flex: 1 1 auto; min-width: 0; display: flex; align-items: flex-end; justify-content: center; overflow: visible; }
.npc-szs-sc { position: relative; flex: none; }
.npc-szs .npc-scene { margin: 0; }
.npc-szs .npc-scene-note, .npc-szs .npc-scene-ceil i, .npc-szs .npc-scene-tag { display: none; }   /* the dimension lines carry every number */
.npc-szs .npc-scene-art { pointer-events: none; }
.npc-szs .npc-scene-art.live { border: 0 !important; }   /* the piece is drawn — no box around it (its top/bottom edges showed as dark ticks in a set's gap) */
.npc-szs .csb, .npc-szs .csr, .npc-szs .csl { display: none; }   /* wall contact shadows off on the size guide: in a set's gap they read as a dark line (Ollie 2026-09-17) */
/* dimension lines: hairline with end ticks; plain two-line labels (name over number) */
.npc-dims { position: absolute; inset: 0; pointer-events: none; }
.npc-dim { position: absolute; }
.npc-dim.h { height: 0; border-top: 1px solid #b9b4aa; }
.npc-dim.v { width: 0; border-left: 1px solid #b9b4aa; }
.npc-dim.h::before, .npc-dim.h::after { content: ''; position: absolute; top: -4px; width: 1px; height: 9px; background: #b9b4aa; }
.npc-dim.h::before { left: 0; } .npc-dim.h::after { right: 0; }
.npc-dim.v::before, .npc-dim.v::after { content: ''; position: absolute; left: -4px; height: 1px; width: 9px; background: #b9b4aa; }
.npc-dim.v::before { top: 0; } .npc-dim.v::after { bottom: 0; }
.npc-dim-tag { position: absolute; line-height: 1.25; white-space: nowrap; background: #f7f6f3; padding: .15em .4em; }   /* plain text on the drawing (Ollie 2026-09-17: no rounded boxes here) */
.npc-dim-tag i { display: block; font-style: normal; font-size: .95em; color: #6b6b6b; }
.npc-dim-tag b { display: block; font-weight: 500; font-size: 1.15em; color: #1a1a1a; }
.npc-dim-tag:not(.two) b { font-size: 1em; }
/* the steppers: name, then a hairline box — minus | number unit | plus */
.npc-szs-ctl.panel { flex: none; display: flex; flex-direction: column; gap: 1.3em; padding-top: .2em; }
.npc-szs-ctl.panel .npc-szs-fld { flex: none; }   /* the column must not spread the three fields down the whole slide */
.npc-szs-ctl.row { flex: none; display: flex; gap: 1.2em; align-items: flex-start; }
.npc-szs:not(.small) .npc-szs-fld { max-width: 250px; }
.npc-szs.small .npc-szs-ctl.row { gap: .5em; }
.npc-szs-fld { display: flex; flex-direction: column; gap: .4em; min-width: 0; flex: 1 1 0; cursor: default; }
/* desktop "Your sofa" row: a hairline over it, the noun in serif at the left, Width / Height inline after it */
.npc-szs:not(.full) .npc-szs-ctl.row { align-items: center; gap: 1.6em; border-top: 1px solid #e8e6e1; padding-top: .9em; }
.npc-szs:not(.full) .npc-szs-you { font-family: 'Lora', Georgia, serif; font-weight: 500; font-size: 1.3em; color: #1a1a1a; white-space: nowrap; margin-right: .4em; min-width: 0; overflow: hidden; text-overflow: ellipsis; }   /* if anything in the row must give, it is the section title — never a measurement label or its control */
.npc-szs:not(.full) .npc-szs-fld { flex: none; flex-direction: row; align-items: center; gap: .7em; max-width: none; }
.npc-szs:not(.full) .npc-szs-fc { width: 10.5em; height: 2.7em; }   /* 10.5 not 11.5: the longest new labels
   ("Height to mantel top", "Overall bed width") plus a section title like "Your console table" had the row at
   736 of 757px at 1512 wide, i.e. one narrower window from ellipsis (measured 2026-09-19). */
.npc-szs:not(.full).small .npc-szs-ctl.row { gap: .9em; }
.npc-szs:not(.full).small .npc-szs-fc { width: 9.5em; }
.npc-szs-note { flex: none; text-align: center; font-size: .85em; color: #9e9890; }
.npc-szs-ft { font-size: 1em; color: #6b6b6b; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.npc-szs-fc { display: flex; align-items: stretch; height: 2.8em; border: 1px solid #e8e6e1; border-radius: 6px; background: #fff; overflow: hidden; }
.npc-szs-st { appearance: none; flex: none; width: 2.5em; border: 0; background: none; padding: 0; font: inherit; font-size: 1.25em; color: #6b6b6b; cursor: pointer; touch-action: manipulation; }
/* Keyboard focus. The number input clears its own outline (below), so the ring goes on the hairline box
   around it; every button in the guide gets the same 2px mark the custom-size field already uses. Before
   this the steppers, room tiles and size chips were all reachable by Tab with NOTHING drawn — checked
   2026-09-19, and the brief asks for focus indicators to survive. */
.npc-szs-fc:focus-within { outline: 2px solid #1a1a1a; outline-offset: 1px; }
.npc-szs-st:focus-visible, .npc-szs-chip:focus-visible, .npc-szs-reset:focus-visible, .npc-szs-size:focus-visible { outline: 2px solid #1a1a1a; outline-offset: 2px; }
.npc-szs-st:first-child { border-right: 1px solid #e8e6e1; } .npc-szs-st:last-child { border-left: 1px solid #e8e6e1; }
.npc-szs-st:hover { color: #1a1a1a; background: #f6f5f2; }
.npc-szs-fv { flex: 1 1 auto; min-width: 0; display: flex; align-items: center; justify-content: center; gap: .3em; }
.npc-szs-fv input { width: 2.6em; border: 0; background: none; padding: 0; font: inherit; font-size: 1.15em; font-weight: 500; color: #1a1a1a; text-align: right; outline: none; }
.npc-szs-fv em { font-style: normal; color: #9e9890; }
/* DESKTOP size-guide fields (Ollie 2026-10-03, "doesn't look right"): the value box was 53px for a 41px input + "in", so the
   number sat off-centre and the unit was jammed against the + divider. Now one clean pill: no inner dividers, the number sized
   to its digits (3ch, centred) with the unit right after it, the pair centred between − and +, and a touch wider box. */
.npc-szs:not(.full):not(.car) .npc-szs-fc { width: 11.5em !important; }
.npc-szs:not(.full):not(.car) .npc-szs-st { width: 2.4em; border: 0 !important; }
.npc-szs:not(.full):not(.car) .npc-szs-st:hover, .npc-szs:not(.full):not(.car) .npc-szs-st:active { background: none !important; color: #1a1a1a; }   /* hover = darker glyph only: with no dividers a grey fill read as half the box changing colour (Ollie 2026-10-03) */
.npc-szs:not(.full):not(.car) .npc-szs-fv { gap: .25em; }
.npc-szs:not(.full):not(.car) .npc-szs-fv input { width: 3ch; text-align: center; }
@supports (field-sizing: content) { .npc-szs:not(.full):not(.car) .npc-szs-fv input { field-sizing: content; width: auto; min-width: 1ch; } }   /* the box hugs the digits, so "60 in" has an even gap */
.npc-szs.small .npc-szs-fv input { font-size: 16px; width: 2.2em; }   /* below 16px iOS zooms the page on focus */
.npc-szs.small .npc-szs-st { width: 2.2em; }
.npc-szs.small .npc-szs-chips { overflow-x: auto; scrollbar-width: none; }   /* six rooms don't fit a 280px bar: scroll, never truncate */
.npc-szs.small .npc-szs-chips::-webkit-scrollbar { display: none; }
.npc-szs.small .npc-szs-chip { flex: none; padding: 0 .55em; font-size: .95em; overflow: visible; }
/* phone carousel size slide (2026-09-24, option A) */
.npc-szs.car { display: flex; flex-direction: column; }
.npc-szs.car .npc-szs-fit { flex: none; display: flex; align-items: center; justify-content: center; text-align: center; font-size: .95em; color: #6b6b6b; white-space: nowrap; }
.npc-szs.car .npc-szs-chips { display: grid; grid-template-columns: repeat(5, 1fr) 1.18fr; gap: 4px; overflow: visible; }   /* the last (Fireplace) cell a touch wider */
.npc-szs.car .npc-szs-chip { flex: none; height: 100%; padding: 0 1px; font-size: .84em; letter-spacing: -.01em; color: #6b6b6b; overflow: visible; text-overflow: clip; }   /* .84em: "Fireplace" fits a sixth of a 355px row */
.npc-szs.car .npc-szs-chip.on { color: #1a1a1a; font-weight: 500; }
.npc-szs.car .npc-szs-chip.on::after { display: none; }   /* no tick: the black outline already says "selected" (Ollie 2026-09-24) */
/* steppers: the label INSIDE one compact box — [ Width  −  84″  + ] — both on one row */
.npc-szs.car .npc-szs-ctl.row { border-top: 0; padding-top: 0; justify-content: space-between; gap: 6px; align-items: stretch; }
.npc-szs.car .npc-szs-fld { flex: 1 1 0; flex-direction: row; align-items: center; gap: 0; border: 1px solid #e0ddd6; border-radius: 6px; background: #fff; padding-left: .75em; min-width: 0; }
.npc-szs.car .npc-szs-ft { color: #6b6b6b; font-weight: 400; flex: none; }
.npc-szs.car .npc-szs-fc { flex: 1 1 auto; width: auto !important; height: 100% !important; border: 0; background: none; }
.npc-szs.car .npc-szs-st { width: 2em; color: #1a1a1a; }
.npc-szs.car .npc-szs-fv input { width: 1.8em; }
.npc-szs.car .npc-szs-fv em { margin-left: -.1em; }
.npc-dim-tag.car b { display: block; font-weight: 500; font-size: 1em; color: #1a1a1a; }
.npc-szs.car .npc-szs-reset { display: none; }   /* no room for it on the square; the full sheet and desktop keep Reset */
.npc-szs.small:not(.full) .npc-szs-chip { flex: 1 1 0; padding: 0 .25em; font-size: .9em; overflow: hidden; text-overflow: ellipsis; }   /* a narrow DESKTOP wall (not the phone sheet): share the bar, never scroll */
/* -- compact phone slide: drawing + caption + the button that opens the full guide -- */
.npc-szs-cap { flex: none; display: flex; align-items: center; justify-content: center; font-size: 1.05em; color: #6b6b6b; }
.npc-szs-open { flex: none; display: flex; align-items: center; justify-content: center; gap: 8px; width: 100%; height: 44px; margin-top: 8px; border: 1px solid #1a1a1a; border-radius: 6px; background: #fff; color: #1a1a1a; font: inherit; font-size: 13px; font-weight: 500; cursor: pointer; touch-action: manipulation; }
.npc-szs-open:active { background: #f6f5f2; }
.npc-szs-sizes { flex: none; display: flex; gap: 6px; margin-bottom: .8em; }
.npc-szs-size { flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: .15em; padding: .35em .2em; border: 1px solid #e8e6e1; border-radius: 6px; background: #fff; font: inherit; color: #1a1a1a; cursor: pointer; touch-action: manipulation; }
.npc-szs-size.on { border-color: #1a1a1a; }
.npc-szs-size b { font-weight: 500; font-size: 1em; white-space: nowrap; }
.npc-szs-size span { font-size: .95em; color: #6b6b6b; }
.npc-szs-size em { font-style: normal; font-size: .85em; color: #9e9890; min-height: 1.1em; }
.npc-szs-size em.npc-fit-good { color: #2e7d4f; }
.npc-szs-size em.npc-fit-tall { color: #b3261e; }
/* -- the full-screen sheet (phones): the same one-column guide as the desktop slide, plus the size row -- */
.npc-szs-full { position: fixed; inset: 0; z-index: 2147483000; background: #f7f6f3; }
.npc-szs-full[hidden] { display: none; }
.npc-szs-fullbody { position: absolute; inset: 0; padding-top: env(safe-area-inset-top); }
.npc-szs-fit { flex: none; display: flex; align-items: center; justify-content: center; text-align: center; font-size: 1em; color: #6b6b6b; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; padding: 0 .2em; }
.npc-szs-fit b { font-weight: 500; color: #1a1a1a; margin: 0 .15em; }
.npc-szs-sep { color: #9e9890; margin: 0 .35em; }
.npc-szs-fit b.npc-szs-good { color: #2e7d4f; }
.npc-szs-tick { flex: none; color: #2e7d4f; margin-left: .3em; vertical-align: -2px; }
.npc-szs.small .npc-szs-fit { font-size: .95em; }
.npc-szs:not(.full) .npc-szs-fit { margin-top: .45em; }   /* desktop: off the width label */
/* desktop two-stat row (Ollie's mock 2026-09-23): label in small caps, the number bold at body size (was 1.6em —
   too big, Ollie 2026-09-27), a hairline between.
   Baselines line up across both stats; the unit phrase is the only part allowed to give way on a narrow slide. */
.npc-szs-fit.big { gap: 1.6em; color: #8a857d; }
.npc-szs-stat { display: inline-flex; align-items: baseline; gap: .6em; min-width: 0; white-space: nowrap; }
.npc-szs-k { font-size: .85em; font-weight: 500; letter-spacing: .07em; text-transform: uppercase; color: #8a857d; }
.npc-szs-fit.big b { font-family: 'DM Sans', sans-serif; font-size: 1.05em; line-height: 1; font-weight: 600; letter-spacing: -0.01em; margin: 0 0 0 .15em; font-variant-numeric: tabular-nums; }
.npc-szs-u { font-size: 1.05em; color: #6b6b6b; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.npc-szs-div { flex: none; align-self: center; width: 1px; height: 1.3em; background: #d9d5ce; }
.npc-szs-fit.big .npc-szs-tick { align-self: center; margin-left: .1em; }
.npc-szs-fit.big > .npc-szs-stat:first-child { flex: 0 1 auto; }
.npc-szs.small .npc-szs-fit.big { gap: 1.2em; }
/* narrow slide (<600px, e.g. a 1024px window): one row doesn't fit "of console table width" — each stat stacks,
   label on top, number (+ unit / tick) under it. Same pieces, about half the width. */
.npc-szs.small .npc-szs-stat { display: grid; grid-template-columns: max-content minmax(0, 1fr); column-gap: .45em; row-gap: .2em; align-items: baseline; justify-content: start; }
.npc-szs.small .npc-szs-k { grid-column: 1 / -1; font-size: .8em; letter-spacing: .05em; }
.npc-szs.small .npc-szs-fit.big b { font-size: 1.05em; margin: 0; }
.npc-szs.small .npc-szs-div { height: 2.2em; }
/* -- the phone sheet (Ollie 2026-09-23: option C's two tabs inside option B's white panel) ------------------
   Title bar / a FIXED drawing box / a white panel pinned to the bottom with two tabs, "Size" and "Your room".
   Both panes sit in ONE grid cell (the inactive one is visibility:hidden), so the panel is always as tall as the
   taller pane and switching tabs, sizes or rooms never moves anything. Heights are laid out against the SMALL
   viewport (Safari toolbar showing), so the toolbar coming back can't push the panel off the screen. */
.npc-szs.full { padding: env(safe-area-inset-top) 0 0 !important; }
.npc-szs.full .npc-szs-title { align-items: center; padding: 0 64px 0 16px; border-bottom: 1px solid #e8e6e1; }
.npc-szs.full .npc-szs-title > span { font-size: 1.55em; }
.npc-szs.full .npc-szs-scene { flex: none; align-items: flex-end; }
.npc-szs-panel { flex: none; margin-top: auto; background: #fff; border-top: 1px solid #e8e6e1; padding: 0 16px calc(18px + env(safe-area-inset-bottom)); }
.npc-szs-tabs { display: flex; border-bottom: 1px solid #e8e6e1; margin: 0 -16px 16px; padding: 0 16px; }
.npc-szs-tab { appearance: none; flex: 1 1 0; min-width: 0; border: 0; background: none; font: inherit; font-size: 13px; color: #6b6b6b; padding: 13px 4px 12px; cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; touch-action: manipulation; }
.npc-szs-tab[aria-selected="true"] { color: #1a1a1a; font-weight: 500; box-shadow: inset 0 -1.5px 0 #1a1a1a; }
.npc-szs-tab:focus-visible { outline: 2px solid #1a1a1a; outline-offset: -2px; }
.npc-szs-panes { display: grid; }
.npc-szs-pane { grid-area: 1 / 1; display: flex; flex-direction: column; gap: 14px; min-width: 0; }
.npc-szs.full.tab-size [data-pane="room"], .npc-szs.full.tab-room [data-pane="size"] { visibility: hidden; }
.npc-szs.full .npc-szs-fit { margin: 0; height: auto; overflow: visible; }
.npc-szs.full .npc-szs-sizes { margin: 0; gap: 6px; }
.npc-szs.full .npc-szs-size { padding: .6em .15em; gap: .15em; }
/* rooms: the same white tiles as the sizes, one swipe row; top padding keeps the chosen tile's tick inside the scroller */
.npc-szs.full .npc-szs-chips { overflow-x: auto; scrollbar-width: none; gap: 6px; padding: 7px 7px 1px 1px; margin: -7px -16px 0 -1px; padding-right: 16px; }
.npc-szs.full .npc-szs-chip { flex: none; height: 2.9em; padding: 0 1em; font-size: 1em; overflow: visible; }
.npc-szs.full .npc-szs-chip.on::after { right: -5px; }
.npc-szs.full .npc-szs-ctl.row { gap: 10px; align-items: flex-end; }
.npc-szs.full .npc-szs-fc { height: 2.9em; }
.npc-szs.full .npc-szs-ft { font-size: .95em; }
.npc-szs.full .npc-szs-reset { align-self: center; margin-left: 2px; }
/* the "Size guide" button beside the Size label (all screens) */
.npc-root .npc-szs-btn { margin-left: auto; display: inline-flex; align-items: center; gap: 6px; padding: 5px 10px; border: 1px solid #e8e6e1; border-radius: 6px; background: #fff; color: #1a1a1a; font-family: 'DM Sans', sans-serif; font-size: 12.5px; font-weight: 500; letter-spacing: 0; text-transform: none; cursor: pointer; touch-action: manipulation; }
.npc-root .npc-szs-btn:hover { border-color: #1a1a1a; }
.npc-szs-x { position: absolute; top: calc(6px + env(safe-area-inset-top)); right: 12px; width: 40px; height: 40px; border: 1px solid #e8e6e1; border-radius: 50%; background: #fff; color: #1a1a1a; display: flex; align-items: center; justify-content: center; cursor: pointer; padding: 0; z-index: 2; }
.npc-szs .npc-scene-ceil i { display: none; }   /* the ceiling dimension line carries the number now */
.npc-dims { position: absolute; inset: 0; pointer-events: none; }
.npc-dim { position: absolute; }
.npc-dim.h { height: 0; border-top: 1px solid #b9b4aa; }
.npc-dim.v { width: 0; border-left: 1px solid #b9b4aa; }
.npc-dim.h::before, .npc-dim.h::after { content: ''; position: absolute; top: -4px; width: 1px; height: 9px; background: #b9b4aa; }
.npc-dim.h::before { left: 0; } .npc-dim.h::after { right: 0; }
.npc-dim.v::before, .npc-dim.v::after { content: ''; position: absolute; left: -4px; height: 1px; width: 9px; background: #b9b4aa; }
.npc-dim.v::before { top: 0; } .npc-dim.v::after { bottom: 0; }
/* the label IS the control: hover (desktop) or tap (phone) shows minus / plus; click the number to type */
.npc-size-pick em { display: block; margin-top: 3px; font-style: normal; font-size: 11.5px; font-weight: 500; color: #A8A49C; }
.npc-size-pick em.npc-fit-good { color: #2f6b45; font-weight: 600; }
.npc-size-pick em.npc-fit-tall { color: #B0503C; }
/* a MEDIA query, not a container query: the overlay is moved to <body> on open, so it has no
   ancestor container left and @container np would never match it again */
@media (max-width: 760px) {
  .npc-cmp { padding: 10px; }
  .npc-cmp-panel { padding: 14px; max-height: calc(100vh - 20px); }
  /* two up, not one giant column: 14 mouldings at full phone width was a mile of scrolling */
  .npc-cmp-grid { grid-template-columns: repeat(2, 1fr); gap: 10px; }
  .npc-cmp-grid figcaption { padding: 7px 8px 9px; }
  .npc-cmp-grid figcaption b, .npc-cmp-grid.two figcaption b { font-size: 13.5px; }
  .npc-cmp-grid figcaption span { font-size: 12px; line-height: 1.35; }
  .npc-cmp-title { font-size: 17px; margin: 0 44px 12px 2px; }
  /* pinned to the VIEWPORT, so it stays reachable however far the grid is scrolled,
     and sized to the 44px minimum tap target */
  .npc-zmwrap img { width: min(100%, 62vh); }   /* width drives it now — see the square-aspect note above */
  .npc-cmp-x { position: fixed; top: 16px; right: 16px; width: 44px; height: 44px; background: #fff; box-shadow: 0 3px 12px rgba(0,0,0,.22); z-index: 2; }
  .npc-size-legend { gap: 6px; }
  .npc-size-pick { padding: 6px 10px; }
  .npc-size-pick b { font-size: 13.5px; }
  .npc-size-pick span { font-size: 12px; }
  .npc-cus { padding: 10px 12px; }
  .npc-cus-in input { width: 52px; font-size: 14px; padding: 9px 22px 9px 4px; }
  .npc-step { width: 34px; }
  /* phones: one column; the fields two to a row (each ~150px) so the card is 2 rows, not 3 tall rows of one
     stepper each with the right half empty (Ollie 10 Sep) */
  .npc-size-cols { display: block; }
  .npc-size-left .npc-cus-fields { flex-direction: row; flex-wrap: wrap; gap: 8px 14px; }
  .npc-size-left .npc-cus { margin-bottom: 10px; padding: 10px 12px 8px; }
  .npc-cus-guide { margin-top: 8px; font-size: 12px; }
  .npc-cus-f { font-size: 12.5px; gap: 3px; }
  .npc-ref { font-size: 12.5px; padding: 6px 11px; white-space: nowrap; flex: 0 0 auto; }
  .npc-ref-lead { font-size: 12.5px; white-space: nowrap; flex: 0 0 auto; }
  /* ═══ Phone compaction (Ollie 14 Sep: "still too big"). Before: two rows of chips, a 340px card of three
     steppers, a floor-to-ceiling scene that was mostly blank wall, and the size picks below the fold —
     ~1300px on a 390x844 phone. Now everything fits one screen: ═══ */
  /* 1. chips on ONE row that scrolls sideways (bleeds to the panel edge so the cut-off chip hints at more) */
  .npc-size-left .npc-ref-row { flex-wrap: nowrap; overflow-x: auto; -webkit-overflow-scrolling: touch; scrollbar-width: none; gap: 6px; margin: 0 -14px 8px; padding: 0 14px; }
  .npc-size-left .npc-ref-row::-webkit-scrollbar { display: none; }
  /* 2. the measurement card collapses to one line ("Headboard 60 x 48 in - Ceiling 10 ft   Edit"); tapping
     it opens the same steppers underneath. Most customers never edit these — the defaults are the point. */
  .npc-size-left .npc-cus { padding: 0; margin-bottom: 8px; }
  .npc-cus-sum { display: flex; width: 100%; align-items: center; gap: 8px; padding: 9px 12px; border: 0; background: none; text-align: left; font-family: 'DM Sans', sans-serif; font-size: 12.5px; color: #1a1a1a; cursor: pointer; }
  .npc-cus-sum span { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .npc-cus-sum em { flex: 0 0 auto; font-style: normal; font-weight: 600; text-decoration: underline; text-underline-offset: 2px; }
  .npc-cus-sum .npc-cus-done, .npc-cus.open .npc-cus-sum .npc-cus-edit { display: none; }
  .npc-cus.open .npc-cus-sum .npc-cus-done { display: inline; }
  .npc-cus:not(.open) .npc-cus-fields, .npc-cus:not(.open) .npc-cus-guide { display: none; }
  .npc-cus.open .npc-cus-fields { padding: 0 12px; }
  .npc-cus.open .npc-cus-guide { padding: 0 12px 10px; }
  /* 3. the scene is cropped to the tallest size in JS (npcSizeParts: cropped) — no 10 ft of blank wall */
  .npc-scene-note { font-size: 12px; margin-top: 6px; }
  /* 4. size picks: a 3-across grid with the verdict INLINE after the price, so two rows cost ~90px not ~130 */
  .npc-size-legend { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 6px; margin-top: 8px; }
  .npc-size-pick { padding: 7px 4px; min-width: 0; }
  .npc-size-pick span, .npc-size-pick em { display: inline; white-space: nowrap; }
  .npc-size-pick em { font-size: 11.5px; margin: 0; }
  .npc-size-pick em { margin-left: 3px; }
  .npc-size-pick em::before { content: "· "; color: #A8A49C; font-weight: 400; margin-right: 1px; }
  .npc-cmp-title { margin-bottom: 8px; }
}

/* ── Compare / size-guide overlay in the buy-box style (Ollie 16 Sep): 6px corners, #e8e6e1 hairlines, 13px medium
   labels, chosen = one black hairline (no double ring), no fills, no 600 weights. Gated like the rest of D2. ── */

.npc-cmp-panel { border-radius: 6px; padding: 24px; box-shadow: 0 18px 50px rgba(0,0,0,.18); }
.npc-cmp-x { background: #fff; border: 1px solid #e8e6e1; }
.npc-cmp-x:hover { background: #fff; border-color: #1a1a1a; }
.npc-cmp-title { font-size: 20px; font-weight: 500; margin: 0 44px 14px 0; }
.npc-ref-lead { font-size: 13px; font-weight: 500; }
.npc-ref { font-size: 13px; font-weight: 500; border: 1px solid #e8e6e1; border-radius: 6px; padding: 6px 12px; }
.npc-ref:hover { border-color: #6b6b6b; }
.npc-ref.on { border-color: #1a1a1a; box-shadow: none; }
.npc-cus { border: 1px solid #e8e6e1; border-radius: 6px; background: #fff; padding: 12px 14px; }
.npc-cus-f { font-size: 12.5px; font-weight: 500; }
.npc-cus-in { border: 1px solid #e8e6e1; border-radius: 6px; }
.npc-cus-in:focus-within { outline: 1px solid #1a1a1a; outline-offset: 0; }
.npc-cus-in input { font-size: 13px; font-weight: 500; }
.npc-cus-guide { font-size: 12.5px; color: #6b6b6b; }
.npc-cus-sum { font-size: 12.5px; }
.npc-cus-sum em { font-weight: 500; }
.npc-size-pick { border: 1px solid #e8e6e1; border-radius: 6px; padding: 8px 12px; }
.npc-size-pick b { font-size: 13px; font-weight: 500; color: #1a1a1a; }
.npc-size-pick span { font-size: 12px; color: #6b6b6b; }
.npc-size-pick em { font-size: 11.5px; font-weight: 400; color: #9e9890; }
.npc-size-pick em.npc-fit-good { font-weight: 500; }
.npc-size-pick:hover { border-color: #6b6b6b; }
.npc-size-pick.on { border-color: #1a1a1a; box-shadow: none; }
.npc-scene-tag { font-size: 12px; font-weight: 500; }
.npc-scene-note { font-size: 12.5px; color: #6b6b6b; }
@media (min-width: 761px) { .npc-scene-note { min-height: 2.6em; } }   /* two lines reserved: a one-line note must not shrink the panel */
.npc-scene-art { border-color: #d6d2ca; }
.npc-scene-art.on { border: 1.5px solid #1a1a1a; }
.npc-scene-ceil i { font-size: 10.5px; color: #9e9890; }
.npc-cmp-grid figure { border: 1px solid #e8e6e1; border-radius: 6px; overflow: hidden; }
.npc-cmp-grid figcaption b { font-size: 13px; font-weight: 500; }
.npc-cmp-grid.two figcaption b { font-size: 14px; }
.npc-cmp-grid figcaption span { font-size: 12.5px; }
@media (max-width: 760px) {
  .npc-cmp-panel { padding: 16px 14px; }
  /* the reference chips WRAP on phones (Ollie 16 Sep: the scrolling row read as cut off) */
  .npc-size-left .npc-ref-row { flex-wrap: wrap; overflow: visible; margin: 0 0 10px; padding: 0; }
  .npc-ref { font-size: 12.5px; padding: 6px 10px; }
  .npc-cmp-x { border: 1px solid #e8e6e1; box-shadow: 0 2px 8px rgba(0,0,0,.12); }
  .npc-size-pick b { font-size: 12.5px; }
  /* the measurements are always on show, three compact fields in one row (Ollie 16 Sep: no Edit tap) */
  .npc-cus-sum { display: none !important; }
  .npc-size-left .npc-cus { padding: 8px 10px !important; margin-bottom: 8px !important; }
  .npc-cus:not(.open) .npc-cus-fields, .npc-cus.open .npc-cus-fields { display: flex !important; flex-wrap: nowrap !important; gap: 6px 8px !important; padding: 0 !important; }
  .npc-cus-guide { display: none !important; }
  .npc-cus-f { flex: 1 1 0; min-width: 0; font-size: 11px; gap: 3px; }
  .npc-cus-f > span:first-child { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; }
  .npc-cus-in { width: 100%; height: 30px; }
  .npc-step { width: 26px !important; font-size: 15px; }
  .npc-cus-num { flex: 1 1 auto; min-width: 0; }
  .npc-cus-in input { width: 100% !important; min-width: 0; padding: 0 14px 0 2px !important; font-size: 12.5px !important; height: 100%; }
  .npc-cus-in em { right: 4px; font-size: 10px; }
}

/* ═══ Add to cart: icon + price, like the live PDP ═══ */
/* Two lines: the action, then the reassurance that used to live only in the bullets below.
   Column padding drops from 16 to 12 so the second line costs 8px, not 17 (Ollie, 2026-09-08). */
.npc-root .atc { display: flex !important; flex-direction: column; align-items: center; justify-content: center; gap: 2px; padding-block: 12px !important; line-height: 1.25; }
.npc-root .npc-atc-main { display: flex; align-items: center; justify-content: center; gap: 10px; }
.npc-root .npc-atc-sub { font-size: 12.5px !important; font-weight: 500; letter-spacing: .01em; color: rgba(255,255,255,.78); }
/* ── D2 buy box (NPC_BUYBOX_D2) — everything below is scoped to .npc-d2 and disappears with the flag ── */
.npc-root.npc-d2 .glabel { font-size: 13px !important; font-weight: 500; letter-spacing: 0; text-transform: none; color: #1a1a1a; margin: 18px 0 8px !important; }
.npc-root.npc-d2 .gval { font-size: 13px; font-weight: 400; color: #6b6b6b; margin-left: 6px; }
/* Desktop step labels ("Shape  Set of 2") 13 -> 15px (Ollie 2026-09-23: too small); spacing unchanged, so no extra height
   beyond the ~3px the bigger line needs. Phone keeps 13px. */
@container np (min-width:901px) {
  .npc-root.npc-d2 .glabel { font-size: 15px !important; }
  .npc-root.npc-d2 .gval { font-size: 15px; margin-left: 8px; }
}
.npc-root.npc-d2 .opt { background: #fff; border: 1px solid #e8e6e1; border-radius: 6px; box-shadow: none; }
.npc-root.npc-d2 .opt.on, .npc-root.npc-d2 .qtile.on { border-color: #1a1a1a; box-shadow: none; background: #fff; }
.npc-root.npc-d2 .opt.on .cardmeta b, .npc-root.npc-d2 .sizecard.on b { font-weight: 500; }
/* Frame names (Ollie 2026-09-25): the desktop tile rule set every name to 600 and the .on rule above took the CHOSEN one
   back to 500 — so the selection read as the only un-bold tile. Unchosen = regular, chosen = bold, phone and desktop. */
.npc-root.npc-d2 .opts.framegrid .framecard .cardmeta b { font-weight: 500 !important; }
.npc-root.npc-d2 .opts.framegrid .framecard.on .cardmeta b { font-weight: 600 !important; }
/* ONE tick everywhere (Ollie 2026-09-18): the frame tile's black circle with a white tick. On text tiles it is a CORNER
   STAMP, half over the top-right corner, anchored to the tile — never to the words, so choosing a tile moves no text
   (an inline tick shifted "42 × 32" off the price's centre and spilled out of the sheet's chips). Frame tiles keep theirs
   in the photo corner. */
.npc-root.npc-d2 .opt.on:not(.framecard), .npc-root.npc-d2 .sizecard.on, .npc-szs-size.on { position: relative; overflow: visible !important; }
.npc-root.npc-d2 .opt.on:not(.framecard)::after, .npc-root.npc-d2 .sizecard.on::after, .npc-szs-size.on::after { content: "✓"; position: absolute; top: -6px; right: 5px; width: 16px; height: 16px; border-radius: 50%; background: #1a1a1a; color: #fff; font-family: 'DM Sans', sans-serif; font-size: 10px; font-weight: 500; line-height: 16px; text-align: center; z-index: 4; box-shadow: 0 0 0 2px #fff; pointer-events: none; }
.npc-root.npc-d2 .medcard.on .npc-badge { display: none; }   /* "NEW" shares that corner: gone while chosen */
.npc-szs-size.on::after { right: -6px; }   /* the sheet's chips are narrow and nothing clips there: stamp half outside the corner, clear of the size text */
.npc-root.npc-d2 .framecard.on > .cardmeta > b::after { content: none; }   /* the photo tile's black hairline is enough */
.npc-root.npc-d2 .cardmeta b { font-size: 13px; font-weight: 500; }
.npc-root.npc-d2 .cardmeta span { font-size: 12px; color: #6b6b6b; }
.npc-root.npc-d2 .cardmeta .fdelta:not(:empty) { font-weight: 400; color: #6b6b6b; }
.npc-root.npc-d2 .medcard .mdesc { font-size: 11.5px; color: #6b6b6b; }
.npc-root.npc-d2 .sizecard b { font-size: 13px; font-weight: 500; }
.npc-root.npc-d2 .sizecard .sprice { font-size: 12px; color: #6b6b6b; }
.npc-root.npc-d2 .opts.sizegrid .sizecard { padding: 12px 8px !important; }
.npc-root.npc-d2 .npc-badge { position: absolute; top: 6px; right: 8px; left: auto; transform: none; padding: 0; background: none; color: #6b6b6b; font-size: 9.5px; letter-spacing: .06em; }
.npc-root.npc-d2 .npc-badge-new { background: none; color: #6b6b6b; }
.npc-root.npc-d2 .framecard { display: flex; flex-direction: column; }
/* overlays on the photo: "+$70" or Popular top-right, the magnifier bottom-left on the moulding, and on the
   CHOSEN frame a black circle with a white tick bottom-right (the photo-swatch convention; a hairline alone
   is hard to see round a dark moulding). */
.npc-root.npc-d2 .framecard.on .cardimg::after { content: "✓"; position: absolute; right: 5px; bottom: 5px; width: 18px; height: 18px; border-radius: 50%; background: #1a1a1a; color: #fff; font-size: 11px; line-height: 18px; text-align: center; z-index: 4; box-shadow: 0 1px 3px rgba(0,0,0,.2); }
/* name UNDER the photo, whole and wrapping (Ollie 16 Sep: the in-photo name was "horrible"); every tile reserves
   two lines so the row stays level, and a three-line name stretches the whole row together */
.npc-root.npc-d2 .framecard .cardmeta { display: flex; padding: 5px 3px 6px; }
.npc-root.npc-d2 .framecard .cardmeta b { display: block !important; width: 100%; min-height: 2.4em; white-space: normal; overflow: visible; text-overflow: clip; -webkit-line-clamp: unset; text-align: center; font-size: 11px; line-height: 1.2; overflow-wrap: normal; hyphens: manual; }
.npc-root.npc-d2 .framecard .npc-badge { position: absolute; top: 6px; right: 6px; left: auto; z-index: 3; margin: 0; padding: 2px 6px; border-radius: 999px; background: rgba(255,255,255,.92); color: #1a1a1a; font-size: 9px; letter-spacing: .05em; box-shadow: 0 1px 3px rgba(0,0,0,.12); }
.npc-root.npc-d2 .framecard .npc-fov { position: absolute; top: 6px; right: 6px; z-index: 3; min-height: 0; padding: 2px 6px; border-radius: 999px; background: rgba(255,255,255,.92); color: #1a1a1a; font-size: 10.5px; font-weight: 500; line-height: 1.3; box-shadow: 0 1px 3px rgba(0,0,0,.12); }
.npc-root.npc-d2 .framecard .npc-fov:empty { display: none; }
/* PHONE THUMBNAIL STRIP (Ollie 2026-10-03, mock "C"): the desktop thumbnails come back on phones as one row of 43px tiles
   UNDER the picture, inside the sticky gallery — so they stay on screen while the choices scroll and always say what
   else there is (rooms, Up close, Frame, details, size guide). Tapping one jumps the carousel (setView); swiping the
   carousel moves the outline (onStageScroll). The pinned area grows by 58px. Label tiles keep only their icon at this
   size (the words would be ~6px). Pinned-full-size layout (.npc-noshrink) only — the shrink layout clips the gallery. */
@container np (max-width:900px) {
  .npc-root.npc-noshrink .gallery { height: calc(var(--npc-slide) + 72px) !important; gap: 0 !important; }   /* + the strip's height (66: six edge-to-edge tiles; was 76 scrollable, 58 before that) */   /* no see-through gap between picture and strip once pinned */
  .npc-root.npc-noshrink .viewthumbs { display: flex !important; order: 99; flex: 0 0 58px !important; height: 58px; box-sizing: border-box; padding: 3px 16px 5px !important; margin: 0 !important; gap: 4px !important; background: #fff; overflow-x: auto; overflow-y: hidden; scrollbar-width: none; align-items: flex-start; --vtn: 43 !important; }
  .npc-root.npc-noshrink .viewthumbs::-webkit-scrollbar { display: none; }
  .npc-root.npc-noshrink .viewthumbs .vth { flex: none; }   /* size: see the OPTION C block (64px); before it: 50x50 square (46x50 and landscape 58x40 rejected 4 Oct) */
  .npc-root.npc-noshrink .viewthumbs .vth > * { left: 0; top: 0; }
  .npc-root.npc-noshrink .viewthumbs .npc-szthumb i { display: none !important; }
  .npc-root.npc-noshrink .viewthumbs .npc-szthumb svg { width: 40px; height: 30px; }
  /* 2026-10-03 pm (Ollie): the picture runs FULL WIDTH (no peek of the next slide); the strip is centred (auto margins, so a
     strip that overflows still scrolls from its first tile); the Product details tile is a real miniature of that slide. */
  /* OPTION C trial (Ollie 2026-10-04 pm): the live page's layout — picture 86% wide with the next slide peeking on
     the right (a square picture, so 14% less width = 56px less height), option-1 thumbnails (64px, full colour, strong
     ring, fade at the end). Title ABOVE the picture (option C) ran for an hour and Ollie chose A: title stays UNDER the picture. */
  .npc-root.npc-noshrink .gallery { --npc-slide: 94cqw !important; }   /* 86 → 94 (Ollie 4 Oct eve: thumbs 10–20% bigger, picture 'somewhere between') */
  .npc-root.npc-noshrink .stage { padding: 0 3cqw !important; scroll-padding-left: 3cqw !important; gap: 14cqw !important; }   /* NO peek (Ollie 2026-10-04 eve: distracting): picture centred with 7cqw either side, and the 14cqw gap parks the neighbours fully off-screen; swipe + snap unchanged (stageStep reads the gap) */
  /* Ollie 2026-10-04 eve: NOT scrollable — all six tiles share the picture's own width (7cqw margins = the picture's edges),
     equal flex tiles, square via aspect-ratio. Six tiles on a 402 phone = 53px each. */
  .npc-root.npc-noshrink .viewthumbs { justify-content: space-between; flex: 0 0 72px !important; height: 72px; padding: 6px 3cqw 6px !important; gap: 4px !important; overflow: hidden !important; }   /* six tiles on 94cqw with 4px gaps = 60px each on a 402 phone (+13% on the 53px of an hour ago) */
  .npc-root.npc-noshrink .viewthumbs .vth { flex: 1 1 0 !important; --vtn: 60 !important; width: auto !important; height: auto !important; aspect-ratio: 1 / 1; min-width: 0; opacity: 1 !important; filter: none !important; border-radius: 6px !important; }
  .npc-root.npc-noshrink .viewthumbs .vth::after { border-radius: 6px !important; box-shadow: inset 0 0 0 1px #E0DED9 !important; }
  .npc-root.npc-noshrink .viewthumbs .vth.on::after { box-shadow: inset 0 0 0 2.5px #1a1a1a !important; }
  .npc-root .cols > .npc-titlerow { padding: 10px 16px 0; margin: 0 0 10px !important; }
  .npc-root.npc-noshrink .viewthumbs .npc-dimport { display: block !important; }
  .npc-root.npc-noshrink .viewthumbs .npc-dimport ~ *, .npc-root.npc-noshrink .viewthumbs .vth:has(.npc-dimport) .npc-szthumb { display: none !important; }
  .npc-root .framecard .npc-zm { display: none !important; }   /* no magnifier on phones (Ollie 2026-10-03) */
  /* phone zoom in place (npcPzoom): the layers' scale animates; while zoomed the slide owns the finger (drag = pan) */
  .npc-root .stage > .wallslide { overflow: hidden; }
  .npc-root .stage > .wallslide > * { transition: scale .25s ease; }
  .npc-root .stage > .wallslide.npc-pz { touch-action: none; }
  .npc-root .stage > .wallslide.npc-pz .wallcta { opacity: 0; pointer-events: none; }
}
.npc-dimport { display: none; }   /* desktop keeps the "Product details" label tile */
/* PHONE SHAPE ROW = the frame row's format (Ollie 2026-10-03): one scrollable line of tiles, a square paper plate with
   the shape's outline on top and the name underneath, the same tile width and the same chosen mark (black circle +
   tick bottom-right on the plate) as the frames. Desktop keeps its icon+label chips. */
/* DESKTOP shape tiles (Ollie 2026-10-04): five across like the frame strip, the artwork rendered in each shape on a
   4:3 plate (not square — keeps the row ~115px tall vs the frame tiles' ~138px), name below, ✓ in the plate corner. */
@container np (min-width:901px) {
  .npc-root .opts.shapegrid { display: flex !important; flex-wrap: nowrap !important; gap: 8px !important; }
  .npc-root .opts.shapegrid .shapecard2 { flex: 0 0 calc((100% - 32px) / 5) !important; min-width: 0 !important; max-width: none !important; height: auto !important; flex-direction: column !important; align-items: stretch !important; justify-content: flex-start !important; gap: 0 !important; padding: 0 !important; overflow: hidden; }
  .npc-root .opts.shapegrid .shapecard2 .cardimg { display: flex !important; position: relative; width: auto !important; height: auto !important; aspect-ratio: 4 / 3 !important; margin: 3px 3px 0 !important; background: #f6f6f6 !important; border-radius: 4px !important; align-items: center; justify-content: center; overflow: hidden; }
  .npc-root .opts.shapegrid .shapecard2 .cardimg .npc-shp { width: 26px; height: 21px; }
  .npc-root .opts.shapegrid .shapecard2 .cardmeta { width: 100%; height: 26px; padding: 0 3px !important; justify-content: center; align-items: center; }
  .npc-root .opts.shapegrid .shapecard2 .cardmeta b { width: 100%; text-align: center; font-size: 11px !important; line-height: 14px; white-space: nowrap; }
  .npc-root.npc-d2 .opts.shapegrid .shapecard2.on::after { content: none !important; }
  .npc-root.npc-d2 .opts.shapegrid .shapecard2.on .cardimg::after { content: "✓"; position: absolute; right: 5px; bottom: 5px; width: 18px; height: 18px; border-radius: 50%; background: #1a1a1a; color: #fff; font-size: 11px; line-height: 18px; text-align: center; z-index: 4; box-shadow: 0 1px 3px rgba(0,0,0,.2); }
}
@container np (max-width:900px) {
  .npc-root .opts.shapegrid { display: flex !important; flex-wrap: nowrap !important; overflow-x: auto !important; gap: 8px !important; scrollbar-width: none; scroll-snap-type: x proximity; -webkit-overflow-scrolling: touch; }
  .npc-root .opts.shapegrid::-webkit-scrollbar { display: none; }
  .npc-root .opts.shapegrid .shapecard2 { flex: 0 0 84px !important; min-width: 84px !important; max-width: 84px !important; height: auto !important; flex-direction: column !important; align-items: stretch !important; justify-content: flex-start !important; gap: 0 !important; padding: 0 !important; scroll-snap-align: start; }
  /* compact + even hairline (Ollie 2026-10-03): whole-pixel sizes (72 x 46 plate) so no edge lands on a half pixel, and the
     plate is inset 3px so the border meets WHITE on all four sides (against the grey plate the top line all but vanished) */
  .npc-root .opts.shapegrid .shapecard2 { border-color: #e3e0da !important; }
  /* chosen tile: the black line drawn INSIDE (border + inset shadow) — the default outer 1px shadow was clipped top and
     bottom by the scrolling row (Ollie 2026-10-03: "the black lines don't follow around the selected shape") */
  .npc-root .opts.shapegrid .shapecard2.on { border-color: #1a1a1a !important; box-shadow: inset 0 0 0 1px #1a1a1a !important; }
  .npc-root .opts.shapegrid .shapecard2 .cardimg { position: relative; display: flex !important; align-items: center; justify-content: center; overflow: hidden; width: auto !important; height: 58px !important; aspect-ratio: auto !important; margin: 3px 3px 0 !important; background: #f6f6f6 !important; border-radius: 4px !important; }   /* phone rail: live render of the piece too (4 Oct pm); plate = the product wall's #f6f6f6 */
  .npc-root .opts.shapegrid .npc-shp { width: 26px; height: 21px; }
  .npc-root .opts.shapegrid .shapecard2 .cardmeta { width: 100%; height: 24px; padding: 0 3px !important; justify-content: center; align-items: center; }
  .npc-root .opts.shapegrid .shapecard2 .cardmeta b { width: 100%; text-align: center; font-size: 11px !important; line-height: 14px; white-space: nowrap; }
  .npc-root.npc-d2 .opts.shapegrid .shapecard2.on::after { content: none !important; }
  .npc-root.npc-d2 .opts.shapegrid .shapecard2.on .cardimg::after { content: "✓"; position: absolute; right: 4px; bottom: 4px; width: 18px; height: 18px; border-radius: 50%; background: #1a1a1a; color: #fff; font-size: 11px; line-height: 18px; text-align: center; z-index: 4; box-shadow: 0 1px 3px rgba(0,0,0,.2); }
}
.npc-root.npc-d2 .framecard .npc-zm { top: auto; bottom: 5px; left: 5px; right: auto; width: 22px; height: 22px; }
/* the per-tile magnifier: hidden until the tile is hovered (desktop); a small quiet one on touch */
.npc-root.npc-d2 .framecard .npc-zm { opacity: 0; transition: opacity .15s; }
.npc-root.npc-d2 .framecard:hover .npc-zm, .npc-root.npc-d2 .framecard:focus-visible .npc-zm { opacity: 1; }
@media (hover: none) { .npc-root.npc-d2 .framecard .npc-zm { opacity: .75; transform: scale(.85); } }
.npc-root.npc-d2 .npc-popnote { font-size: 12px; color: #6b6b6b; margin-top: 8px; }
.npc-root.npc-d2 .npc-popnote:empty { display: none; }
.npc-root.npc-d2 .npc-cmp-open { border: 0; background: none; padding: 6px 0; margin: -6px 0 -6px auto; font-size: 12px; font-weight: 400; color: #6b6b6b; text-decoration: underline; text-underline-offset: 3px; border-radius: 0; }
.npc-root.npc-d2 .npc-cmp-open svg { display: none; }
.npc-root.npc-d2 .npc-cmp-open:hover { color: #1a1a1a; border-color: transparent; }
.npc-root.npc-d2 :has(> #frameOpts) { position: relative; }
.npc-root.npc-d2 :has(> #frameOpts)::after { content: none; }   /* white edge fade REMOVED on phones too (Ollie 2026-10-03: it sat over the last frame at the end of the row) */
/* DESKTOP frame strip (Ollie 2026-10-03): the arrows leave the strip and sit at the right of the "Frame" label row, so all
   five tiles are whole and nothing is laid over a frame photo; the white edge fade goes too. Quiet 26px hairline circles;
   at the end of travel an arrow greys rather than vanishing (it no longer covers anything). Phones keep the overlay arrows. */
@container np (min-width:901px) {
  .npc-root.npc-d2 :has(> #frameOpts)::after { display: none !important; }
  .npc-root.npc-d2 .framescroll { overflow: visible !important; }
  .npc-root.npc-d2 .fnav { top: auto !important; bottom: calc(100% + 4px) !important; transform: none !important; width: 26px !important; height: 26px !important; box-shadow: none !important; border: 1px solid #e8e6e1 !important; background: #fff !important; }
  .npc-root.npc-d2 .fnav svg { width: 13px; height: 13px; }
  .npc-root.npc-d2 .fnav.next { right: 0 !important; left: auto !important; }
  .npc-root.npc-d2 .fnav.prev { right: 34px !important; left: auto !important; }
  .npc-root.npc-d2 .fnav:hover { border-color: #1a1a1a !important; }
  .npc-root.npc-d2 .fnav.off { opacity: .35 !important; pointer-events: none; }
}
.npc-root.npc-d2 .cardimg, .npc-root.npc-d2 .fspic { border-radius: 2px; }
/* "See it on your wall": same family as the tiles — 6px corners, hairline, 12.5px medium, no pill, quiet shadow */
.npc-root.npc-d2 .stage .npc-wallcta, .npc-root.npc-d2 .npc-wallcta { border-radius: 6px !important; border: 1px solid #e8e6e1 !important; background: #fff !important; padding: 8px 12px !important; gap: 7px !important; font-size: 12.5px !important; font-weight: 500 !important; letter-spacing: 0 !important; color: #1a1a1a !important; box-shadow: 0 1px 4px rgba(0,0,0,.10) !important; }
.npc-root.npc-d2 .npc-wallcta svg { width: 14px !important; height: 14px !important; }
/* Desktop (Ollie 2026-09-23): the wall button up a size — 15px, taller hit box; the phone keeps its own rule below */
@container np (min-width:901px) {
  .npc-root.npc-d2 .stage .npc-wallcta, .npc-root.npc-d2 .npc-wallcta { font-size: 15px !important; padding: 11px 17px !important; gap: 9px !important; }
  .npc-root.npc-d2 .npc-wallcta svg { width: 18px !important; height: 18px !important; }
}
.npc-root.npc-d2 .npc-wallcta:hover { border-color: #1a1a1a !important; box-shadow: 0 1px 4px rgba(0,0,0,.10) !important; }
@container np (max-width:900px) {
  .npc-root.npc-d2 .stage .npc-wallcta { padding: 7px 10px !important; font-size: 12px !important; right: 10px !important; bottom: 10px !important; }
  .npc-root.npc-d2 .npc-wallcta svg { width: 13px !important; height: 13px !important; }
}
/* Phones (2026-09-20, the one survivor of the space pass): title and rating share ONE row — title left, stars
   right, wrapping when a long title needs the width. npcLayout() wraps them in .npc-titlerow on phones only.
   The 4:3 port, scroll-up header and tile compaction from the same pass were reverted the same day (Ollie). */
@container np (max-width:900px) {
  .npc-root .npc-titlerow { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: 6px 12px; margin: 4px 0 18px; }   /* 2026-09-23 (Ollie: "super tight"): picture->title 12->16 (the 4px margin), title->stars 2->6, stars->Medium 8->18 */
  .npc-root .npc-titlerow h1 { margin: 0 !important; flex: 0 1 auto; }
  .npc-root .npc-titlerow .np-top-review { margin: 0 !important; flex: 0 0 auto; max-width: 100%; }   /* capped so the brand count wraps instead of running off the screen */
}
/* Image 1 artwork panel, -2% brightness (Ollie 2026-09-20): the Photoshop Brightness -5 (on a -150..150 slider, i.e.
   ~5 of 255 levels) he used to apply to the artwork file before mocking it up. Only the artwork pixels, not the frame
   or wall; product view only (rooms carry their own grade via photoPass, and every other slide hides .artwrap). */
/* NO CSS FILTERS ON THE ARTWORK (Ollie 2026-10-06): Safari rasterises a filtered element at its layout size and only scales that
   bitmap when the wall zooms (izoom, lightbox, pinch), so the ~300px room piece came up smeared and jagged at 2x. The photo pass keeps
   its overlays (tint, shadow, rim) — those are separate elements — and drops blur, grain, brightness/contrast and the wall-light
   brightness/saturate on the image layer. Proven in WebKit + Chrome, room-zoom-probe/*.png. */
.npc-root .npc-photo .art, .npc-root .artwrap .art { filter: none !important; }
.npc-root .artwrap .img { filter: none !important; }
.npc-root .npc-photo .ph-grain { display: none !important; }
/* Tick list under the button (2026-09-20): one column, check + one line each, no rule, no fill. */
.npc-root .npc-trust.npc-trust-list { display: flex !important; flex-direction: column; gap: 9px; grid-template-columns: none; margin-top: 14px; padding: 0 2px; background: none; border: 0; border-radius: 0; container-type: normal; }
.npc-root .npc-trust-list .npc-trust-item { flex-direction: row !important; align-items: center !important; justify-content: flex-start; text-align: left !important; gap: 9px !important; padding: 0 !important; border: 0 !important; }
.npc-root .npc-trust-list .npc-trust-item svg { flex: 0 0 19px; width: 19px; height: 19px; color: #1a1a1a; }
.npc-root .npc-trust-list .npc-trust-item span { display: block; font-size: 13px !important; font-weight: 400; line-height: 1.4; color: #1a1a1a !important; white-space: normal; overflow: visible; text-overflow: clip; }
.npc-root .npc-trust-list .npc-trust-item b { display: inline; font-size: 13px !important; font-weight: 600; color: #1a1a1a; white-space: normal; }
.npc-root .npc-trust-list .npc-trust-item u { text-decoration: underline; text-underline-offset: 2px; text-decoration-thickness: 1px; }
.npc-root.npc-d2 .atc { border-radius: 6px; font-size: 16px; font-weight: 500; letter-spacing: 0; min-height: 48px; }   /* 16 = the size tiles' figures (2026-09-24, Ollie: ATC text looked small on phones; was 14). Desktop is --t-lg 17 via the !important rule above, matching its 17px tiles */   /* 48: without the delivery sub-line (2026-09-20) the button fell to 42px */
.npc-root.npc-d2 .npc-atc-dot { display: inline-block; width: 4px; height: 4px; border-radius: 50%; background: currentColor; margin: 0 .55em; vertical-align: middle; position: relative; top: -1px; }   /* a small bullet between the words and the price (Ollie 2026-09-21; was an empty spacer since 09-18) */
.npc-root.npc-d2 .npc-atc-main { gap: 0; }
/* Tablets in portrait get the phone layout, where the pinned square was ~730px tall and left ~240px to choose in
   (audit 2026-09-24): cap it at 62% of the screen height; phones (<600px) are unaffected. */
@container np (min-width:600px) and (max-width:900px) { .npc-root .gallery { --npc-slide: min(95cqw, 62vh); } }
.npc-root .npc-atc-ic { width: 1.25em; height: 1.25em; flex: none; margin-right: .45em; position: relative; top: -1px; }   /* bag-plus, as on the live PDP (Ollie 2026-09-24) */
.npc-root.npc-d2 .npc-atc-sub { font-size: 12px !important; font-weight: 400; }
.npc-root.npc-d2 .npc-trust { background: none; border-radius: 0; border-top: 0; padding: 12px 0 0; margin-top: 8px; }   /* no rule under the button (Ollie 16 Sep) */
.npc-root.npc-d2 .npc-trust-item + .npc-trust-item { border-left: 0; }
.npc-root.npc-d2 .npc-trust-item b { font-size: 13px; font-weight: 500; }
.npc-root.npc-d2 .npc-trust-item span { font-size: 12px; color: #9e9890; }
.npc-root.npc-d2 .npc-trust-item svg { display: none; }
.npc-root.npc-d2 .npc-trust-list .npc-trust-item svg { display: block; }   /* the icon list (2026-09-20) keeps its symbols; the D2 grid hid them */
/* less air under the buy column before the next section (Ollie 16 Sep: "a tonne of white space") */
.npc-root.npc-d2 .wrap { padding-bottom: 24px !important; }
/* accordions: no card, no row icons — hairline dividers, 13px medium headings, 13px grey body (D2) */
.npc-root.npc-d2 .npc-acc-scope { margin-top: 18px !important; }
.npc-root.npc-d2 .npc-acc-scope .accordion { border: 0; border-top: 1px solid #e8e6e1; border-radius: 0; padding: 0; margin-top: 0; }
.npc-root.npc-d2 .npc-acc-scope .accordion .details { border-block-start: 0 !important; border-block-end: 1px solid #e8e6e1 !important; }
.npc-root.npc-d2 .npc-acc-scope .accordion .details__header { font-family: 'DM Sans', sans-serif; font-size: 13px; font-weight: 500; letter-spacing: 0; line-height: 1.4; color: #1a1a1a; padding: 14px 0; }
.npc-root.npc-d2 .npc-acc-scope .accordion .details__icon { display: none; }
.npc-root.npc-d2 .npc-acc-scope .accordion summary .svg-wrapper { width: 14px; height: 14px; opacity: .55; }
.npc-root.npc-d2 .npc-acc-scope .accordion .details-content { padding-inline: 0; }
.npc-root.npc-d2 .npc-acc-scope .accordion--dividers .details-content { padding-block-end: 16px; }
.npc-root.npc-d2 .npc-acc-scope .accordion .details-content, .npc-root.npc-d2 .npc-acc-scope .accordion .details-content p, .npc-root.npc-d2 .npc-acc-scope .accordion .details-content .rte p, .npc-root.npc-d2 .npc-acc-scope .accordion .details-content li { font-family: 'DM Sans', sans-serif; font-size: 13px; line-height: 1.6; color: #6b6b6b; }
.npc-root.npc-d2 .npc-acc-scope .accordion .details-content b, .npc-root.npc-d2 .npc-acc-scope .accordion .details-content strong { font-weight: 500; color: #1a1a1a; }
.npc-root.npc-d2 .npc-acc-scope .accordion .details-content h3, .npc-root.npc-d2 .npc-acc-scope .accordion .details-content h4 { font-family: 'DM Sans', sans-serif; font-size: 13px; font-weight: 500; color: #1a1a1a; margin: 10px 0 4px; }
/* ═══ Bigger, uniform type for the tick list + accordions (Ollie 2026-09-23: "as big as possible without breaking the
   alignment", for older customers). ONE heading size and ONE body size for every accordion, whatever the panel's own
   inline <style> says (the shipping panel was 14px, materials 12.5–14px, description 13px). Desktop 16/15, phone 15/14. */
.npc-root.npc-d2 { --npc-acc-h: 16px; --npc-acc-b: 15px; --npc-tick: 15px; --npc-tick-ic: 21px; }
@container np (max-width:900px) { .npc-root.npc-d2 { --npc-acc-h: 15px; --npc-acc-b: 14px; --npc-tick: 14px; --npc-tick-ic: 20px; } }
.npc-root.npc-d2 .npc-trust-list { gap: 11px; }
.npc-root.npc-d2 .npc-trust-list .npc-trust-item { gap: 11px !important; }
.npc-root.npc-d2 .npc-trust-list .npc-trust-item svg { flex: 0 0 var(--npc-tick-ic); width: var(--npc-tick-ic); height: var(--npc-tick-ic); }
.npc-root.npc-d2 .npc-trust-list .npc-trust-item span, .npc-root.npc-d2 .npc-trust-list .npc-trust-item b { font-size: var(--npc-tick) !important; line-height: 1.4; }
.npc-root.npc-d2 .npc-acc-scope .accordion .details__header { font-size: var(--npc-acc-h); padding: 16px 0; }
.npc-root.npc-d2 .npc-acc-scope .accordion summary .svg-wrapper { width: 15px; height: 15px; }
.npc-root.npc-d2 .npc-acc-scope .accordion .details-content { font-size: var(--npc-acc-b); line-height: 1.6; }
.npc-root.npc-d2 .npc-acc-scope .accordion .details-content, .npc-root.npc-d2 .npc-acc-scope .accordion .details-content :is(p, li, span) { color: #4a4a4a !important; }   /* darker body text (2026-09-24, idea A): #6b6b6b read faint */
.npc-root.npc-d2 .npc-acc-scope .accordion .details-content .np-acc-a b { font-weight: 600; color: #1a1a1a !important; }   /* the lead-ins must read as bold to scan by (the D2 rule sets b to 500) */
.npc-root.npc-d2 .npc-acc-scope .accordion .details-content .np-acc-new { color: #9e9890 !important; font-size: 10.5px !important; line-height: 1 !important; }
.npc-root.npc-d2 .npc-acc-scope .accordion .details-content :is(p, li, span, a, strong, b, em, div) { font-size: inherit !important; line-height: 1.6 !important; }
.npc-root.npc-d2 .npc-acc-scope .accordion .details-content :is(h3, h4) { font-family: 'DM Sans', sans-serif; font-size: var(--npc-acc-b) !important; font-weight: 500; line-height: 1.4; }
.npc-root .npc-atc-sub b { font-weight: 600; color: #fff; }

/* ═══ Feedback: things you press should feel pressed (2026-09-07) ═══
   Hover lifts are behind (hover: hover) so a phone never leaves a tile stuck in its
   hover state after a tap, and everything collapses under prefers-reduced-motion. */
/* Hover = the live PDP's thumbnail behaviour: the picture drifts in 9% inside its own
   frame and the ring darkens; the tile does NOT move (a lift shoves its neighbours'
   optical rhythm around). The base rule scales a 92px child to --vtn from origin 0 0,
   so the hover transform re-states that formula x1.09 and translates back by half the
   growth - that is what makes it read as a centre zoom rather than a corner one. */
.npc-root .vth > * { transition: transform .45s cubic-bezier(.22,1,.36,1); }
.npc-root .vth::after { transition: box-shadow .18s ease; }
.npc-root .npc-nav { transition: transform .16s cubic-bezier(.22,.72,.28,1), background .16s ease, box-shadow .16s ease; }
.npc-root .opt { transition: border-color .15s ease, box-shadow .15s ease, transform .13s cubic-bezier(.22,.72,.28,1); }
.npc-root .atc { transition: transform .13s ease, opacity .15s ease; }
/* Pointer devices ONLY, :active included. On a touchscreen the press-squash fires on
   every half-tap and while you are scrolling past a tile, which reads as the page
   zooming in and out under your thumb (Ollie, 2026-09-07). Phones get no transform. */
@media (hover: hover) {
  .npc-root .vth:hover > * { transform: translate(calc(var(--vtn) * -0.045px), calc(var(--vtn) * -0.045px)) scale(calc(var(--vtn) / 92 * 1.09)); }
  .npc-root .vth:hover::after { box-shadow: inset 0 0 0 1.5px rgba(26,26,26,.45) !important; }
  .npc-root .vth.on:hover::after { box-shadow: inset 0 0 0 1.5px #1a1a1a !important; }
  .npc-root .vth:active { transform: scale(.95); transition-duration: .08s; }
  .npc-root .npc-nav:hover { transform: translateY(-50%) scale(1.09); box-shadow: 0 4px 18px rgba(0,0,0,.2); }
  .npc-root .npc-nav:active { transform: translateY(-50%) scale(.9); transition-duration: .08s; }
  .npc-root .opt:not(.on):hover { transform: translateY(-2px); }
  .npc-root .opt:active { transform: scale(.97); transition-duration: .07s; }
  .npc-root .atc:not([disabled]):active { transform: scale(.985); }
}
@media (prefers-reduced-motion: reduce) {
  .npc-root .vth, .npc-root .npc-nav, .npc-root .opt, .npc-root .atc { transition: none !important; }
  .npc-root .vth:hover > *, .npc-root .vth:active, .npc-root .opt:hover, .npc-root .opt:active, .npc-root .atc:active { transform: none !important; }
  .npc-root .npc-nav:hover, .npc-root .npc-nav:active { transform: translateY(-50%) !important; }
}

/* The ANNOUNCEMENT BAR still scrolls away on this template — relative, not static, because its
   background is absolutely positioned and needs a positioned ancestor or it floods the page. */
div.shopify-section-group-header-group:not(.header-section) { position: relative !important; }
/* The HEADER is ALWAYS pinned on this template (forced in sections/header.liquid): the menu, search
   and cart have to be reachable from anywhere. It out-ranks the pinned gallery (z-index 30); the
   theme's own --layer-sticky is only 8. */
header.shopify-section-group-header-group { z-index: 40 !important; }
/* Sale mode (2026-10-14): header-announcements drops the pinned header by the bar's height so it sits under a
   STICKY bar. Here the bar scrolls away (above), so the header must pin at the very top or a strip of page shows above it. */
#header-group .header-section:has(> #header-component[sticky='always']) { top: 0 !important; }
/* ...and the gallery pins BELOW it rather than behind it, so the header never crops the picture.
   --npc-headh is the header's measured height less the 1px the theme pins it by, written on resize
   only — the theme's own --header-height is a static 60px default and does not match reality (47px
   on a phone). Desktop keeps its 12px breathing room on top of that. */
@container np (min-width:901px) { .npc-root .gallery { top: calc(var(--npc-headh, 0px) + 12px) !important; } }   /* scoped: an unscoped rule sits AFTER the phone block in this sheet and would win it */

/* ── frame close-up slide (2026-09-28): photo on top, caption band on the details slide's paper ── */
.npc-root .framescene { display: none; position: absolute; inset: 0; z-index: 3; background: #F6F4EF; flex-direction: column; }
.npc-root .framescene .dimmini { position: absolute; left: 0; top: 0; }
.npc-fd { position: absolute; inset: 0; display: flex; flex-direction: column; font-family: 'DM Sans', sans-serif; color: #1a1a1a; -webkit-font-smoothing: antialiased; }
/* the macro is square with the mitre near its centre, so a landscape crop of the middle keeps the corner */
.npc-fd-pic { flex: 1 1 auto; min-height: 0; background-size: cover; background-position: center; background-color: #EDEAE3; }
.npc-fd-txt { flex: 0 0 auto; padding: 1.1em 1.35em 1.25em; border-top: 1px solid #e8e6e1; text-align: left; }
.npc-fd-name { font-family: 'Lora', Georgia, serif; font-weight: 500; font-size: 1.22em; line-height: 1.3; margin-bottom: 0; display: flex; flex-wrap: wrap; align-items: baseline; gap: .2em .7em; }
.npc-fd-spec { font-family: 'DM Sans', sans-serif; font-weight: 400; font-size: .8em; color: #6b6b6b; }
.npc-fd p { margin: 0; font-size: 1em; line-height: 1.45; color: #1a1a1a; text-wrap: pretty; }
.npc-fd p + p { margin-top: .35em; }
.npc-fd p b { font-weight: 500; }
/* the 92px tile shows the photo alone — a band of 13px text at tile scale is a smear (same rule as the old .fdband) */
.vthwall .npc-fd-txt { display: none; }

/* ── Up close slide (2026-10-01): crop on top, the frame slide's caption band underneath with a locator ── */
.npc-root .closeupscene { background: #F6F4EF; }
.npc-cu { position: absolute; inset: 0; display: flex; flex-direction: column; font-family: 'DM Sans', sans-serif; color: #1a1a1a; -webkit-font-smoothing: antialiased; }
.npc-cu-pic { flex: 1 1 auto; min-height: 0; position: relative; overflow: hidden; container-type: size; background-color: #EDEAE3; }
.npc-cu-pic img { position: absolute; max-width: none; display: block; }
.npc-cu-pic.npc-cu-old { background-size: cover; background-position: center; }
.npc-cu-txt { flex: 0 0 auto; display: flex; align-items: center; justify-content: space-between; gap: 1em; padding: 1em 1.35em 1.1em; border-top: 1px solid #e8e6e1; }
.npc-cu-name { font-family: 'Lora', Georgia, serif; font-weight: 500; font-size: 1.22em; line-height: 1.3; }
.npc-cu-sub { font-size: .85em; color: #6b6b6b; line-height: 1.35; margin-top: .1em; }
.npc-cu-loc { position: relative; flex: none; height: 3.6em; max-width: 6em; display: block; border: 1px solid #e8e6e1; border-radius: 3px; overflow: hidden; background: #fff; }
.npc-cu-loc img { display: block; width: 100%; height: 100%; object-fit: cover; }
.npc-cu-loc i { position: absolute; border: 1.5px solid #fff; outline: 1px solid rgba(0,0,0,.55); box-sizing: border-box; }
/* the thumbnail tile shows the crop alone (same rule as the frame slide) */
.vthwall .npc-cu-txt { display: none; }

.npc-root .dimsvg { max-width: none !important; max-height: none !important; }

/* DESKTOP frame tiles: ONE row that SCROLLS (Ollie 2026-10-04 late: "make the frame choice scrollable" — replaces the two-row
   wrap tried earlier that day). Five tiles whole plus half of the sixth as the scroll cue; native trackpad/wheel scroll with a
   thin scrollbar, and the label-row arrows (updateFrameNav) stay as the click control. Canvas (5 frames) fits and shows no cue. */
@container np (min-width:901px) {
  .npc-root .opts.framegrid { flex-wrap: nowrap !important; overflow-x: auto !important; overflow-y: hidden !important; gap: 8px !important; scrollbar-width: thin; padding-bottom: 6px !important; scroll-snap-type: x proximity; }
  .npc-root .opts.framegrid .framecard { flex: 0 0 calc((100% - 44px) / 5.5) !important; scroll-snap-align: start; }
}
/* "The frame" slide shows the TRUE moulding colour (Ollie 2026-10-04: the drawn corner read lighter than his frame photos).
   Measured: the texture files match the photos (black L 8.3 vs 8.6) but .artlight lays a 17% warm highlight over the top-left
   of the piece (--wl-arthi = WL.art x 0.02) and this slide IS that corner, so every frame was washed (+16..+25 L, Copper
   -12 a/b). The highlight is cut to a quarter here; the product slide keeps its lighting. */
.npc-root .framescene .npc-fzoom .artlight { opacity: .25; }
/* "The frame" slide: the moulding's name in a small box, bottom-left (Ollie 2026-10-04) */
.npc-root .npc-fz-name { position: absolute; left: 12px; bottom: 12px; z-index: 6; background: rgba(255,255,255,.94); border: 1px solid #e8e6e1; border-radius: 6px; padding: 5px 9px; font-family: 'DM Sans', sans-serif; font-size: 12.5px; font-weight: 500; line-height: 1.3; color: #1a1a1a; pointer-events: none; white-space: nowrap; }
.vthwall .npc-fz-name { display: none; }

/* shape tiles: small CDN crops instead of the 2400px previews (speed audit 2026-10-04) */
.npc-root .npc-shapeico .img[style*="--artimg-horizontal-A"] { background-image: var(--tile-horizontal-A, var(--artimg-horizontal-A)) !important; background-size: var(--tile-horizontal-A-sz, 100% 100%) !important; background-position: var(--tile-horizontal-A-ps, 50% 50%) !important; }
.npc-root .npc-shapeico .img[style*="--artimg-horizontal-B"] { background-image: var(--tile-horizontal-B, var(--artimg-horizontal-B)) !important; background-size: var(--tile-horizontal-B-sz, 100% 100%) !important; background-position: var(--tile-horizontal-B-ps, 50% 50%) !important; }
.npc-root .npc-shapeico .img[style*="--artimg-horizontal-C"] { background-image: var(--tile-horizontal-C, var(--artimg-horizontal-C)) !important; background-size: var(--tile-horizontal-C-sz, 100% 100%) !important; background-position: var(--tile-horizontal-C-ps, 50% 50%) !important; }
.npc-root .npc-shapeico .img[style*="--artimg-vertical-A"] { background-image: var(--tile-vertical-A, var(--artimg-vertical-A)) !important; background-size: var(--tile-vertical-A-sz, 100% 100%) !important; background-position: var(--tile-vertical-A-ps, 50% 50%) !important; }
.npc-root .npc-shapeico .img[style*="--artimg-vertical-B"] { background-image: var(--tile-vertical-B, var(--artimg-vertical-B)) !important; background-size: var(--tile-vertical-B-sz, 100% 100%) !important; background-position: var(--tile-vertical-B-ps, 50% 50%) !important; }
.npc-root .npc-shapeico .img[style*="--artimg-vertical-C"] { background-image: var(--tile-vertical-C, var(--artimg-vertical-C)) !important; background-size: var(--tile-vertical-C-sz, 100% 100%) !important; background-position: var(--tile-vertical-C-ps, 50% 50%) !important; }
.npc-root .npc-shapeico .img[style*="--artimg-square-A"] { background-image: var(--tile-square-A, var(--artimg-square-A)) !important; background-size: var(--tile-square-A-sz, 100% 100%) !important; background-position: var(--tile-square-A-ps, 50% 50%) !important; }
.npc-root .npc-shapeico .img[style*="--artimg-square-B"] { background-image: var(--tile-square-B, var(--artimg-square-B)) !important; background-size: var(--tile-square-B-sz, 100% 100%) !important; background-position: var(--tile-square-B-ps, 50% 50%) !important; }
.npc-root .npc-shapeico .img[style*="--artimg-square-C"] { background-image: var(--tile-square-C, var(--artimg-square-C)) !important; background-size: var(--tile-square-C-sz, 100% 100%) !important; background-position: var(--tile-square-C-ps, 50% 50%) !important; }
.npc-root .npc-shapeico .img[style*="--artimg-set2-A"] { background-image: var(--tile-set2-A, var(--artimg-set2-A)) !important; background-size: var(--tile-set2-A-sz, 100% 100%) !important; background-position: var(--tile-set2-A-ps, 50% 50%) !important; }
.npc-root .npc-shapeico .img[style*="--artimg-set2-B"] { background-image: var(--tile-set2-B, var(--artimg-set2-B)) !important; background-size: var(--tile-set2-B-sz, 100% 100%) !important; background-position: var(--tile-set2-B-ps, 50% 50%) !important; }
.npc-root .npc-shapeico .img[style*="--artimg-set2-C"] { background-image: var(--tile-set2-C, var(--artimg-set2-C)) !important; background-size: var(--tile-set2-C-sz, 100% 100%) !important; background-position: var(--tile-set2-C-ps, 50% 50%) !important; }
.npc-root .npc-shapeico .img[style*="--artimg-set3-A"] { background-image: var(--tile-set3-A, var(--artimg-set3-A)) !important; background-size: var(--tile-set3-A-sz, 100% 100%) !important; background-position: var(--tile-set3-A-ps, 50% 50%) !important; }
.npc-root .npc-shapeico .img[style*="--artimg-set3-B"] { background-image: var(--tile-set3-B, var(--artimg-set3-B)) !important; background-size: var(--tile-set3-B-sz, 100% 100%) !important; background-position: var(--tile-set3-B-ps, 50% 50%) !important; }
.npc-root .npc-shapeico .img[style*="--artimg-set3-C"] { background-image: var(--tile-set3-C, var(--artimg-set3-C)) !important; background-size: var(--tile-set3-C-sz, 100% 100%) !important; background-position: var(--tile-set3-C-ps, 50% 50%) !important; }

/* price line under the title, both devices (2026-10-05: more air under the title; desktop too) */
.npc-root .npc-pricerow { display: block; margin: 8px 0 2px; font-family: 'DM Sans', sans-serif; font-size: 14px; line-height: 1.35; }
.npc-root .npc-pricerow:empty { display: none; }
.npc-root .npc-pricerow b { font-weight: 500; color: #1a1a1a; }
.npc-root .npc-pricerow span { color: #6b6b6b; font-size: 12.5px; margin-left: 6px; }
@container np (max-width:900px) {
  .npc-root .npc-titlerow { margin-bottom: 14px; }
  .npc-root .npc-titlerow .npc-pricerow, .npc-root .npc-titlerow + .npc-pricerow { flex: 0 0 100%; order: 1; margin: 2px 0 0; }
  .npc-root .npc-titlerow + .npc-pricerow { margin: -8px 0 14px; }
}
/* the same summary directly above the real button — "Your selection: …" (Ollie 2026-10-05) */
.npc-root .npc-atc-sel b { font-weight: 500; color: #1a1a1a; }
/* "Popular" ON the size tile (2026-10-05): top-left, the quiet D2 badge (no fill), tile gets 3px more headroom so it never touches the size */
.npc-root.npc-d2 .opts.sizegrid .sizecard .npc-badge { position: absolute; top: 4px; left: 7px; right: auto; transform: none; padding: 0; background: none; color: #6b6b6b; font-size: 9px; letter-spacing: .06em; line-height: 1; }
.npc-root.npc-d2 .opts.sizegrid .sizecard:has(.npc-badge) { padding-top: 15px !important; }

.npc-root .npc-atc-sel { font-family: 'DM Sans', sans-serif; font-size: 12.5px; line-height: 1.3; color: #6b6b6b; margin: 14px 0 -8px; }
.npc-root .npc-atc-sel:empty { display: none; }
@container np (min-width:901px) { .npc-root .npc-atc-sel { display: none !important; } }   /* phones only (Ollie 2026-10-05) */
/* sticky bar (phones; on <body>): a compact copy of that block — summary left (wraps to 2 lines, never truncates), the same button right */
.npc-stickybar { position: fixed; left: 0; right: 0; bottom: 0; z-index: 40; display: flex; align-items: center; gap: 12px; padding: 8px 16px calc(8px + env(safe-area-inset-bottom)); background: #fff; border-top: 1px solid #e8e6e1; font-family: 'DM Sans', sans-serif; }
.npc-stickybar[hidden] { display: none !important; }
.npc-stickybar .npc-sb-txt { min-width: 0; font-size: 12px; line-height: 1.3; color: #6b6b6b; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.npc-stickybar .npc-sb-btn { margin-left: auto; flex: none; height: 42px; padding: 0 16px; border: 0; border-radius: 6px; background: #1a1a1a; color: #fff; font: 500 14px 'DM Sans', sans-serif; white-space: nowrap; cursor: pointer; touch-action: manipulation; }
.npc-stickybar .npc-sb-btn:disabled { opacity: .55; }
@media (min-width: 901px) { .npc-stickybar { display: none !important; } }

/* SALE (2026-10-14): struck full price beside the sale price — size tiles, price row under the title, size guide */
.npc-was { color: #9e9890; font-weight: 400; text-decoration: line-through; margin-right: 3px; }
.npc-atc-was { color: rgba(255,255,255,.55); font-weight: 400; text-decoration: line-through; margin-right: 6px; }
/* Sale line under Add to cart, coloured + bolder (Ollie 2026-10-14) — chestnut to match the bar, a shade lighter so it reads as colour, not black */
.npc-root .npc-fact--sale { color: #7a3e1d !important; font-weight: 600; font-size: 14px !important; }
.npc-root .npc-fact--sale svg { stroke-width: 1.8; }
/* Sale line option 6 (2026-10-14): soft cream strip + maple leaf + boxed countdown */
.npc-root .np-sline { position: relative; overflow: hidden; align-items: center !important; gap: 8px !important; background: #f7efe6; border-radius: 6px; padding: 8px 10px; font-size: 13px !important; font-weight: 400 !important; color: #7a3e1d !important; }
.npc-root .np-sline b { font-weight: 600; }
.npc-root .np-sline__ic { position: relative; z-index: 1; font-size: 14px; line-height: 1; flex: 0 0 auto; }
.npc-root .np-sline__txt { position: relative; z-index: 1; }
.npc-root .np-sline__chip { display: inline-block; border: 1px solid currentColor; border-radius: 4px; padding: 0 5px; font-size: 12.5px; font-weight: 600; letter-spacing: .02em; font-variant-numeric: tabular-nums; line-height: 1.5; white-space: nowrap; }
@media (max-width: 400px) { .npc-root .np-sline { font-size: 12.5px !important; } .npc-root .np-sline__chip { font-size: 12px; } }   /* one line at 375 incl. Monday's "6h 12m" */
@media (prefers-reduced-motion: reduce) { 