/* Generated by tools/build_product_3d.py from product-3d/product.css.
   Do not edit: edit that file and build again. */
/* product.css - PORCEMALL product page, "The Architect's Catalog" tokens.
   Warm paper ground · hairline ink rules · crimson used structurally. */
.p3d {
  --paper: #fbf9f4;
  --paper-lift: #fefdfa;
  --paper-deep: #f1eee6;
  --ink: #141110;
  --ink-soft: #3d3831;
  --ink-faint: #6b645c;
  --line: rgba(20, 17, 16, 0.32);
  --line-strong: #141110;
  --red: #a02030;
  --font-ui: "Archivo", "Helvetica Neue", sans-serif;
  --font-mono: "Spline Sans Mono", "SFMono-Regular", monospace;
  --gutter: clamp(20px, 3vw, 44px);
  --info-w: clamp(360px, 30vw, 440px);
  /* light only, never darkened by the browser (porcemall.css says why) */
  color-scheme: only light;
}
.p3d *, .p3d *::before, .p3d *::after { box-sizing: border-box; }
.p3d {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-ui);
  font-size: 16px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}
.p3d button { font: inherit; color: inherit; background: none; border: none; padding: 0; cursor: pointer; }
.p3d a { color: inherit; }
/* paper grain - one fixed overlay, almost subliminal */
.p3d .mono {
  font-family: var(--font-mono);
  font-size: 11.5px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}
.p3d .foot-brand { display: inline-flex; align-items: center; gap: 8px; }
.p3d .foot-brand img { width: 16px; height: 16px; display: block; }
/* ---------- hero: stage + info ---------- */
.p3d .hero {
  display: grid;
  grid-template-columns: minmax(0, 1fr) var(--info-w);
  height: 100svh;
  min-height: 560px;
}
.p3d .stage-col {
  position: relative;
  min-width: 0;
  display: flex;
  flex-direction: column;
}
.p3d .stage {
  position: relative;
  flex: 1;
  min-height: 0;
  overflow: hidden;
  /* a faint warm vignette behind the tile: the object floats in a lit room, not a void */
  background:
    radial-gradient(60% 55% at 50% 48%, rgba(255, 253, 248, 0.9) 0%, rgba(251, 249, 244, 0) 70%),
    var(--stage-bg, var(--paper));
  transition: background-color 0.35s ease;
}
/* a chosen backdrop replaces the vignette; dark ones flip the stage type to a light ink */
.p3d .stage[style*="--stage-bg"] { background: var(--stage-bg); }
.p3d .stage.dark-bd .stage-hint, .p3d .stage.dark-bd .stage-status { color: rgba(251, 249, 244, 0.78); }
.p3d .stage.dark-bd .dim-label { color: #fbf9f4; }
.p3d #backdrop-input { position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0; cursor: pointer; margin: 0; padding: 0; border: 0; }
.p3d .viewbar .sw { flex: none; width: 12px; height: 12px; border: 1px solid var(--line-strong); background: var(--paper); }
.p3d .stage canvas.gl {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  display: block;
  cursor: grab;
  opacity: 0;
  transition: opacity 0.9s cubic-bezier(0.16, 1, 0.3, 1);
}
.p3d .stage.ready canvas.gl { opacity: 1; }
.p3d .stage canvas.gl:active { cursor: grabbing; }
/* ---------- room scenes: photographs at 4:5 ----------
   Desktop: the photo runs flush to the stage height, centred, with a slim
   vertical filmstrip of the other scenes attached to its right edge.
   Phone: the photo fills the width. The 3D view fades under it. */
.p3d .stage .scenes {
  position: absolute;
  inset: 0;
  display: flex;
  justify-content: flex-start;
  align-items: stretch;
  gap: 3px;
  opacity: 0;
  transform: scale(1.015);
  transition: opacity 0.5s ease, transform 0.6s cubic-bezier(0.32, 0.72, 0, 1);
  pointer-events: none;
}
.p3d .stage.in-scenes .scenes { opacity: 1; transform: none; pointer-events: auto; }
.p3d .stage.in-scenes canvas.gl, .p3d .stage.in-scenes .stage-hint, .p3d .stage.in-scenes .dim-labels { opacity: 0 !important; pointer-events: none; }
.p3d .stage.in-scenes canvas.gl { transition: opacity 0.45s ease; }
/* the filmstrip sits LEFT of the photo: a quarter of the page (see layoutStrip in index.html); the
   photo takes the rest of the stage, flush against the buy box, cropping the 4:5 photograph as needed */
.p3d .scene-main { position: relative; flex: 1 1 0; min-width: 0; order: 1; height: 100%; background: var(--paper-deep); overflow: hidden; }
.p3d .scene-img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; opacity: 0; transition: opacity 0.55s ease; }
.p3d .scene-img.on { opacity: 1; }
/* the filmstrip: every scene stacked in one column, frames cropped to the column */
.p3d .scene-strip { display: flex; flex: none; order: 0; flex-direction: column; gap: 3px; height: 100%; }
.p3d .strip-thumb { display: block; flex: 1 1 0; min-height: 0; width: 100%; overflow: hidden; background: var(--paper-deep); position: relative; }
.p3d .strip-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; transition: transform 0.5s cubic-bezier(0.16, 1, 0.3, 1); }
.p3d .strip-thumb:hover img { transform: scale(1.03); }
.p3d .strip-thumb.active::after { content: ""; position: absolute; inset: 0; box-shadow: inset 0 0 0 2px var(--ink); pointer-events: none; }
/* thumbnails in the bar (phones only - on desktop the filmstrip does this job and the bar keeps just the square) */
.p3d .viewbar .thumb { padding: 0; position: relative; }
.p3d .viewbar .thumb img { display: block; width: 100%; height: 100%; object-fit: cover; }
.p3d .viewbar .thumb.active::after { content: ""; position: absolute; inset: 0; box-shadow: inset 0 0 0 2px var(--ink); pointer-events: none; }
@media (min-width: 961px) {
.p3d .viewbar .menu-layer[data-menu="scenes"] > button { display: none; }
}
/* status: "LOADING" in the corner, gone when ready. It names no product:
   the catalogue is planks, mouldings, underlayment and coping as well as
   tile, and this one line speaks for all of them. */
.p3d .stage-status {
  position: absolute;
  top: 22px;
  left: var(--gutter);
  z-index: 3;
  display: inline-flex;
  align-items: center;
  gap: 10px;
  color: var(--ink-soft);
  pointer-events: none;
  transition: opacity 0.3s ease;
}
.p3d .stage-status::before {
  content: "";
  width: 7px; height: 7px;
  background: var(--red);
  animation: pulse 1.1s ease-in-out infinite;
}
.p3d .stage.ready .stage-status { opacity: 0; }
.p3d .stage.ready.light-loading .stage-status { opacity: 1; }
/* an interaction that has waited on the network longer than the page is
   willing to sit silent. The same corner and the same words as first load,
   so the stage grows no second indicator; see the stall watch in the page
   module for what arms it and what a second of silence buys. */
.p3d .stage.ready.is-stalled .stage-status { opacity: 1; }
/* a failure has to be legible, and after first load it was not: the line
   was set to the failed words and left at opacity 0 by the rule above. */
.p3d .stage.ready .stage-status.is-error { opacity: 1; }
.p3d .stage-status.is-error { color: var(--red); }
.p3d .stage-status.is-error::before { animation: none; }
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.25; } }
/* asked for less motion, the dot stops pulsing and stays where it is. The
   status is the stage's only word for a wait, so it still arrives and is
   still read: it is the animation that goes, never the indicator. */
@media (prefers-reduced-motion: reduce) {
.p3d .stage-status::before { animation: none; }
}
/* hint: fades after the first drag */
.p3d .stage-hint {
  position: absolute;
  top: 22px;
  right: var(--gutter);
  z-index: 3;
  color: var(--ink-soft);
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.5s ease 0.6s;
}
.p3d .stage.ready .stage-hint { opacity: 1; }
.p3d .stage.ready.touched .stage-hint { opacity: 0; transition-delay: 0s; }
/* dimension lines and labels: projected from the scene into this overlay every frame */
.p3d .dim-labels { position: absolute; inset: 0; pointer-events: none; overflow: hidden; }
.p3d .dim-svg { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
.p3d .dim-svg line { stroke: var(--line-strong); stroke-width: 1; shape-rendering: crispEdges; vector-effect: non-scaling-stroke; }
.p3d .dim-svg.dark line { stroke: #fbf9f4; }
.p3d .dim-label {
  position: absolute;
  transform: translate(-50%, -50%);
  font-family: var(--font-mono);
  font-size: 11.5px;
  letter-spacing: 0.08em;
  color: var(--ink);
  background: var(--stage-bg, var(--paper));
  padding: 1px 6px;
  white-space: nowrap;
  opacity: 0;
  transition: opacity 0.25s ease;
}
.p3d .dim-label.on { opacity: 1; }
/* view controls - bottom centre.
   The bar's own ground is the ink its frame is drawn in, not paper, and
   that is a rendering matter rather than a design one: every pixel inside
   the frame belongs to a button or to the rule between the rows, all of
   which paint their own paper, so the ground is only ever seen in the
   frame itself. It has to be seen there. A 1.5px border reserves a whole
   CSS pixel of layout at a 3x device pixel ratio but paints two device
   rows of the three, and the children begin at the reserved edge, so the
   third row falls to the bar's ground. Measured 2026-09-22 at 390 and 414
   on a phone in the room-scene view: one device row of paper stood
   between the frame and the 2px box on the selected thumbnail, which is
   the gap the owner saw along its top edge, and the same row ran along
   every unselected one. It comes and goes with the bar's sub-pixel
   position - at 360 the bar lands on .891 and the row is painted - so it
   cannot be cured by moving anything. Painting the ground in the frame's
   own ink closes it wherever it appears, on all four sides and between
   the buttons, for the selected cell and its neighbours alike, and
   nothing moves. */
.p3d .viewbar {
  position: absolute;
  left: 50%;
  bottom: 20px;
  transform: translateX(-50%);
  z-index: 3;
  display: flex;
  align-items: center;
  border: 1.5px solid var(--line-strong);
  background: var(--line-strong);
  overflow: hidden;
}
.p3d .viewbar button, .p3d .viewbar .bd-btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  height: 40px;
  padding: 0 14px;
  color: var(--ink);
  cursor: pointer;
  position: relative;
  transition: background 0.15s ease, color 0.15s ease;
  /* every button is opaque on its own, so one can slide over another cleanly */
  border-left: 1.5px solid var(--line-strong);
  white-space: nowrap;
  overflow: hidden;
  background: var(--paper);
}
.p3d .viewbar .bd-btn { user-select: none; -webkit-user-select: none; }
.p3d .viewbar .bd-btn:hover { color: var(--red); }
.p3d .viewbar button svg { width: 16px; height: 16px; display: block; }
.p3d .viewbar button:hover { color: var(--red); }
.p3d .viewbar button.active { background: var(--ink); color: var(--paper); }
.p3d .viewbar button.active:hover { color: var(--paper); }
/* The bar is a set of rows of buttons plus option layers that sit above the
   rows while a menu is open. All motion is scripted (see index.html): CSS
   here only describes the resting states. */
.p3d .viewbar .row { display: flex; align-items: stretch; }
/* rows are not positioned: absolute children answer to the bar */
.p3d .viewbar .rule { display: none; }
.p3d .viewbar .is-hidden { display: none !important; }
.p3d .viewbar .menu-layer {
  position: absolute;
  top: 0;
  left: 0;
  display: none;
  align-items: stretch;
  background: var(--paper);
  visibility: hidden;
}
.p3d .viewbar .menu-layer.open { display: flex; visibility: visible; }
/* the hairline that rides the wipe front during a menu's motion */
.p3d .viewbar .edge-rider { position: absolute; display: none; width: 1.5px; background: var(--line-strong); z-index: 2; pointer-events: none; }
/* the very first button of the bar meets its outer edge without a rule */
.p3d .viewbar .row-a > :first-child { border-left-width: 0; }
.p3d .viewbar .menu-layer > button { border-left: 1.5px solid var(--line-strong); }
.p3d .viewbar .menu-btn .when-closed, .p3d .viewbar .menu-btn .when-open { white-space: nowrap; }
.p3d .viewbar .menu-btn .lbl { font-size: 11px; }
.p3d .viewbar .menu-btn .val { font-weight: 700; }
.p3d .viewbar .menu-btn { position: relative; }
.p3d .viewbar .menu-btn .when-open { display: none; }
.p3d .viewbar .menu-btn.open .when-closed { display: none; }
.p3d .viewbar .menu-btn.open .when-open { display: inline; }
.p3d .viewbar .menu-btn .when-closed[style*="absolute"], .p3d .viewbar .menu-btn .when-open[style*="absolute"] { left: 14px; top: 50%; transform: translateY(-50%); }
.p3d .viewbar .menu-btn.open { color: var(--ink-soft); }
.p3d .viewbar .menu-btn.open:hover { color: var(--red); }
.p3d .viewbar .lbl { font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.12em; white-space: nowrap; }
.p3d .viewbar .l-short { display: none; }
/* ---------- info column ---------- */
.p3d .info-col {
  border-left: 1.5px solid var(--line-strong);
  padding: clamp(18px, 3vh, 40px) var(--gutter) clamp(18px, 3vh, 32px) clamp(24px, 2.4vw, 40px);
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-height: 0;
  overflow-y: auto; /* safety net on very short windows: the column scrolls, the page never grows */
}
.p3d .info-col > * { min-width: 0; }
/* The three tags read as one line. They were three islands: measured
   2026-09-22 on /en/product/eagle-ash/ at 320, 390 and 1440, the ink
   stood 20.0px to 21.0px either side of each dot against a 8.0px space
   between the words inside a tag, so each dot sat in a gap two and a
   half words wide. Three things make that gap and only two are ours: the
   dot's own side bearings are 9.84px of it and cannot be taken back
   without moving the glyph off its advance, the tracking laid another
   2.16px after the last letter, and the dot carried 8px of padding on
   each side. The padding does most of the work, 8px to 2px, and the
   tracking comes down a notch to 0.12em, the figure the mono type on
   this page already tracks at, which is still plainly tracked capitals
   and takes the space inside a tag to 6.33px. The gap either side of a
   dot is then 13.33px to 14.0px, about two spaces rather than five.
   The line also narrows 42.7px at 390, and that is what brings the
   Spanish on to one line: "Vinilo SPC / Look madera / Natural" wrapped
   at 390 and no longer does. The longest Spanish kicker in the
   catalogue, litos-artico's "Porcelanico / Look swimming pool /
   Rectificado", still wraps on a phone, as does the English at 320. */
.p3d .kicker {
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--red);
  margin: 0 0 12px;
}
.p3d .kicker .dot { color: var(--ink-soft); padding-inline: 2px; }
.p3d h1.title {
  font-size: clamp(30px, 2.6vw, 42px);
  font-weight: 800;
  font-stretch: 112%;
  line-height: 0.98;
  text-transform: uppercase;
  letter-spacing: -0.005em;
  margin: 0 0 10px;
}
.p3d h1.title em { font-style: normal; color: var(--red); display: inline-block; /* inline-block so the .settle transform (setText) can move it */ }
/* The choice groups sit on an even rhythm with a hairline in the middle of
   the gap between them. The rhythm is the one this sheet always had, 22px
   from the foot of one group's box to the head of the next label's, and the
   rule is cut out of that 22px rather than added to it, so the column stands
   exactly as tall as it did. Ten pixels of margin, the rule, eleven of
   padding. The odd pixel goes under the rule because the type either side of
   it is not symmetrical: measured 2026-09-22 at 390 and at 1440, the group
   above ends in small capitals or a mono label whose ink stops 5.6px to
   6.2px short of its box, while the label below starts 4.4px to 5.3px inside
   its own, so an even split would have hung the rule about a pixel high.
   With 10 and 11 the ink stands 15.4px to 16.3px either side at every width
   measured, within half a pixel of centre.
   The rule is the page's own weight, 1px of --line, the one the facts grid
   and the price block draw. It is not drawn over the first group, which
   follows the title and belongs to it. The near-identical hairline that
   porcemall.css puts on every .field is still held off in the adapter: that
   one is the order calculator's form row, it lands 4px above the label, and
   it is what the owner saw as wrong before it was removed. */
.p3d .field { padding: 0; }
.p3d .field + .field { margin-top: 10px; border-top: 1px solid var(--line); padding-top: 11px; }
/* the one group that does not end in type. The Finish control is a bordered
   segment bar, so the ink over the rule is its own 1.5px edge and stops 1.0px
   short of the box instead of the 5.6px to 6.2px a line of small capitals or a
   mono label leaves. With the figures above, the rule under Finish stood 11.0px
   over against 15.0px under; 12 and 9 put it within 0.2px of centre and the
   group keeps the same 22px. */
.p3d .field:has(> .fin-seg) + .field { margin-top: 12px; padding-top: 9px; }
.p3d h1.title + .field { margin-top: 18px; }
.p3d .field-label {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  margin-bottom: 8px;
}
/* sizes - proportional plates: each button is the tile's true relative shape */
/* sizes share the same three-column rhythm as the colour swatches: one column each,
   plate centred, label centred beneath, plates bottom-aligned so the smaller formats sit on the same baseline */
.p3d .sizes { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; align-items: end; }
.p3d .size-opt {
  display: flex;
  flex-direction: column;
  gap: 6px;
  align-items: center;
  text-align: center;
}
.p3d .size-opt .plate {
  display: block;
  height: 44px;
  border: 1.5px solid var(--ink);
  background: var(--paper-deep);
  transition: background 0.15s ease, outline-color 0.15s ease;
}
.p3d .size-opt[data-shape="4848"] .plate { width: 44px; }
.p3d .size-opt[data-shape="4040"] .plate { width: 37px; height: 37px; }
.p3d .size-opt[data-shape="2448"] .plate { width: 22px; }
.p3d .size-opt .lbl { font-family: var(--font-mono); font-size: 11.5px; letter-spacing: 0.04em; color: var(--ink-soft); white-space: nowrap; }
.p3d .size-opt:hover .plate { background: var(--paper-lift); }
.p3d .size-opt:hover .lbl { color: var(--red); }
.p3d .size-opt.active .plate { background: var(--ink); outline: 2px solid var(--ink); outline-offset: 2px; }
.p3d .size-opt.active .lbl { color: var(--ink); font-weight: 600; }
.p3d .size-opt:focus-visible { outline: none; }
.p3d .size-opt:focus-visible .plate { outline: 2px solid var(--red); outline-offset: 2px; }
/* colour swatches: the chip is the face scan itself, the label sits under it */
.p3d .swatches { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }
.p3d .swatch { text-align: center; }
.p3d .swatch .chip {
  display: block;
  height: 46px;
  background-size: 260% auto;
  background-position: center;
  border: 1.5px solid var(--ink);
  margin-bottom: 7px;
  outline: 2px solid transparent;
  outline-offset: 0;
  transition:
    transform 0.45s cubic-bezier(0.16, 1, 0.3, 1),
    outline-color 0.4s ease,
    outline-offset 0.45s cubic-bezier(0.16, 1, 0.3, 1),
    box-shadow 0.3s ease;
}
.p3d .swatch .swatch-label {
  display: block;
  font-size: 12.5px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  transition: color 0.4s ease;
}
/* selecting a colour: the frame draws out from the chip while the chip lifts and settles */
.p3d .swatch.active .chip { outline-color: var(--ink); outline-offset: 3px; animation: chip-settle 0.6s cubic-bezier(0.16, 1, 0.3, 1); }
@keyframes chip-settle {
  0% { transform: translateY(0); }
  35% { transform: translateY(-3px); }
  100% { transform: translateY(0); }
}
.p3d .swatch.active .swatch-label { color: var(--red); }
.p3d .swatch:not(.active):hover .chip { transform: translateY(-2px); box-shadow: 0 10px 22px -8px rgba(46, 32, 14, 0.35); }
.p3d .swatch:not(.active):hover .swatch-label { color: var(--red); }
.p3d .swatch:focus-visible { outline: none; }
.p3d .swatch:focus-visible .chip { outline: 2px solid var(--red); outline-offset: 3px; }
/* text that changes settles in from a hair below */
.p3d .settle { animation: settle 0.45s cubic-bezier(0.16, 1, 0.3, 1); }
@keyframes settle {
  from { opacity: 0; transform: translateY(5px); }
  to { opacity: 1; transform: none; }
}
/* the finish toggle spans the column like the grids above it, two equal halves */
.p3d .fin-seg { display: grid; grid-template-columns: 1fr 1fr; border: 1.5px solid var(--line-strong); }
.p3d .fin-seg button {
  text-align: center;
  padding: 8px 18px;
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  transition: background 0.15s ease, color 0.15s ease;
}
.p3d .fin-seg button + button { border-left: 1.5px solid var(--line-strong); }
.p3d .fin-seg button.active { background: var(--ink); color: var(--paper); }
.p3d .fin-seg button:not(.active):hover { color: var(--red); }
/* prices */
.p3d .prices { margin-top: 22px; border-top: 1px solid var(--line); padding-top: 14px; display: grid; gap: 6px; }
.p3d .price-row { display: grid; grid-template-columns: 1fr auto; gap: 2px 16px; align-items: baseline; }
.p3d .price-row .l { font-size: 15px; }
.p3d .price-row .sub { grid-column: 1; font-family: var(--font-mono); font-size: 12px; color: var(--ink-soft); letter-spacing: 0.03em; }
.p3d .price-row .p { font-family: var(--font-mono); font-size: 16px; font-weight: 600; letter-spacing: 0.02em; }
.p3d .link-btn {
  font: inherit;
  color: var(--ink);
  border-bottom: 1px solid var(--ink);
  letter-spacing: 0.04em;
  transition: color 0.15s ease, border-color 0.15s ease;
}
.p3d .link-btn:hover { color: var(--red); border-color: var(--red); }
/* while the sample is cut, the size plates step back */
.p3d .sizes.is-sample .size-opt { opacity: 0.45; }
.p3d .sizes.is-sample .size-opt.active .plate { background: var(--paper-deep); outline-color: var(--line); }
.p3d .btn {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-top: 14px;
  padding: 13px 18px;
  font-size: 13px;
  font-weight: 800;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  text-decoration: none;
  background: var(--red);
  color: var(--paper);
  border: 2px solid var(--red);
  transition: background 0.18s ease, border-color 0.18s ease;
}
.p3d .btn:hover { background: var(--ink); border-color: var(--ink); }
.p3d .btn svg { width: 20px; height: 20px; transition: transform 0.18s ease; }
.p3d .btn:hover svg { transform: translateX(4px); }
.p3d .link-row { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-top: 8px; }
.p3d .link-row a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 9px 10px;
  border: 1.5px solid var(--ink);
  font-size: 11.5px;
  font-weight: 800;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  text-decoration: none;
  transition: background 0.18s ease, color 0.18s ease;
}
.p3d .link-row a:hover { background: var(--ink); color: var(--paper); }
.p3d .code-line {
  margin-top: 12px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.08em;
  color: var(--ink-soft);
}
.p3d .code-line b { color: var(--ink); font-weight: 600; }
.p3d .code-line button { border-bottom: 1px solid var(--ink); color: var(--ink); font-size: 11px; letter-spacing: 0.12em; text-transform: uppercase; }
.p3d .code-line button:hover { color: var(--red); border-color: var(--red); }
/* the distributor's buy box (default): order code, quick facts, room viewer, documents.
   The shop's (prices, sample, dealer, code line above) replaces it with ?commerce. */
.p3d .order-code { display: flex; align-items: center; gap: 12px; font-family: var(--font-mono); }
.p3d .order-code .code { font-size: 17px; font-weight: 600; letter-spacing: 0.06em; }
.p3d .order-code .copy-btn { font-size: 12px; letter-spacing: 0.12em; text-transform: uppercase; border-bottom: 1px solid var(--ink); padding-bottom: 1px; transition: color 0.15s ease, border-color 0.15s ease; }
.p3d .order-code .copy-btn:hover { color: var(--red); border-color: var(--red); }
/* the column narrows with the window (--info-w): fact text scales with it so "Water absorb." keeps to its cell */
.p3d .facts { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); margin-top: 16px; border-block: 1px solid var(--line); container-type: inline-size; }
/* The foot is 7px and not the 10px it was. The 11px key label's line box
   hangs 5.05px of descent below the label's own ink, so a 10px foot stood
   the divider 15.05px under the label and 12.00px over the value in the
   cell below it. 7px reads the same from either side, within 0.6px at
   every width, which is how the specification table further down already
   sets its own rules. Measured 2026-09-22 from 320 to 1920. */
.p3d .facts > div { padding: 8px 4px 7px; text-align: center; }
.p3d .facts > div + div { border-left: 1px solid var(--line); }
.p3d .facts > div:nth-child(n + 4) { border-top: 1px solid var(--line); }
.p3d .facts > div:nth-child(4) { border-left: none; }
.p3d .facts .v { display: block; font-family: var(--font-mono); font-size: clamp(13.5px, 4.4cqi, 15.5px); font-weight: 600; line-height: 1.5; white-space: nowrap; }
.p3d .facts .k { display: block; margin-top: 2px; font-size: clamp(9px, 3cqi, 11px); font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase; white-space: nowrap; color: var(--ink-soft); }
.p3d .flag { display: inline-block; width: 1.36em; height: 0.9em; margin-right: 0.5em; vertical-align: -0.1em; }
.p3d .btn-room { position: relative; margin-top: 20px; }
.p3d .btn-room svg { width: 22px; height: 22px; }
.p3d .btn-badge { position: absolute; top: 0; right: 16px; transform: translateY(-55%); padding: 4px 10px; background: var(--ink); color: var(--paper); font-size: 10.5px; font-weight: 800; letter-spacing: 0.14em; line-height: 1.55; pointer-events: none; transition: background 0.18s ease; }
.p3d .btn-room:hover .btn-badge { background: var(--red); }
.p3d .link-row.docs { grid-template-columns: 1fr 1fr auto; }
.p3d .link-row.docs a { gap: 8px; line-height: 1.3; text-align: center; }
.p3d .link-row .tag { font-family: var(--font-mono); font-size: 10.5px; letter-spacing: 0.1em; padding: 2px 6px; background: var(--ink); color: var(--paper-lift); }
.p3d .link-row a:hover .tag { background: var(--paper); color: var(--ink); }
.p3d .fav-btn { display: flex; align-items: center; justify-content: center; padding: 0 14px; border: 1.5px solid var(--ink); transition: background 0.18s ease, color 0.18s ease, border-color 0.18s ease; }
.p3d .fav-btn svg { width: 18px; height: 18px; display: block; }
.p3d .fav-btn:hover { background: var(--ink); color: var(--paper); }
.p3d .fav-btn.on { color: var(--red); border-color: var(--red); }
.p3d .fav-btn.on:hover { background: var(--red); color: var(--paper); }
.p3d .fav-btn.on svg { fill: currentColor; }
/* staggered reveal on load */
.p3d .info-col > * { opacity: 0; transform: translateY(12px); animation: rise 0.7s cubic-bezier(0.16, 1, 0.3, 1) forwards; }
.p3d .info-col > *:nth-child(1) { animation-delay: 0.05s; }
.p3d .info-col > *:nth-child(2) { animation-delay: 0.12s; }
.p3d .info-col > *:nth-child(3) { animation-delay: 0.19s; }
.p3d .info-col > *:nth-child(4) { animation-delay: 0.26s; }
.p3d .info-col > *:nth-child(5) { animation-delay: 0.33s; }
.p3d .info-col > *:nth-child(6) { animation-delay: 0.40s; }
.p3d .info-col > *:nth-child(7) { animation-delay: 0.47s; }
.p3d .info-col > *:nth-child(n + 8) { animation-delay: 0.54s; }
@keyframes rise { to { opacity: 1; transform: none; } }
/* ---------- details below the fold ---------- */
.p3d .details {
  border-top: 1.5px solid var(--line-strong);
  padding: clamp(36px, 5vw, 64px) var(--gutter) clamp(48px, 6vw, 88px);
  display: grid;
  grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr);
  gap: clamp(32px, 5vw, 80px);
}
.p3d .details h2 {
  margin: 0 0 14px;
  font-size: 12.5px;
  font-weight: 800;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--red);
  border-bottom: 2px solid var(--line-strong);
  padding-bottom: 10px;
}
.p3d .details p { margin: 0 0 14px; font-size: 15.5px; line-height: 1.65; color: var(--ink-soft); max-width: 58ch; }
.p3d .spec-table { width: 100%; border-collapse: collapse; }
.p3d .spec-table td { padding: 10px 0; border-bottom: 1px solid var(--line); vertical-align: top; font-size: 14.5px; }
.p3d .spec-table td.k { color: var(--ink-soft); width: 42%; padding-right: 16px; }
.p3d .spec-table td.v { font-family: var(--font-mono); font-size: 13.5px; letter-spacing: 0.01em; }
/* about: the colour's short description as the opening statement, then the claims as a
   two-up sheet on the same hairlines as the spec table - icon, value tag, claim, one line of proof.
   The buy box facts link down to their cells. */
.p3d .details .lede {
  margin: 22px 0 0;
  max-width: 34ch;
  font-size: clamp(22px, 1.8vw, 28px);
  font-weight: 500;
  line-height: 1.28;
  letter-spacing: -0.01em;
  color: var(--ink);
  text-wrap: balance;
}
.p3d .claims {
  list-style: none;
  margin: clamp(30px, 3.2vw, 44px) 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  border-top: 1px solid var(--line);
}
.p3d .claim {
  display: grid;
  grid-template-columns: 44px minmax(0, 1fr);
  grid-template-rows: auto auto 1fr;
  column-gap: 16px;
  padding: 18px 28px 20px 0;
  border-bottom: 1px solid var(--line);
  scroll-margin-top: 16px;
}
.p3d .claim:nth-child(even) { padding: 18px 0 20px 28px; border-left: 1px solid var(--line); }
.p3d .claim img { grid-row: 1 / span 3; width: 44px; height: 44px; margin-top: 2px; }
.p3d .claim .tag { font-family: var(--font-mono); font-size: 11px; font-weight: 600; letter-spacing: 0.1em; text-transform: uppercase; line-height: 1.4; color: var(--red); }
.p3d .claim h3 { margin: 3px 0 4px; font-size: 16.5px; font-weight: 700; line-height: 1.25; letter-spacing: -0.005em; }
.p3d .details .claim p { margin: 0; font-size: 14.5px; line-height: 1.55; text-wrap: pretty; }
/* technologies: how it is made, above the spec table - two-up on the same hairlines, the
   divider meeting the heading rule; words only, so it reads apart from the claims */
.p3d .techs { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); margin: -14px 0 clamp(36px, 3.6vw, 52px); }
.p3d .tech { padding: 18px 24px 20px 0; border-bottom: 1px solid var(--line); }
.p3d .tech:nth-child(even) { padding: 18px 0 20px 24px; border-left: 1px solid var(--line); }
.p3d .tech h3 { margin: 0 0 6px; font-size: 13px; font-weight: 800; letter-spacing: 0.1em; line-height: 1.35; text-transform: uppercase; }
.p3d .details .tech p { margin: 0; font-size: 14.5px; line-height: 1.55; text-wrap: pretty; }
.p3d .facts a.fact-link { display: block; color: inherit; text-decoration: none; }
.p3d .fact-link:hover .v, .p3d .fact-link:hover .k { color: var(--red); }
/* small laptops: the column is narrow, so the icon sits above the words and the sheet stays two-up */
@media (min-width: 961px) and (max-width: 1180px) {
.p3d .claim { grid-template-columns: minmax(0, 1fr); grid-template-rows: auto auto auto 1fr; padding-right: 20px; }
.p3d .claim:nth-child(even) { padding-left: 20px; }
.p3d .claim img { grid-row: auto; margin: 0 0 10px; }
}
/* phones: one claim per row */
@media (max-width: 620px) {
.p3d .claims, .p3d .techs { grid-template-columns: 1fr; }
.p3d .claim, .p3d .claim:nth-child(even) { padding: 18px 0 20px; border-left: none; }
.p3d .tech, .p3d .tech:nth-child(even) { padding: 16px 0 18px; border-left: none; }
/* fact labels: a readable floor on the narrowest phones, a touch less tracking so they still fit */
.p3d .facts .k { font-size: clamp(10px, 3.3cqi, 11px); letter-spacing: 0.06em; }
/* spec values get the width, so a list breaks after two items rather than one */
.p3d .spec-table td.k { width: 36%; }
}
/* small controls get a finger-sized hit area without changing how they look */
.p3d .order-code .copy-btn, .p3d .facts a.fact-link { position: relative; }
.p3d .order-code .copy-btn::after { content: ""; position: absolute; inset: -12px -10px; }
.p3d .facts a.fact-link::after { content: ""; position: absolute; inset: -8px -4px -10px; }
.p3d .details h2 { scroll-margin-top: 16px; }
@media (max-width: 960px) {
.p3d .fin-seg button { display: flex; align-items: center; justify-content: center; min-height: 44px; }
}
/* short desktop windows (13"/14" laptops): one more step of compaction */
@media (min-width: 961px) and (max-height: 840px) {
.p3d h1.title { font-size: 30px; margin-bottom: 8px; }
.p3d .kicker { margin-bottom: 8px; font-size: 11px; }
.p3d .swatch .chip { height: 38px; margin-bottom: 5px; }
.p3d .swatch .swatch-label { font-size: 11.5px; }
.p3d .size-opt .plate { height: 38px; }
.p3d .size-opt[data-shape="4848"] .plate { width: 38px; }
.p3d .size-opt[data-shape="4040"] .plate { width: 32px; height: 32px; }
.p3d .size-opt[data-shape="2448"] .plate { width: 19px; }
.p3d .field + .field { margin-top: 7px; padding-top: 8px; }
.p3d .field:has(> .fin-seg) + .field { margin-top: 9px; padding-top: 6px; }
.p3d h1.title + .field { margin-top: 14px; }
.p3d .prices { margin-top: 16px; padding-top: 11px; }
.p3d .fin-seg button { padding: 6px 15px; }
.p3d .price-row .l { font-size: 14px; }
.p3d .price-row .sub { font-size: 11px; }
.p3d .btn { margin-top: 10px; padding: 11px 16px; font-size: 12px; }
.p3d .link-row a { padding: 8px 10px; }
.p3d .code-line { margin-top: 10px; }
.p3d .order-code .code { font-size: 15.5px; }
.p3d .facts { margin-top: 12px; }
/* the same 7px foot as the full-height band, for the same reason */
.p3d .facts > div { padding: 6px 4px 7px; }
.p3d .btn-room { margin-top: 18px; }
}
/* 720-tall windows: the last few pixels */
@media (min-width: 961px) and (max-height: 760px) {
.p3d .info-col { padding-top: 14px; padding-bottom: 14px; }
.p3d .field + .field { margin-top: 5px; padding-top: 6px; }
.p3d .field:has(> .fin-seg) + .field { margin-top: 7px; padding-top: 4px; }
.p3d h1.title + .field { margin-top: 10px; }
.p3d .prices { margin-top: 12px; padding-top: 9px; gap: 4px; }
.p3d .btn { margin-top: 8px; padding: 10px 16px; }
.p3d .link-row { margin-top: 6px; }
.p3d .code-line { margin-top: 8px; }
.p3d .swatch .chip { height: 34px; }
.p3d .facts { margin-top: 10px; }
.p3d .facts > div { padding: 3px 4px 4px; }
.p3d .btn-room { margin-top: 16px; }
}
/* ---------- responsive ---------- */
@media (max-width: 960px) {
.p3d { --gutter: 18px; }
.p3d .hero { grid-template-columns: 1fr; height: auto; min-height: 0; }
/* the tile gets most of the first screen; the info column peeks below so it is clear there is more */
.p3d .stage { flex: none; }
/* the tile owns every gesture on it, vertical included; the page scrolls from the controls and the column below */
.p3d .stage canvas.gl { touch-action: none !important; }
.p3d .stage-status { top: 14px; }
.p3d .stage-hint { display: none; }
.p3d .info-col { border-left: none; border-top: 1.5px solid var(--line-strong); padding: 26px var(--gutter) 34px; }
.p3d h1.title { font-size: 34px; }
/* controls: below the tile, not over it - two full-width rows, short labels */
.p3d .stage { height: 64svh; min-height: 340px; }
.p3d .viewbar {
    position: relative; /* in flow below the stage; the option layers position against it */
    left: auto;
    bottom: auto;
    transform: none;
    display: flex;
    flex-direction: column;
    align-items: stretch;
    margin: 14px var(--gutter) 18px;
    max-width: none;
  }
/* the hairline between the rows is its own element so it can fade by scale (sub-pixel smooth) with the fold */
.p3d .viewbar .rule { display: block; height: 1.5px; background: var(--line-strong); transform-origin: 50% 50%; flex: none; }
/* sizing: the two layout icons are a fixed, modest width; every other button
     in a row shares the remaining width equally. A menu button that is open
     shrinks to its label so its options get the room. */
.p3d .viewbar button, .p3d .viewbar .bd-btn { height: 44px; padding: 0 6px; flex: 1 1 0; justify-content: center; min-width: 0; }
.p3d .viewbar [data-layout] { flex: 0 0 56px; }
.p3d .viewbar .row > :first-child { border-left-width: 0; }
.p3d .viewbar .menu-btn { flex: 1 1 0; padding: 0 8px; }
.p3d .viewbar .menu-btn.open { flex: 0 0 auto; padding: 0 14px; }
.p3d .viewbar .menu-layer > button { flex: 1 1 0; padding: 0 3px; }
/* full labels in the rows (they fit); short ones only inside the option layers */
.p3d .viewbar .row .l-long { display: inline; }
.p3d .viewbar .row .l-short { display: none; }
.p3d .viewbar .menu-layer .l-long { display: none; }
.p3d .viewbar .menu-layer .l-short { display: inline; }
.p3d .viewbar .lbl { font-size: 10.5px; letter-spacing: 0.08em; }
.p3d .stage { transition: height 0.5s cubic-bezier(0.32, 0.72, 0, 1); }
.p3d .stage.in-scenes { height: calc(100vw * 1.25); }
.p3d .stage .scenes { gap: 0; }
.p3d .scene-main { height: auto; width: 100%; aspect-ratio: 4 / 5; }
.p3d .scene-strip { display: none; }
.p3d .viewbar .menu-layer .thumb { flex: 1 1 0; padding: 0; height: 44px; }
.p3d .dim-label { font-size: 10.5px; }
.p3d .swatches { gap: 8px; }
.p3d .price-row .l { font-size: 14.5px; }
.p3d .details { grid-template-columns: 1fr; padding-top: 30px; }
}
@media (prefers-reduced-motion: reduce) {
.p3d .info-col > * { animation: none; opacity: 1; transform: none; }
.p3d .stage canvas.gl { transition: none; }
}
/* phones held sideways: the tile owns every touch on it, so it must never fill the screen -
   the bar stays in sight and there is page to scroll on */
@media (max-width: 960px) and (max-height: 520px) {
.p3d .stage, .p3d .stage.in-scenes { height: calc(100svh - 76px); min-height: 200px; }
/* room scenes keep their 4:5 photo, as tall as the stage and centred */
.p3d .scene-main { flex: 0 0 auto; width: auto; height: 100%; margin-inline: auto; }
}

/* ---- what changes when the finished page's hero lives inside a site page
   rather than on a page of its own. Layout, sections and order are the
   page's own; these rules only give it back the height the site chrome
   takes and let the two lists grow past three items. ---- */
.p3d { display: block; }
.p3d { --p3d-chrome: 112px; }
.p3d .hero { height: calc(100svh - var(--p3d-chrome)); min-height: 520px;
  grid-template-rows: minmax(0, 1fr); }
.p3d .info-col { justify-content: safe center; }
/* The media column's own padding, gap and floor, held off. porcemall.css
   names .stage-col too: there it is the picture stage of the old product
   page's two-column hero, and it carries clamp(24px, 3vw, 44px) of padding
   on three sides, a 22px gap between the picture and the toggle under it
   (14px below 721px) and a 720px floor. The 3D page's column has none of
   that in product.css, because the stage IS the page's top edge and the
   room photograph runs to it. Measured 2026-09-22 on
   /en/product/eagle-ash/ at 390: a 24px band of paper stood between the
   masthead's rule and the photograph, which is the gap the owner saw, and
   another 14px was added under the stage on top of the view bar's own
   14px margin. At 1440 the same padding took 43.19px off the stage's top,
   its right and its foot. The 720px floor is not cosmetic: above 1080px
   porcemall.css does not override it, so a desktop window short enough to
   make the hero shorter than 720px would have the stage row overflow the
   hero it is laid in. Reset here rather than in product.css for the
   reason the .field reset is here: where that sheet is loaded alone there
   is nothing to reset. */
.p3d .stage-col { padding: 0; gap: 0; min-height: 0; }
/* The hairline porcemall.css draws on every .field, held off the choice
   groups. There it is the order calculator's form row: a 1px rule with a
   14px/16px padding. product.css takes the padding back and never took
   back a border its own sheet does not set, so COLOR, SIZE, FINISH and
   ORDER CODE each drew a line placed for a form. Measured 2026-09-22 from
   320 to 1920 in both languages: it sat 4.0px above the capitals of the
   label under it against 21.8px to 37.6px below the content over it, an
   asymmetry of up to 33.59px, where every rule the page draws for itself
   sits within 3px of centre; in Spanish it passed 1.0px under the accent
   on CODIGO. The reset belongs in this adapter and not in product.css,
   because the leak only exists where porcemall.css is loaded: in the
   prototype the same line would be a no-op that reads as a fix for
   something that sheet never does. It wins on weight and on order, (0,2,0)
   against the site's (0,1,0) with this file loaded second, and .p3d holds
   it off the calculator's own rows on the how-much-to-order guide, which
   keeps its hairlines.
   Taking it off all four groups left COLOR, SIZE and ORDER CODE reading as
   one undifferentiated run, which the owner said looked weird, so
   product.css now draws a rule of its own between the groups and centres
   it in the gap. That rule is .field + .field, (0,3,0) once scoped, so it
   outranks this line and this line still governs the first group, the one
   under the title, which wants no rule over it. */
.p3d .field { border-top: 0; }
@media (max-width: 960px) { .p3d .hero { height: auto; grid-template-rows: none; } }
/* the Finish control holds a button per surface the PIM states, where the
   finished page was drawn for two */
.p3d .fin-seg { grid-template-columns: none; grid-auto-flow: column; grid-auto-columns: 1fr; }
/* The document, the full specifications and the save button read as one
   group of three: the save button is tied to the full specifications in
   its own box, so the row can never leave it orphaned on a line of its
   own. The row carries ONE document button - the family's brochure, or
   its tech sheet where the PIM files no brochure - and the three hold a
   single line at every width, measured 2026-09-22 at 320/360/390/414 and
   at 1100/1280/1440, in both languages and on the pages that show no
   document at all. Beside the 3D stage the row is an info column and not
   a page width: 353px at 1440, 300px at 1100, 284px to 378px on a phone,
   and three items are what fits in that. A label too long for its button
   (the Spanish "Ficha técnica") sets on two lines inside it, and every
   item on the line takes that height. */
.p3d .link-row.docs { display: flex; flex-direction: row; flex-wrap: wrap; align-items: stretch; }
.p3d .link-row.docs > a { flex: 1 1 96px; min-width: 0; }
.p3d .link-row.docs .docs-end { display: flex; gap: 10px; flex: 1 1 150px; min-width: 0; }
.p3d .link-row.docs .docs-end > a { flex: 1 1 auto; min-width: 0; }
.p3d .link-row.docs .fav-btn { flex: 0 0 auto; }
/* Phones. The row has a third of the width it was drawn for, and the site
   stylesheet stacks every .link-row below 720 (porcemall.css, the buy box's
   two-link row) - which took this one apart, the sheet on one line and the
   full specifications with the save button on the next. It states its own
   direction above so no site rule can turn it again, and here the labels
   give back the few pixels they need to set inside their buttons. */
@media (max-width: 760px) {
/* the basis a document button would take if the row ever held more than
   one: two to a line, so a label as long as "Care & maintenance guide"
   keeps the width to set in. The row shows one document today, and the rule
   below outranks this one for it; this is the floor under any future row. */
.p3d .link-row.docs > a { flex-basis: calc(50% - 7px); }
/* the row as it is built - one document button and the pair - holds the
   button beside the full specifications and the save button, and never takes
   a line of its own */
.p3d .link-row.docs > a:first-child:nth-last-child(2) { flex-basis: 96px; }
}
/* the narrowest phones: a touch less type and tracking, and the three still fit */
@media (max-width: 420px) {
.p3d .link-row.docs a { font-size: clamp(10px, 3vw, 11.5px); letter-spacing: 0.06em; padding-inline: 8px; }
}
/* the facts grid is six cells, but a family that cannot fill six shows
   fewer. The finished page unsets the left border on its fourth cell by
   hand; every cell that starts a row needs it. */
.p3d .facts > div:nth-child(3n + 1) { border-left: none; }
/* A PIM value is as long as the catalogue writes it: one family's origin is
   "Dominican Republic" and another's size "23.62 x 47.24 in". The finished
   page held every value on a single line, so a long one ran straight over
   the cell beside it. A value that does not fit now wraps inside its own
   cell, and every cell holds its value at the top and its label at the
   foot, so the labels still read across as one row. */
.p3d .facts > div { display: flex; flex-direction: column; justify-content: space-between; min-width: 0; }
.p3d .facts a.fact-link { display: flex; flex: 1 1 auto; flex-direction: column; justify-content: space-between; }
.p3d .facts .v { white-space: normal; overflow-wrap: break-word; text-wrap: balance; }
.p3d .facts .k { white-space: normal; }
/* the scene column: at most five photographs of the colourway the page is
   on, and sometimes two. Every frame keeps a fifth of the stage whatever
   the number, so a short column is short and centred against the
   photograph rather than one frame stretched down the whole height. A
   colourway with a single photograph has no column, because the only frame
   in it would be the photograph already on show. */
.p3d .scene-strip { justify-content: center; }
.p3d .scene-strip[hidden] { display: none; }
.p3d .strip-thumb { flex: 0 1 calc((100% - 12px) / 5); }
/* the sale or clearance badge, in the stage's top right. That is where
   the eye lands after the title, so the one commercial word on the page
   is read before the tile is turned. The corner was the drag hint's, and
   the hint moves down to the line under the loading status: the status
   is gone by the time the hint fades in, the two never share a line, and
   the hint's own corner is then free for the badge. The other two are
   taken as they were, the status at the top left and the view bar
   centred on the bottom edge. A solid red block with white type carries
   on the pale backdrop and on the dark one alike, which is why it needs
   no rule or shadow of its own. The tile is centred and the badge is
   outside it at every window, so a change of size, colour or surface
   leaves it exactly where it was. */
.p3d .stage-deal { position: absolute; right: var(--gutter); top: 22px;
  left: auto; bottom: auto; z-index: 3;
  display: flex; flex-direction: column; align-items: flex-end; gap: 4px;
  pointer-events: none; transition: opacity 0.3s ease; }
.p3d .stage-deal[hidden] { display: none; }
/* the word inside the block, and what it takes to stand it in the middle
   of one. Two things push it off centre and neither shows in the box
   arithmetic. The first is the face: the badge is set in capitals, so
   its ink is the cap band and nothing else, and Spline Sans Mono hangs
   more under the baseline than its capitals need over the cap line - at
   11.5px a line box of 1 puts the baseline 9px down and keeps 2.5px
   under it against 0.64px over the cap. The second is that a box is
   painted on whole pixels: the block stood 20.5px tall, which rounds to
   20 on one page and 21 on the next, and the odd pixel always fell
   under the word. So the block is a whole 20px and the baseline a whole
   14px below its top - 14 is the nearest whole pixel to the middle
   (the cap band wants 14.18), and whole so that a stage whose own top
   is a fraction cannot round the type and the block apart. That leaves
   5.64px over the cap line against 6px under the baseline, as level as
   the pixel grid gets, on every page and at every window. Sideways it
   is the tracking: .mono tracks 0.12em and the tail of it is laid after
   the last letter as well, so equal padding leaves the word 1.38px
   short of the right edge; the padding carries half that tail over to
   the left. What is left either way is the letters' own side bearings,
   which are the type's rhythm and not ours to level. None of it reads
   the word, so Clearance, Sale, Liquidacion and Oferta all stand the
   same, and the accent on the O rides above the cap band as it should. */
.p3d .stage-deal .deal-tag { display: block; line-height: 1;
  --tag-h: 20px;
  --tag-asc: 9px;
  --tag-base: 14px;
  --tag-line: calc(var(--tag-base) - var(--tag-asc));
  --tag-track: 0.12em;
  height: var(--tag-h); letter-spacing: var(--tag-track);
  padding: var(--tag-line) calc(9px - var(--tag-track) / 2)
    calc(var(--tag-h) - 1em - var(--tag-line)) calc(9px + var(--tag-track) / 2);
  background: var(--red); color: #fff; }
/* the hint on the status's left margin, one line below it. It keeps the
   gutter the status keeps, and it is a line of its own whether or not
   this page has a badge, so the badge cannot push it and a colourway
   that carries two words cannot either. */
.p3d .stage-hint { top: 44px; left: var(--gutter); right: auto; text-align: left; }
/* the room photographs take the whole stage: the badge steps aside with
   the hint and the dimension labels */
.p3d .stage.in-scenes .stage-deal { opacity: 0; }
@media (max-width: 960px) { .p3d .stage-deal { top: 14px; } }
/* the claims sheet and the technologies hold what the PIM states, which
   can be one: a lone card takes the column rather than leaving a hole */
.p3d .claims.one, .p3d .techs.one { grid-template-columns: minmax(0, 1fr); }
.p3d .claims.one .claim, .p3d .techs.one .tech { padding-right: 0; }
/* a series can have any number of colourways and a family any number of
   sizes; the finished page was drawn with three of each */
.p3d .swatches { grid-template-columns: repeat(auto-fill, minmax(58px, 1fr)); }
.p3d .sizes { grid-template-columns: repeat(auto-fill, minmax(84px, 1fr)); --plate: 44px; }
/* auto-fill takes as many chips as the field holds and strands whatever is
   left over: eight colourways in a field that takes six read as six and
   two, which looks like a fault rather than a layout. A run that wraps is
   balanced instead, over the same number of rows it already used:
   ceil(N / ceil(N / C)) columns, where C is the number that actually fit.
   C follows the width of the FIELD and not the width of the window, since
   that field is a whole phone screen wide and a 360px rail on a desktop,
   so the bands below are container queries. Each band is the stretch of
   field widths over which auto-fill and the balanced count disagree,
   measured rather than assumed; outside them auto-fill is already even and
   is left alone, which is why one, two and three colourways have no rule
   at all. The swatch gap is 8px below 961px and 10px above it, and that
   moves every swatch band by 2px per column, so the five are written once
   for each. The size gap is 10px at every width and is written once. */
.p3d .field:has(> .swatches), .p3d .field:has(> .sizes) {
  container-type: inline-size; container-name: options; }
@media (max-width: 960px) {
/* four colourways where three fit: 2 + 2, not 3 + 1 */
@container options (width >= 190px) and (width < 256px) {
.p3d .swatches:has(> :nth-child(4):last-child) { grid-template-columns: repeat(2, 1fr); } }
/* five where four fit: 3 + 2 */
@container options (width >= 256px) and (width < 322px) {
.p3d .swatches:has(> :nth-child(5):last-child) { grid-template-columns: repeat(3, 1fr); } }
/* six where four or five fit: 3 + 3 */
@container options (width >= 256px) and (width < 388px) {
.p3d .swatches:has(> :nth-child(6):last-child) { grid-template-columns: repeat(3, 1fr); } }
/* seven where five or six fit: 4 + 3 */
@container options (width >= 322px) and (width < 454px) {
.p3d .swatches:has(> :nth-child(7):last-child) { grid-template-columns: repeat(4, 1fr); } }
/* eight where five, six or seven fit: 4 + 4, the six and two that was seen */
@container options (width >= 322px) and (width < 520px) {
.p3d .swatches:has(> :nth-child(8):last-child) { grid-template-columns: repeat(4, 1fr); } }
}
/* the same five bands on the desktop rail, where the gap is 10px */
@media (min-width: 961px) {
@container options (width >= 194px) and (width < 262px) {
.p3d .swatches:has(> :nth-child(4):last-child) { grid-template-columns: repeat(2, 1fr); } }
@container options (width >= 262px) and (width < 330px) {
.p3d .swatches:has(> :nth-child(5):last-child) { grid-template-columns: repeat(3, 1fr); } }
@container options (width >= 262px) and (width < 398px) {
.p3d .swatches:has(> :nth-child(6):last-child) { grid-template-columns: repeat(3, 1fr); } }
@container options (width >= 330px) and (width < 466px) {
.p3d .swatches:has(> :nth-child(7):last-child) { grid-template-columns: repeat(4, 1fr); } }
@container options (width >= 330px) and (width < 534px) {
.p3d .swatches:has(> :nth-child(8):last-child) { grid-template-columns: repeat(4, 1fr); } }
}
/* four sizes where three fit: 2 + 2, not 3 + 1 */
@container options (width >= 272px) and (width < 366px) {
.p3d .sizes:has(> :nth-child(4):last-child) { grid-template-columns: repeat(2, 1fr); } }
/* the finished page sized its three plates by hand, one CSS rule each. A
   family has its own formats, so each plate carries its ratio and the rule
   is the same one: the longest side of the largest format fills the plate. */
.p3d .size-opt .plate { width: calc(var(--pw, 1) * var(--plate));
  height: calc(var(--ph, 1) * var(--plate)); }
@media (min-width: 961px) and (max-height: 840px) { .p3d .sizes { --plate: 38px; } }
/* A size or a surface the series is made in and this page is not. The
   option keeps its place in the run so the control reads as the series'
   whole range, it is ruled out rather than removed, and it answers no
   hover, click, tap or key. The sentence that says so in words is on the
   button itself.
   It was drawn as a dashed outline with the label struck through, and on
   a plank that came apart. The plate is the format's true shape on the
   series' own scale, so a 9 x 60 in a 44px plate is 6.59px wide: a dashed
   1.5px outline at 0.6 opacity has nothing to describe at that width and
   rendered as two short marks, which the owner read as a shape that had
   not loaded - the one thing the state was drawn to avoid. The plate is
   now hatched: a solid --line outline and a 45 degree rule fill at the
   same weight. Hatching is what a drawing does to take a region out, it
   holds at 6.59px as well as it does on a 44px square, and it cannot be
   mistaken for a skeleton, which is flat by definition. The label drops
   the line through it, which read as a deletion of the words rather than
   of the size, and goes quiet in --ink-faint instead. The Finish control
   is the same state and takes the same hatch. */
.p3d .size-opt.is-off, .p3d .fin-seg button.is-off { cursor: default;
  /* 1.5px of rule every 4.5px on the diagonal, in the ink --line is made
     of at a lighter alpha. One figure serves both controls: the Size
     plate can be 6.59px wide and the Finish button 170px, and a hatch
     dense enough to read on the plate would crowd the word on the
     button at --line's own 0.32. */
  --off-hatch: repeating-linear-gradient(45deg,
    transparent 0 3px, rgba(20, 17, 16, 0.2) 3px 4.5px); }
.p3d .size-opt.is-off .plate { border-color: var(--line); opacity: 1;
  background: var(--off-hatch); }
.p3d .size-opt.is-off .lbl { color: var(--ink-faint); opacity: 1;
  text-decoration: none; }
.p3d .size-opt.is-off:hover .plate { background: var(--off-hatch); }
.p3d .size-opt.is-off:hover .lbl { color: var(--ink-faint); }
.p3d .fin-seg button.is-off { color: var(--ink-faint); opacity: 1;
  text-decoration: none; background: var(--off-hatch); }
.p3d .fin-seg button.is-off:hover { color: var(--ink-faint); }
/* said to a screen reader and to nobody else */
.p3d .vh { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }
