/* Smart Art storefront — editorial gallery.
 *
 * Ink on bone, thin rules, one brass accent. The murals carry all the colour,
 * so the chrome around them stays almost monochrome.
 *
 * No build step and no webfonts (the whole thing has to run off a local static
 * server with no network), so the display face is a system serif stack.
 */

:root {
  --ink: #1b1a16;
  --ink-soft: #4a473f;
  --muted: #6d685d;
  --bone: #eae6dd;
  --bone-2: #f3f0e9;
  --paper: #fbfaf7;
  --line: #d6d0c4;
  --line-soft: #e5e0d6;
  --brass: #9a7b3f;
  --brass-lit: #b8934c;
  --white: #fff;

  --serif: "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, "Times New Roman", serif;
  --sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", sans-serif;

  --shell: 1240px;
  --gut: 24px;
  --rule: 1px solid var(--line);
}

* { box-sizing: border-box; }

/* Any class that sets `display` (.btn, .loading, …) would otherwise beat the
 * user-agent's [hidden] rule, leaving "hidden" elements on screen. */
[hidden] { display: none !important; }

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

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 16px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;           /* belt and braces: nothing may scroll sideways */
}

img { max-width: 100%; display: block; }

a { color: inherit; }

h1, h2, h3 { font-family: var(--serif); font-weight: 400; letter-spacing: -0.01em; line-height: 1.1; }

.shell { max-width: var(--shell); margin: 0 auto; padding: 0 var(--gut); }

.eyebrow {
  font-size: 11px;
  letter-spacing: 0.26em;
  text-transform: uppercase;
  color: var(--muted);
  font-family: var(--sans);
}

.rule { border: 0; border-top: var(--rule); margin: 0; }

/* skip link — keyboard users shouldn't have to tab the whole nav */
.skip {
  position: absolute; left: -9999px;
  background: var(--ink); color: var(--bone);
  padding: 12px 18px; z-index: 100;
}
.skip:focus { left: 12px; top: 12px; }

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

/* ============================ header ============================ */
.utility {
  background: var(--ink);
  color: #bdb8ab;
  font-size: 12.5px;
}
.utility .shell {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px; min-height: 38px; flex-wrap: wrap;
}
.utility a { color: #d5d0c4; text-decoration: none; }
.utility a:hover { color: var(--white); }
.utility .u-left { display: flex; gap: 20px; flex-wrap: wrap; }
.utility .u-right { display: flex; gap: 16px; align-items: center; }

.masthead {
  position: sticky; top: 0; z-index: 40;
  border-bottom: var(--rule);
}
/* The frosted panel lives on a pseudo-element, not on .masthead itself.
 * A backdrop-filter makes its element a containing block for `position: fixed`
 * descendants — which would trap the mobile nav drawer inside the header and
 * make its off-screen position scroll the whole page sideways. */
.masthead::before {
  content: ""; position: absolute; inset: 0; z-index: -1;
  background: rgba(251, 250, 247, 0.92);
  backdrop-filter: blur(8px);
}
.masthead .shell {
  position: relative;
  display: flex; align-items: center; justify-content: space-between;
  min-height: 74px; gap: 20px;
}

/* the wordmark: "smart | art", the rule between them is the brand */
.wordmark {
  display: inline-flex; align-items: baseline; gap: 10px;
  font-family: var(--serif); font-size: 27px; letter-spacing: -0.02em;
  text-decoration: none; color: var(--ink); line-height: 1;
}
.wordmark .bar { width: 1px; height: 24px; background: var(--brass); align-self: center; }
.wordmark .tag {
  display: block; font-family: var(--sans); font-size: 9.5px;
  letter-spacing: 0.2em; text-transform: uppercase; color: var(--muted);
  margin-top: 5px;
}
.wordmark-stack { display: flex; flex-direction: column; }
.wordmark-row { display: inline-flex; align-items: center; gap: 10px; }

nav.primary { display: flex; gap: 30px; align-items: center; }
nav.primary a {
  text-decoration: none; font-size: 13.5px; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--ink-soft); padding: 6px 0;
  border-bottom: 1px solid transparent;
}
nav.primary a:hover { color: var(--ink); border-bottom-color: var(--line); }
nav.primary a[aria-current="page"] { color: var(--ink); border-bottom-color: var(--brass); }

/* SA-6: the Artists group. The link navigates; the chevron opens the list.
   On a pointer device the panel drops on hover or keyboard focus and the
   chevron button is decorative-only (still rendered, so the hit area is the
   same); inside the mobile drawer the panel is an accordion the chevron
   toggles, because there is no hover to open it with. */
.navgroup { position: relative; display: flex; align-items: center; gap: 4px; }
.navgroup-toggle {
  background: none; border: 0; padding: 6px 2px; cursor: pointer; line-height: 0;
  color: var(--ink-soft);
}
.navgroup-toggle .chev {
  display: block; width: 7px; height: 7px; border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor; transform: rotate(45deg) translate(-2px, -2px);
  transition: transform 0.2s ease;
}
.navdrop {
  position: absolute; top: 100%; left: -18px; min-width: 288px;
  background: var(--paper); border: var(--rule); border-radius: 3px;
  padding: 6px 0; margin-top: 12px; box-shadow: 0 14px 34px rgba(20,19,15,0.13);
  opacity: 0; visibility: hidden; transform: translateY(-6px);
  transition: opacity 0.18s ease, transform 0.18s ease, visibility 0.18s;
}
/* SPECIFICITY — the reason these are scoped under `nav.primary`:
   `nav.primary a` is (0,1,2) and beats a plain `.navdrop a` (0,1,1), so EVERY
   property the two rules share — padding, font-size, text-transform,
   letter-spacing — silently reverted to the main nav's uppercase menu style
   with no horizontal padding. That is what "cramped, and tight against the left
   edge" looked like on both breakpoints. Two classes plus the type beat it.

   Name and dates stack: "Hannah Borger Overbeck" cannot share a line with its
   lifespan, and a wrapped name with the dates floating up the right edge reads
   as two separate entries. */
nav.primary .navdrop a {
  display: block; width: auto; padding: 12px 24px;
  font-family: var(--serif); font-size: 16px; line-height: 1.25;
  text-transform: none; letter-spacing: 0; color: var(--ink);
  border-bottom: 0;
}
nav.primary .navdrop a + a { border-top: 1px solid var(--line-soft); }
nav.primary .navdrop a small {
  display: block; margin-top: 4px; white-space: nowrap;
  font-family: var(--sans); font-size: 10px; letter-spacing: 0.14em; color: var(--muted);
}
nav.primary .navdrop a:hover { background: var(--bone-2); color: var(--ink); border-bottom: 0; }
nav.primary .navdrop .all {
  font-family: var(--sans); font-size: 10.5px; letter-spacing: 0.16em;
  text-transform: uppercase; color: var(--muted);
  border-top: var(--rule); padding: 13px 24px; margin-top: 4px;
}
nav.primary .navdrop .all:hover { color: var(--ink); }
@media (hover: hover) {
  .navgroup:hover .navdrop, .navgroup:focus-within .navdrop {
    opacity: 1; visibility: visible; transform: none;
  }
  .navgroup:hover .navgroup-toggle .chev { transform: rotate(225deg) translate(-2px, -2px); }
}
/* Keyboard users on a touch device still need it to open on focus. */
.navgroup:focus-within .navdrop { opacity: 1; visibility: visible; transform: none; }

.burger {
  display: none; background: none; border: var(--rule); border-radius: 2px;
  padding: 9px 11px; cursor: pointer; line-height: 0;
}
.burger span { display: block; width: 18px; height: 1.5px; background: var(--ink); margin: 3.5px 0; }

@media (max-width: 820px) {
  .burger { display: block; }
  nav.primary {
    position: fixed; inset: 0 0 0 auto; width: min(78vw, 320px);
    background: var(--bone-2); border-left: var(--rule);
    flex-direction: column; align-items: flex-start; justify-content: flex-start;
    padding: 92px 28px; gap: 6px;
    transform: translateX(100%); transition: transform 0.28s ease;
  }
  nav.primary.open { transform: none; }
  nav.primary a { font-size: 16px; padding: 14px 0; width: 100%; border-bottom: var(--rule); }
  .utility .u-left { gap: 14px; }
  .utility .u-right { display: none; }

  /* In the drawer the group is a stacked row + accordion, not a floating panel.
     `position: static` on the panel is what turns the dropdown into an
     in-flow accordion; without it the list would hover over the links below. */
  .navgroup { display: block; width: 100%; position: relative; }
  .navgroup > a { display: inline-block; width: calc(100% - 56px); }
  .navgroup-toggle {
    position: absolute; top: 0; right: 0; width: 48px; height: 52px;
    display: flex; align-items: center; justify-content: center; padding: 0;
  }
  .navgroup-toggle .chev { width: 9px; height: 9px; }
  .navgroup.open .navgroup-toggle .chev { transform: rotate(225deg) translate(-2px, -2px); }
  .navdrop {
    position: static; opacity: 1; visibility: visible; transform: none;
    border: 0; box-shadow: none; background: none; margin: 0; padding: 0;
    min-width: 0; max-height: 0; overflow: hidden; transition: max-height 0.26s ease;
  }
  .navgroup.open .navdrop { max-height: 420px; }
  /* Same specificity note as the desktop panel: these must be scoped under
     `nav.primary` or the drawer's own `nav.primary a` rule wins and the names
     end up flush against the brass rail with no indent at all. */
  nav.primary .navdrop a {
    font-size: 16px; padding: 13px 0 13px 20px; border-bottom: var(--rule);
    border-top: 0; border-left: 2px solid var(--brass); margin-left: 2px;
    /* The uppercase "All artists" row is only 41px on its own line height. */
    min-height: 44px; display: flex; flex-direction: column; justify-content: center;
  }
  nav.primary .navdrop a:hover { background: none; }
  /* Out of the artist rail: it is a link to the index, not a fourth artist. */
  nav.primary .navdrop .all {
    border-left: 0; margin-left: 0; padding: 14px 0 14px 22px;
    border-top: 0; margin-top: 0;
  }
}

/* ============================ buttons ============================ */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 10px;
  font-family: var(--sans); font-size: 12.5px; letter-spacing: 0.16em;
  text-transform: uppercase; text-decoration: none; cursor: pointer;
  padding: 15px 30px; border: 1px solid var(--ink); background: var(--ink);
  color: var(--bone); border-radius: 1px; transition: background 0.18s, color 0.18s, border-color 0.18s;
}
.btn:hover { background: var(--brass); border-color: var(--brass); color: var(--white); }
.btn.ghost { background: transparent; color: var(--ink); }
.btn.ghost:hover { background: var(--ink); color: var(--bone); border-color: var(--ink); }
.btn.light { background: transparent; color: var(--bone); border-color: rgba(234,230,221,0.5); }
.btn.light:hover { background: var(--bone); color: var(--ink); border-color: var(--bone); }
.btn.block { width: 100%; }
.btn[disabled] { opacity: 0.45; cursor: not-allowed; }

/* ============================ hero ============================ */
.hero { position: relative; background: var(--ink); }
.hero img { width: 100%; height: min(76vh, 660px); object-fit: cover; opacity: 0.96; }
.hero .overlay {
  position: absolute; inset: 0;
  background: linear-gradient(100deg, rgba(20,19,15,0.72) 0%, rgba(20,19,15,0.42) 38%, rgba(20,19,15,0) 66%);
}
.hero .copy {
  position: absolute; inset: 0; display: flex; align-items: center;
}
.hero .copy .shell { width: 100%; }
.hero .inner { max-width: 520px; color: var(--bone); }
.hero .eyebrow { color: #c3bcac; }
.hero h1 { font-size: clamp(38px, 5.6vw, 68px); margin: 16px 0 0; color: var(--white); }
.hero h1 .sep { display: block; width: 62px; height: 1px; background: var(--brass-lit); margin: 26px 0; }
.hero p { color: #d9d4c8; font-size: 17px; max-width: 40ch; margin: 0 0 32px; }

@media (max-width: 700px) {
  .hero img { height: 74vh; }
  .hero .overlay { background: linear-gradient(180deg, rgba(20,19,15,0.35) 0%, rgba(20,19,15,0.8) 70%); }
  .hero .copy { align-items: flex-end; padding-bottom: 44px; }
}

/* ============================ sections ============================ */
.section { padding: 92px 0; }
.section.tight { padding: 64px 0; }
.section.bone { background: var(--bone-2); }
.section.ink { background: var(--ink); color: var(--bone); }
.section.ink h2 { color: var(--white); }
.section.ink .eyebrow { color: #a09a8b; }

.section-head { max-width: 620px; margin-bottom: 48px; }
.section-head h2 { font-size: clamp(28px, 3.4vw, 42px); margin: 14px 0 16px; }
.section-head p { color: var(--muted); margin: 0; font-size: 16.5px; }
.section.ink .section-head p { color: #a8a294; }

.head-row {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: 24px; margin-bottom: 48px; flex-wrap: wrap;
}
.head-row .section-head { margin-bottom: 0; }

.link-arrow {
  text-decoration: none; font-size: 12.5px; letter-spacing: 0.16em;
  text-transform: uppercase; border-bottom: 1px solid var(--brass);
  padding-bottom: 5px; white-space: nowrap;
}
.link-arrow:hover { color: var(--brass); }

/* ---- collection cards ---- */
.cards { display: grid; grid-template-columns: repeat(3, 1fr); gap: 28px; }
@media (max-width: 900px) { .cards { grid-template-columns: 1fr; gap: 24px; } }

.card { position: relative; text-decoration: none; color: inherit; display: block; }
.card .frame { overflow: hidden; background: var(--bone); aspect-ratio: 4 / 5; }
.card .frame img { width: 100%; height: 100%; object-fit: cover; transition: transform 0.7s ease; }
.card:hover .frame img { transform: scale(1.04); }
.card h3 { font-size: 23px; margin: 20px 0 6px; }
.card p { color: var(--muted); font-size: 14.5px; margin: 0; }

/* the "your own design" card has no photograph, only a rule and an invitation */
.card.custom .frame {
  display: flex; align-items: center; justify-content: center; padding: 34px;
  border: 1px solid var(--line); background: var(--paper);
}
.card.custom .frame .mark {
  text-align: center; font-family: var(--serif); font-size: 25px; color: var(--ink-soft); line-height: 1.35;
}
.card.custom .frame .mark span { display: block; font-family: var(--sans); font-size: 12px;
  letter-spacing: 0.22em; text-transform: uppercase; color: var(--brass); margin-top: 18px; }

/* ---- why-us / specs strip ---- */
.specs { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1px; background: var(--line-soft); border: var(--rule); }
.specs .spec { background: var(--paper); padding: 34px 30px; }
.section.ink .specs { background: #2c2a24; border-color: #33312a; }
.section.ink .specs .spec { background: var(--ink); }
.specs .spec .n { font-family: var(--serif); font-size: 15px; color: var(--brass); }
.specs .spec h3 { font-size: 19px; margin: 14px 0 8px; }
.specs .spec p { margin: 0; color: var(--muted); font-size: 14.5px; }
.section.ink .specs .spec p { color: #a09a8b; }
@media (max-width: 900px) { .specs { grid-template-columns: 1fr; } }

/* ---- guarantee ---- */
.guarantee { display: grid; grid-template-columns: 1fr 1fr; gap: 72px; align-items: center; }
@media (max-width: 900px) { .guarantee { grid-template-columns: 1fr; gap: 36px; } }
.guarantee h2 { font-size: clamp(30px, 3.6vw, 44px); margin: 14px 0 20px; }
.guarantee p { color: #a8a294; }
blockquote.pull {
  margin: 0; padding-left: 30px; border-left: 1px solid var(--brass);
  font-family: var(--serif); font-size: clamp(20px, 2.3vw, 27px); line-height: 1.45; color: var(--bone);
}
blockquote.pull cite {
  display: block; margin-top: 22px; font-family: var(--sans); font-style: normal;
  font-size: 11.5px; letter-spacing: 0.22em; text-transform: uppercase; color: var(--brass-lit);
}

/* ============================ shop ============================ */
.filters { display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 40px; }
.chip {
  background: transparent; border: var(--rule); border-radius: 999px;
  padding: 9px 20px; font-size: 12.5px; letter-spacing: 0.1em; text-transform: uppercase;
  cursor: pointer; color: var(--ink-soft); font-family: var(--sans);
}
.chip:hover { border-color: var(--ink); color: var(--ink); }
.chip[aria-pressed="true"] { background: var(--ink); border-color: var(--ink); color: var(--bone); }

.grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 34px 28px; }
@media (max-width: 980px) { .grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 620px) { .grid { grid-template-columns: 1fr; } }

/* A column, so the buttons line up across the row however long the blurb runs. */
.tile { display: flex; flex-direction: column; }
.tile .art {
  position: relative; display: block; overflow: hidden;
  aspect-ratio: 4 / 3; background: var(--bone);
}
.tile .art img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }

/* The room shot sits underneath; the flat artwork lifts away on hover to reveal
 * it. It is therefore invisible on a touch screen, where a tap on the tile just
 * follows the link. Carrying it as a background inside `(hover: hover)` means a
 * phone never downloads it at all — a rule that does not match is never fetched.
 * That is ~650KB saved on the product page alone. The URLs arrive as custom
 * properties on the element (see designs.html / design_detail.html). */
@media (hover: hover) {
  .tile .art {
    background-image: var(--room-full);
    /* Where image-set is supported, a non-retina screen takes the 700px file. */
    background-image: image-set(var(--room-small) 1x, var(--room-full) 2x);
    background-size: cover;
    background-position: center;
  }
  .tile .art .flat { transition: opacity 0.45s ease; }
  .tile:hover .art .flat, .tile:focus-within .art .flat { opacity: 0; }
}
.tile .art .peek {
  position: absolute; left: 0; bottom: 0; z-index: 2;
  background: rgba(27,26,22,0.82); color: var(--bone);
  font-size: 10.5px; letter-spacing: 0.18em; text-transform: uppercase;
  padding: 8px 14px; opacity: 0; transition: opacity 0.3s;
}
.tile:hover .art .peek { opacity: 1; }

.tile .meta { display: flex; justify-content: space-between; align-items: baseline; gap: 14px; margin-top: 18px; }
.tile .meta h3 { font-size: 22px; margin: 0; }
.tile .meta h3 a { text-decoration: none; }
.tile .meta h3 a:hover { color: var(--brass); }
.tile .meta .coll { font-size: 11px; letter-spacing: 0.16em; text-transform: uppercase; color: var(--muted); white-space: nowrap; }
.tile p.desc {
  color: var(--muted); font-size: 14.5px; margin: 8px 0 18px;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  line-clamp: 2; overflow: hidden;
}
.tile .acts { display: flex; gap: 14px; align-items: center; margin-top: auto; }
.tile .acts .btn { padding: 12px 20px; font-size: 11.5px; }

.empty-note { color: var(--muted); padding: 40px 0; }

/* ============================ product ============================ */
.crumbs { font-size: 12.5px; color: var(--muted); padding: 22px 0; }
.crumbs a { text-decoration: none; }
.crumbs a:hover { color: var(--ink); }
.crumbs .sep { margin: 0 8px; color: var(--line); }

.product { display: grid; grid-template-columns: 1.35fr 1fr; gap: 60px; align-items: start; padding-bottom: 90px; }
.product > * { min-width: 0; }   /* grid items default to min-content; the preview is wide */
@media (max-width: 1000px) { .product { grid-template-columns: 1fr; gap: 40px; } }

.product .info h1 { font-size: clamp(32px, 3.8vw, 46px); margin: 12px 0 18px; }
.product .info .lede { font-size: 17px; color: var(--ink-soft); margin: 0 0 30px; }
.product .actions { display: flex; flex-direction: column; gap: 12px; margin-bottom: 34px; }
.product .consult { font-size: 13.5px; color: var(--muted); text-align: center; }
.product .consult a { color: var(--brass); }

.speclist { list-style: none; margin: 0; padding: 0; border-top: var(--rule); }
.speclist li { display: flex; gap: 16px; padding: 14px 0; border-bottom: var(--rule); font-size: 14.5px; }
.speclist li .k { color: var(--muted); min-width: 118px; }

.match {
  margin-top: 34px; padding: 26px 28px; background: var(--bone-2); border-left: 2px solid var(--brass);
}
.match h3 { font-size: 20px; margin: 0 0 10px; }
.match p { margin: 0; font-size: 14.5px; color: var(--ink-soft); }
.match .who { display: block; margin-top: 14px; font-size: 11px; letter-spacing: 0.2em;
  text-transform: uppercase; color: var(--muted); }

/* ---- the embedded visualiser ---- */
.preview { position: relative; background: var(--ink); overflow: hidden; }
.preview .stagebox {
  position: relative; width: 100%; aspect-ratio: 4 / 3; overflow: hidden;
}
/* The stage is laid out at the room's natural size (e.g. 4000x3000) and scaled
 * down to fit, which keeps every corner coordinate in room pixels. It must be
 * absolutely positioned: as a normal-flow child its 4000px width becomes the
 * grid item's min-content width and pushes the page sideways. */
.preview .stage {
  position: absolute; left: 50%; top: 50%;
  transform-origin: center;
  /* No will-change here. It promotes the stage to its own GPU layer for the
     whole life of the page, and a promoted layer holding a mix-blend-mode child
     is a documented source of the blend dropping out during scroll on Android.
     The transform still gets composited while a corner is actually being
     dragged; a permanent hint buys nothing and costs that risk. */
  /* Contain the .pattern multiply to this stage. A blend layer that is not
     isolated composites against whatever is behind it, and on a phone that
     backdrop changes as the page moves — which reads as the picture flickering
     or tearing while you scroll. transform already opens a stacking context;
     this states the intent and covers browsers that promote the layer. */
  isolation: isolate;
}
.preview .layer { position: absolute; inset: 0; width: 100%; height: 100%; }
.preview .pattern {
  position: absolute; top: 0; left: 0; transform-origin: 0 0;
  background-repeat: no-repeat; background-size: 100% 100%;
  mix-blend-mode: multiply; pointer-events: none;
}
.preview img.shade { mix-blend-mode: multiply; }
.preview img.base, .preview img.fg, .preview img.shade { pointer-events: none; }

.preview .edges { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; opacity: 0; }
.preview .edges polygon { fill: none; stroke: var(--brass-lit); stroke-width: 3; stroke-dasharray: 10 8; vector-effect: non-scaling-stroke; }
.preview.adjust .edges { opacity: 1; }

.preview .handles { position: absolute; inset: 0; pointer-events: none; }
.preview.adjust .handles { pointer-events: auto; }
.preview .handle {
  position: absolute; width: 30px; height: 30px; margin: -15px 0 0 -15px;
  border-radius: 50%; border: 2px solid var(--brass-lit); background: rgba(27,26,22,0.55);
  cursor: grab; opacity: 0; transition: opacity 0.2s; touch-action: none;
}
.preview.adjust .handle { opacity: 1; }
.preview .handle:active { cursor: grabbing; background: var(--brass); }

.preview .bar {
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  padding: 14px 16px; background: #14140f; border-top: 1px solid #2b2921; flex-wrap: wrap;
}
.preview .bar label { font-size: 11px; letter-spacing: 0.18em; text-transform: uppercase; color: #8d8779; }
.preview .roomsel { display: flex; align-items: center; gap: 12px; }
.preview select {
  background: #201f18; color: var(--bone); border: 1px solid #3a382e; border-radius: 2px;
  padding: 9px 12px; font-family: var(--sans); font-size: 14px; min-width: 190px;
}
.preview .toggle {
  background: transparent; border: 1px solid #3a382e; color: #b3ada0; cursor: pointer;
  font-size: 11px; letter-spacing: 0.16em; text-transform: uppercase;
  padding: 10px 16px; border-radius: 2px; font-family: var(--sans);
}
.preview .toggle:hover { color: var(--bone); border-color: #55524512; border-color: #555245; }
.preview .toggle[aria-pressed="true"] { background: var(--brass); border-color: var(--brass); color: #14140f; }
.preview .hint { font-size: 12px; color: #6f6a5e; }

.preview .loading {
  position: absolute; inset: 0; display: grid; place-items: center;
  color: #6f6a5e; font-size: 13px; letter-spacing: 0.16em; text-transform: uppercase;
}

/* ============================ quote dialog ============================ */
dialog.quote {
  border: none; padding: 0; width: min(660px, calc(100vw - 32px));
  max-height: min(88vh, 900px); background: var(--paper); color: var(--ink);
  border-radius: 2px; box-shadow: 0 30px 90px rgba(0,0,0,0.4);
}
dialog.quote::backdrop { background: rgba(20,19,15,0.72); backdrop-filter: blur(3px); }
dialog.quote form { display: flex; flex-direction: column; max-height: inherit; }

.q-head { padding: 24px 30px 0; }
.q-head .top { display: flex; justify-content: space-between; align-items: flex-start; gap: 20px; }
.q-head h2 { font-size: 27px; margin: 10px 0 4px; }
.q-head .sub { color: var(--muted); font-size: 14px; margin: 0; }
.q-close { background: none; border: none; cursor: pointer; font-size: 26px; line-height: 1; color: var(--muted); padding: 4px 6px; }
.q-close:hover { color: var(--ink); }

/* the chosen design, carried into the form so nobody retypes its name */
.q-chip { display: flex; align-items: center; gap: 14px; margin-top: 20px; padding: 12px; background: var(--bone-2); border: var(--rule); }
.q-chip img { width: 62px; height: 48px; object-fit: cover; }
.q-chip .t { font-family: var(--serif); font-size: 18px; line-height: 1.2; }
.q-chip .c { font-size: 11px; letter-spacing: 0.16em; text-transform: uppercase; color: var(--muted); margin-top: 4px; }

.q-steps { display: flex; gap: 8px; padding: 20px 30px 0; }
.q-steps .s { flex: 1; }
.q-steps .s .bar { height: 2px; background: var(--line); }
.q-steps .s.done .bar, .q-steps .s.now .bar { background: var(--brass); }
.q-steps .s .lbl { font-size: 10.5px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--muted); margin-top: 8px; }
.q-steps .s.now .lbl { color: var(--ink); }

.q-body { padding: 26px 30px; overflow-y: auto; flex: 1; }
.q-panel { display: none; }
.q-panel.active { display: block; animation: qfade 0.22s ease; }
@keyframes qfade { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }

.q-legend { font-family: var(--serif); font-size: 20px; margin: 0 0 4px; }
.q-note { color: var(--muted); font-size: 13.5px; margin: 0 0 22px; }

.field { margin-bottom: 18px; }
.field > label, .fieldset > legend {
  display: block; font-size: 11.5px; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--ink-soft); margin-bottom: 8px; padding: 0;
}
.field .req { color: var(--brass); }
.field input[type="text"], .field input[type="email"], .field input[type="tel"],
.field input[type="number"], .field textarea, .field select, .field input[type="file"] {
  width: 100%; padding: 12px 14px; border: var(--rule); border-radius: 2px;
  background: var(--white); font-family: var(--sans); font-size: 15px; color: var(--ink);
}
.field textarea { min-height: 92px; resize: vertical; }
.field input:focus, .field textarea:focus, .field select:focus { border-color: var(--brass); outline: none; }
.field input[aria-invalid="true"], .field textarea[aria-invalid="true"] { border-color: #a33b2b; }
.field .err { color: #a33b2b; font-size: 12.5px; margin-top: 6px; display: none; }
.field .err.show { display: block; }

.row2 { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; }
@media (max-width: 560px) { .row2 { grid-template-columns: 1fr; gap: 0; } }

.fieldset { border: 0; margin: 0 0 18px; padding: 0; }
.opts { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
@media (max-width: 560px) { .opts { grid-template-columns: repeat(2, 1fr); } }
.opt {
  display: flex; align-items: center; gap: 9px; padding: 11px 12px;
  border: var(--rule); border-radius: 2px; cursor: pointer; font-size: 13.5px; background: var(--white);
}
.opt:hover { border-color: var(--ink-soft); }
.opt input { margin: 0; accent-color: var(--brass); }
.opt:has(input:checked) { border-color: var(--brass); background: #faf6ee; }
.opts.two { grid-template-columns: 1fr 1fr; }

/* live area readout */
.q-area {
  display: flex; align-items: baseline; justify-content: space-between; gap: 16px;
  padding: 14px 16px; background: var(--bone-2); border: var(--rule); margin-bottom: 6px;
}
.q-area .n { font-family: var(--serif); font-size: 26px; }
.q-area .n small { font-size: 14px; color: var(--muted); }
.q-area .k { font-size: 11px; letter-spacing: 0.16em; text-transform: uppercase; color: var(--muted); }
.q-seam {
  display: none; gap: 10px; align-items: flex-start;
  font-size: 13px; color: var(--ink-soft); background: #faf6ee;
  border-left: 2px solid var(--brass); padding: 12px 14px; margin-bottom: 18px;
}
.q-seam.show { display: flex; }

/* review table */
.q-review { border-top: var(--rule); }
.q-review .r { display: flex; gap: 16px; padding: 11px 0; border-bottom: var(--rule); font-size: 14px; }
.q-review .r .k { color: var(--muted); min-width: 150px; }
.q-review .r .v { flex: 1; }

.q-foot {
  display: flex; align-items: center; justify-content: space-between; gap: 14px;
  padding: 18px 30px; border-top: var(--rule); background: var(--bone-2);
}
.q-foot .spacer { flex: 1; }
.q-foot .btn { padding: 13px 26px; }

/* sent confirmation */
.q-sent { text-align: center; padding: 16px 0 6px; }
.q-sent .tick { font-size: 40px; color: var(--brass); font-family: var(--serif); }
.q-sent h3 { font-size: 25px; margin: 12px 0 8px; }
.q-sent p { color: var(--muted); margin: 0 auto 22px; max-width: 44ch; font-size: 14.5px; }
.q-sent .row { display: flex; gap: 12px; justify-content: center; flex-wrap: wrap; }

/* ============================ contact ============================ */
.contact { display: grid; grid-template-columns: 1fr 1fr; gap: 72px; align-items: start; }
@media (max-width: 900px) { .contact { grid-template-columns: 1fr; gap: 44px; } }
.contact h1 { font-size: clamp(32px, 4vw, 48px); margin: 14px 0 18px; }

.deets { list-style: none; margin: 32px 0 0; padding: 0; border-top: var(--rule); }
.deets li { padding: 16px 0; border-bottom: var(--rule); display: flex; gap: 18px; }
.deets li .k { font-size: 11px; letter-spacing: 0.16em; text-transform: uppercase; color: var(--muted); min-width: 96px; padding-top: 3px; }
.deets li .v { font-size: 15.5px; }
.deets li .v a { text-decoration: none; border-bottom: 1px solid var(--line); }
.deets li .v a:hover { color: var(--brass); border-color: var(--brass); }

/* a drawn placeholder, because an embedded map would need the network */
.mapcard { margin-top: 36px; border: var(--rule); background: var(--bone-2); padding: 26px; }
.mapcard .pin { font-family: var(--serif); font-size: 19px; }
.mapcard p { color: var(--muted); font-size: 14px; margin: 8px 0 16px; }

.formcard { border: var(--rule); background: var(--white); padding: 34px; }
.formcard h2 { font-size: 24px; margin: 0 0 6px; }
.formcard > p.intro { color: var(--muted); font-size: 14px; margin: 0 0 26px; }
.check { display: flex; gap: 10px; align-items: flex-start; font-size: 14px; margin-bottom: 12px; color: var(--ink-soft); }
.check input { margin-top: 4px; accent-color: var(--brass); }
.form-ok {
  display: none; margin-top: 18px; padding: 14px 16px; background: #f2f6f0;
  border-left: 2px solid #5c7a52; font-size: 14px;
}
.form-ok.show { display: block; }

/* ============================ footer ============================ */
footer.site { background: var(--ink); color: #a09a8b; padding: 74px 0 30px; margin-top: 0; }
footer.site h4 {
  font-family: var(--sans); font-size: 11px; letter-spacing: 0.2em; text-transform: uppercase;
  color: var(--bone); margin: 0 0 20px; font-weight: 500;
}
footer.site .cols { display: grid; grid-template-columns: 1.4fr 1fr 1fr 1fr; gap: 44px; }
@media (max-width: 900px) { footer.site .cols { grid-template-columns: 1fr 1fr; gap: 36px; } }
@media (max-width: 520px) { footer.site .cols { grid-template-columns: 1fr; } }
footer.site ul { list-style: none; margin: 0; padding: 0; }
footer.site li { margin-bottom: 11px; font-size: 14.5px; }
footer.site a { color: #a09a8b; text-decoration: none; }
footer.site a:hover { color: var(--bone); }
footer.site .wordmark { color: var(--bone); }
footer.site .wordmark .tag { color: #7d7768; }
footer.site .blurb { font-size: 14.5px; margin: 22px 0 0; max-width: 30ch; }
footer.site .legal {
  margin-top: 56px; padding-top: 24px; border-top: 1px solid #302e27;
  display: flex; justify-content: space-between; gap: 20px; flex-wrap: wrap; font-size: 12.5px; color: #6f6a5e;
}

/* the demo needs to say it's a demo */
.demo-flag {
  background: #2c2a24; color: #c9c3b4; font-size: 12px; text-align: center;
  padding: 9px 16px; letter-spacing: 0.04em;
}
.demo-flag b { color: var(--brass-lit); font-weight: 500; }

/* ============================ platform additions ============================
 * Everything below was added when the standalone demo became a PBC microsite.
 * The brand accent is driven by Microsite.primary_color, injected as an inline
 * `--brass` override on <html> by smartart_base.html, so these rules only need
 * to reference the variable.
 */

/* Quote dialog: the demo ended at a mailto link. Now it posts, so it needs a
 * pending state, a server-error state, and a real reference number on success. */
.q-sent .ref {
  font-family: var(--serif); font-size: 30px; letter-spacing: 0.02em;
  color: var(--brass); margin: 14px 0 6px;
}
.q-sent .ref small { display: block; font-family: var(--sans); font-size: 12px;
  letter-spacing: 0.16em; text-transform: uppercase; color: var(--muted); }

.btn[aria-busy="true"] { opacity: 0.6; pointer-events: none; }

.q-error {
  display: none; margin: 16px 0 0; padding: 12px 14px;
  border: 1px solid #b4553f; background: #fdf3f0; color: #8c3a28; font-size: 14px;
}
.q-error.show { display: block; }

/* upload field: show the chosen filename + guard oversize files client-side */
.q-file-note { font-size: 12.5px; color: var(--muted); margin-top: 6px; }
.q-file-note.bad { color: #a6442f; }

/* Room switcher thumbnails — the DB gives us a preview.webp per room, which the
 * standalone demo never had (it only had a <select>). */
.preview .roomstrip {
  display: flex; gap: 8px; overflow-x: auto; padding: 10px 12px;
  background: #1b1a16; border-top: 1px solid #302e27; scrollbar-width: thin;
}
.preview .roomstrip button {
  flex: 0 0 auto; width: 84px; padding: 0; border: 2px solid transparent;
  background: none; cursor: pointer; border-radius: 2px; overflow: hidden; line-height: 0;
}
.preview .roomstrip button img { width: 100%; height: 56px; object-fit: cover; }
.preview .roomstrip button span {
  display: block; font-size: 10px; line-height: 1.3; padding: 4px 2px;
  color: #8d8779; font-family: var(--sans); text-align: center;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.preview .roomstrip button:hover { border-color: #55524533; }
.preview .roomstrip button[aria-pressed="true"] { border-color: var(--brass); }
.preview .roomstrip button[aria-pressed="true"] span { color: var(--bone); }

/* the visualiser page: design swatches down the side */
.vis-wrap { display: grid; grid-template-columns: 1fr 240px; gap: 28px; align-items: start; }
/* A grid item's automatic minimum size is its CONTENT's min-content width. The
 * room strip is a 25-button flex row ~2300px wide, so without this the column
 * refuses to shrink and drags the whole page sideways on every screen. The strip
 * scrolls inside itself; the column must be allowed to be narrower than it.
 * (The product page escapes this only because .preview is itself the grid item
 * there, and its overflow:hidden zeroes the automatic minimum.) */
.vis-wrap > * { min-width: 0; }

/* The stage column here is ~924px wide, and `aspect-ratio: 4/3` turns that into
 * 693px of height — the whole tool (stage + bar + room strip) then runs ~400px
 * past the fold. The product page never hits this because .preview shares a
 * two-column split and lands at ~650px wide.
 *
 * So budget the HEIGHT first and derive the width from it, using the 3:2 shape
 * that 21 of the 25 rooms have. Constraining the width (rather than clamping the
 * height with max-height) is what keeps the room filling the box instead of
 * sitting in black letterbox bars. fitStage() scales the room into whatever box
 * it is handed, so no JS needs to know about this. */
.vis-wrap .preview {
  width: min(100%, calc(min(56vh, 520px) * 1.5));
  margin-inline: auto;
}
.vis-wrap .stagebox { aspect-ratio: 3 / 2; }

@media (max-width: 900px) {
  .vis-wrap { grid-template-columns: 1fr; }
  .vis-wrap .preview { width: 100%; }   /* one column: the stage may have it all */
}

/* On this page the tool is the content, not the copy above it. Trimming the
 * standard section chrome lifts the whole stage above the fold on a 900px screen. */
body[data-page="visualiser"] .section.tight { padding-top: 36px; }
body[data-page="visualiser"] .section-head { margin-bottom: 28px; }
.vis-swatches { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; }
@media (max-width: 900px) { .vis-swatches { grid-template-columns: repeat(4, 1fr); } }
.vis-swatches button {
  padding: 0; border: 2px solid var(--line); background: none; cursor: pointer;
  line-height: 0; border-radius: 2px; overflow: hidden;
}
.vis-swatches button img { width: 100%; aspect-ratio: 1; object-fit: cover; }
.vis-swatches button span {
  display: block; font-size: 11px; padding: 5px 4px; color: var(--muted);
  font-family: var(--sans); line-height: 1.3; text-align: center;
}
.vis-swatches button[aria-pressed="true"] { border-color: var(--brass); }
.vis-swatches button[aria-pressed="true"] span { color: var(--ink); }

/* ============================ mobile / touch ============================
 * Audited over the DevTools protocol with touch emulation at 360, 375, 390 and
 * 768px. The visualiser is the selling point and it is mostly used on a phone,
 * so these are correctness rules, not polish.
 */

/* iOS Safari zooms the whole page when you focus a field whose font-size is
 * under 16px. That happens on the quote form (the entire revenue path), the
 * contact form and the visualiser's room picker. 16px is the floor on touch.
 *
 * !important is deliberate. Component rules like `.formcard .field input` and
 * `.preview select` are more specific than any plain `input` selector, and this
 * is a correctness rule about the browser zooming, not a style preference. No
 * component may opt out of it. */
@media (max-width: 1000px) {
  input:not([type="hidden"]), select, textarea { font-size: 16px !important; }
}

/* Thumb-sized controls. WCAG 2.5.5 asks for 44x44; links sitting inline in a
 * sentence are exempt and left alone. */
@media (max-width: 1000px) {
  .burger { padding: 12px 13px; min-width: 44px; min-height: 44px; }
  .wordmark { min-height: 44px; }
  .preview .toggle { min-height: 44px; padding: 12px 18px; }
  .preview select { min-height: 44px; }
  .chip { min-height: 44px; display: inline-flex; align-items: center; }
  .btn { min-height: 44px; display: inline-flex; align-items: center; justify-content: center; }
  /* .link-arrow is a standalone call to action, not prose: give it a real box. */
  .link-arrow { display: inline-flex; align-items: center; min-height: 44px; }
  .vis-swatches button, .preview .roomstrip button { min-height: 44px; }
}

/* The stage is letterboxed inside a 4:3 box. On a tall phone a shorter box wastes
 * less screen, and the room strip + bar still fit above the fold. */
@media (max-width: 700px) {
  .preview .stagebox { aspect-ratio: 3 / 2; }
  .preview .bar { padding: 12px; gap: 10px; }
  .preview .roomsel { flex: 1 1 auto; }
  .preview select { min-width: 0; width: 100%; }
  .preview .toggle { flex: 0 0 auto; }
}

/* Corner handles are dragged with a fingertip, not a 1px cursor. */
@media (pointer: coarse) {
  .preview .handle { width: 44px; height: 44px; margin: -22px 0 0 -22px; border-width: 3px; }
}

/* ── SA-5: Portfolio ─────────────────────────────────────────────────────────
   Theresa's call, 24 Jul 2026: the landing page should look like her WordPress
   one, which she prefers. She is right — an even wall of client marks says
   "these are the brands that trust us" faster than a mixed grid of photographs,
   and it is the layout her customers already recognise.

   So this is her page: a dark title band and one calm grid of white plates.
   The single thing it adds is that a plate whose brand has installation
   photography is a link to that case study, marked so you can tell before you
   tap. The photographs are one tap behind the logo, not gone.

   The bento grid and the separate credits wall that used to live here are
   replaced by this; the case-study cards they rendered are now reached through
   the plates. Project-detail styles below are unchanged. */

/* The title band. Hers is a dark textured strip with the page name centred. */
.pf-band {
  background: var(--ink); padding: 42px 0; text-align: center;
  background-image:
    radial-gradient(120% 140% at 50% 0%, rgba(255,255,255,0.07) 0%, rgba(255,255,255,0) 60%);
}
.pf-band h1 {
  color: var(--white); margin: 0; font-size: clamp(28px, 4.6vw, 46px); letter-spacing: 0.01em;
}

.pf-intro {
  max-width: 62ch; margin: 30px auto 0; text-align: center;
  font-size: 16px; line-height: 1.65; color: var(--ink-soft);
}
.pf-intro span { display: block; margin-top: 6px; font-size: 13px; color: var(--muted); }

.pf-more {
  max-width: 56ch; margin: 18px auto 0; text-align: center;
  font-size: 14px; line-height: 1.7; color: var(--muted);
}
.pf-more a { color: var(--brass); text-decoration: none; border-bottom: 1px solid var(--line); }
.pf-more a:hover { color: var(--ink); border-bottom-color: var(--brass); }

/* The wall. Two up on a phone, three from 760px — her desktop grid is three,
   and a 26-plate single column is a scroll nobody finishes. */
.pf-wall {
  display: grid; grid-template-columns: repeat(2, 1fr); gap: 14px;
  padding: 26px 0 10px;
}
.pf-plate {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 10px; aspect-ratio: 6 / 5; padding: 16px;
  background: var(--white); border: 1px solid var(--line-soft); border-radius: 2px;
  box-shadow: 0 2px 10px rgba(20,19,15,0.05);
  text-decoration: none; color: inherit;
  transition: box-shadow 0.22s ease, transform 0.22s ease, border-color 0.22s ease;
}
.pf-plate img {
  max-width: 84%; max-height: 62%; width: auto; height: auto; object-fit: contain;
}
/* The one client with no logo file anywhere on her site: set the name in the
   site serif rather than invent a mark. */
.pf-plate .name {
  font-family: var(--serif); font-size: 17px; text-align: center; color: var(--ink);
  line-height: 1.2;
}

/* A plate that leads somewhere has to say so WITHOUT hover, or on a phone it is
   indistinguishable from the 14 that do not (the SA-2/SA-5 lesson). The label is
   always rendered; hover only warms it up. */
.pf-plate.linked { border-color: var(--line); }
.pf-plate .see {
  font-family: var(--sans); font-size: 9px; letter-spacing: 0.18em;
  text-transform: uppercase; color: var(--brass);
  display: flex; align-items: center; gap: 6px;
}
.pf-plate .see::after {
  content: ""; width: 12px; height: 1px; background: var(--brass);
  transition: width 0.22s ease;
}

@media (hover: hover) {
  .pf-plate.linked:hover {
    box-shadow: 0 10px 26px rgba(20,19,15,0.13); transform: translateY(-3px);
    border-color: var(--brass);
  }
  .pf-plate.linked:hover .see::after { width: 22px; }
}
.pf-plate.linked:focus-visible { outline: 2px solid var(--brass); outline-offset: 3px; }

@media (min-width: 760px) {
  .pf-band { padding: 58px 0; }
  .pf-wall { grid-template-columns: repeat(3, 1fr); gap: 26px; padding: 34px 0 16px; }
  .pf-plate { padding: 30px; gap: 14px; }
  .pf-plate .see { font-size: 9.5px; }
  .pf-intro { margin-top: 40px; font-size: 17px; }
}
@media (min-width: 1100px) {
  .pf-wall { gap: 30px; }
  .pf-plate { padding: 38px; }
}

/* ── Project detail ── */
.pf-detail-hero { position: relative; background: var(--ink); }
.pf-detail-hero img { width: 100%; max-height: 74vh; object-fit: cover; display: block; opacity: 0.92; }
.pf-detail-hero .cap { position: absolute; left: 0; right: 0; bottom: 0; padding: 64px 0 34px;
  background: linear-gradient(to top, rgba(10,9,8,0.92) 0%, rgba(10,9,8,0.55) 40%, rgba(10,9,8,0) 100%); }
.pf-detail-hero h1 { color: var(--white); font-size: clamp(30px, 5vw, 58px); margin: 8px 0 0;
  text-shadow: 0 2px 14px rgba(0,0,0,0.5); }
.pf-detail-hero .client { color: #e8cf9a; font-size: 11px; letter-spacing: 0.2em;
  text-transform: uppercase; text-shadow: 0 1px 8px rgba(0,0,0,0.7); }
.pf-detail-logo {
  display: inline-flex; background: rgba(255,255,255,0.94); border-radius: 10px;
  padding: 10px 14px; margin-bottom: 16px; box-shadow: 0 3px 16px rgba(0,0,0,0.25);
}
.pf-detail-logo img { height: 44px; width: auto; max-width: 240px; object-fit: contain; display: block; }

.pf-facts { display: flex; flex-wrap: wrap; gap: 40px; padding: 34px 0; border-bottom: var(--rule); }
.pf-facts dt { font-size: 10px; letter-spacing: 0.17em; text-transform: uppercase; color: var(--ink-soft); }
.pf-facts dd { margin: 7px 0 0; font-family: var(--serif); font-size: 19px; }
.pf-body { max-width: 68ch; padding: 34px 0 8px; font-size: 17px; line-height: 1.72; color: var(--ink-soft); }

.pf-gallery { columns: 3 260px; column-gap: 16px; padding: 22px 0 40px; }
.pf-gallery button {
  display: block; width: 100%; margin: 0 0 16px; padding: 0; border: 0; background: none;
  cursor: zoom-in; break-inside: avoid; line-height: 0;
}
.pf-gallery img { width: 100%; border-radius: 3px; transition: opacity 0.2s; }
.pf-gallery button:hover img { opacity: 0.88; }

.pf-nextprev { display: flex; justify-content: space-between; gap: 16px; padding: 26px 0 60px; border-top: var(--rule); }
.pf-nextprev a { text-decoration: none; color: var(--ink); font-family: var(--serif); font-size: 19px; }
.pf-nextprev a small {
  display: block; font-family: var(--sans); font-size: 10px; letter-spacing: 0.16em;
  text-transform: uppercase; color: var(--ink-soft); margin-bottom: 5px;
}
.pf-nextprev .next { text-align: right; }
.pf-nextprev a:hover { color: var(--brass); }

/* Lightbox */
.pf-lb {
  position: fixed; inset: 0; background: rgba(10,9,8,0.94); display: none;
  align-items: center; justify-content: center; z-index: 200; padding: 3vh 3vw;
}
.pf-lb[open], .pf-lb.on { display: flex; }
.pf-lb img { max-width: 100%; max-height: 94vh; object-fit: contain; border-radius: 2px; }
.pf-lb .x, .pf-lb .nav {
  position: absolute; background: rgba(255,255,255,0.1); color: var(--white); border: 0;
  width: 48px; height: 48px; border-radius: 50%; cursor: pointer; font-size: 20px; line-height: 1;
}
.pf-lb .x { top: 18px; right: 18px; }
.pf-lb .nav.prev { left: 18px; top: 50%; transform: translateY(-50%); }
.pf-lb .nav.next { right: 18px; top: 50%; transform: translateY(-50%); }
.pf-lb .x:hover, .pf-lb .nav:hover { background: rgba(255,255,255,0.22); }

/* ── SA-5 mobile ─────────────────────────────────────────────────────────────
   The portfolio is what she sends people from her phone, so it is a phone page
   first. The landing page's own mobile layout now lives with the plate grid
   above (two up, then three from 760px); what remains here is the project
   DETAIL page, which is still where the photographs are.

   Historical note, because it is the rule that matters: the landing page used to
   put the client logo on a plate over the installation photo and fade it on
   HOVER. On a phone it never faded, so a white panel sat permanently across the
   middle of every photograph. Any reveal that depends on hover needs a touch
   layout designed on purpose — which is why the new plates label themselves
   "See the work" instead of waiting for a pointer. */

@media (max-width: 700px) {
  /* Detail page. The cover is landscape, so at 100% width on a phone it was a
     150px band with the caption crushed into it. Crop it to a real hero. */
  /* `>` matters: `.pf-detail-hero img` also matches the client logo inside the
     caption, and a min-height meant for the cover photo blew the logo up to
     half the screen (nothing else on the chip can override a min-height). */
  .pf-detail-hero > img { min-height: 54vh; max-height: 62vh; object-fit: cover; }
  .pf-detail-hero .cap { padding: 46px 0 22px; }
  .pf-detail-logo { padding: 8px 11px; margin-bottom: 12px; border-radius: 8px; }
  .pf-detail-logo img { height: 32px; max-width: 170px; }

  .pf-facts { display: grid; grid-template-columns: 1fr 1fr; gap: 20px 16px; padding: 26px 0; }
  .pf-facts dd { font-size: 17px; }
  .pf-body { font-size: 16.5px; padding: 26px 0 4px; }
  .pf-gallery { padding: 18px 0 30px; }

  /* Two long client names side by side crush at 390px. Stack them. */
  .pf-nextprev { flex-direction: column; gap: 0; padding: 20px 0 46px; }
  .pf-nextprev a { padding: 16px 0; font-size: 18px; }
  .pf-nextprev a + a { border-top: var(--rule); }
  .pf-nextprev .next { text-align: left; }

  /* Stacked, full-width CTAs beat two buttons squeezed onto one line. */
  .pf-cta { display: flex; flex-direction: column; gap: 10px; }
  .pf-cta .btn { width: 100%; }
}

/* The lightbox caption (SA-6: artwork titles). Sits under the photo on every
   width, above the arrow bar on a phone. */
.pf-lb .cap {
  color: var(--bone); font-family: var(--serif); font-size: 17px;
  margin: 16px 0 0; text-align: center; max-width: 60ch;
}

/* Lightbox on a phone: side arrows sat on top of the photo. Move them to a bar
   under it, thumb-height, and let the image have the screen. Swipe also works —
   see SmartArt.lightbox() in site.js. */
@media (max-width: 700px) {
  .pf-lb { padding: 0; flex-direction: column; justify-content: center; }
  .pf-lb img { max-height: 74vh; max-width: 94vw; }
  .pf-lb .x {
    top: max(12px, env(safe-area-inset-top)); right: 12px; width: 44px; height: 44px;
    background: rgba(255,255,255,0.16);
  }
  .pf-lb .nav { width: 56px; height: 56px; font-size: 24px; background: rgba(255,255,255,0.14); }
  /* Specificity: the desktop rules are `.pf-lb .nav.prev` (0,3,0), so top and
     transform have to be re-set on the same two-class selectors here — setting
     them on `.pf-lb .nav` alone loses and the arrows stay pinned mid-photo. */
  .pf-lb .nav.prev, .pf-lb .nav.next {
    top: auto; bottom: max(22px, env(safe-area-inset-bottom)); transform: none; right: auto;
  }
  .pf-lb .nav.prev { left: 50%; margin-left: -66px; }
  .pf-lb .nav.next { left: 50%; margin-left: 10px; }
  .pf-lb .cap { font-size: 15px; margin: 12px 16px 0; }
}

/* ── SA-6: Artists ───────────────────────────────────────────────────────────
   Her WordPress artist pages are a heading and a thumbnail grid. This is built
   the other way round: the work is the page, and the words are what her version
   is missing entirely.

   Written mobile-first this time, with the SA-5 lessons already applied: no
   hover-only reveals, 44px targets, nothing that needs a mouse to work. The
   desktop alternation is the ENHANCEMENT (it starts at 900px); the phone gets
   the plain stacked column, which is the right layout for it anyway. */

.ar-hero { padding: 34px 0 30px; border-bottom: var(--rule); }
.ar-hero h1 { font-size: clamp(36px, 6.2vw, 74px); margin: 12px 0 0; }
.ar-hero .lead {
  max-width: 60ch; margin: 16px 0 0; font-size: 16.5px;
  color: var(--ink-soft); line-height: 1.65;
}
.ar-stats { display: grid; grid-template-columns: 1fr 1fr; gap: 22px 14px; margin: 26px 0 0; }
.ar-stats b { display: block; font-family: var(--serif); font-size: 29px; line-height: 1; }
.ar-stats span {
  display: block; margin-top: 7px; font-size: 10px; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--ink-soft);
}

/* One artist on the index. Stacked on a phone; a two-column spread that
   alternates side to side from 900px up. */
.ar-row { display: grid; gap: 20px; padding: 34px 0; border-bottom: var(--rule); }
.ar-row:last-of-type { border-bottom: 0; }
.ar-row .shot {
  display: block; overflow: hidden; border-radius: 3px; background: var(--bone-2);
  aspect-ratio: 3 / 2;
}
.ar-row .shot img {
  width: 100%; height: 100%; object-fit: cover; display: block;
  transition: transform 0.7s cubic-bezier(.2,.7,.2,1);
}
.ar-row .copy { align-self: center; }
.ar-row h2 { font-size: clamp(28px, 5vw, 44px); margin: 12px 0 0; }
.ar-row h2 a { text-decoration: none; }
.ar-row .meta {
  display: flex; flex-wrap: wrap; gap: 6px 14px; margin: 12px 0 0;
  font-size: 11px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--muted);
}
.ar-row .meta span + span::before { content: "· "; color: var(--brass); }
.ar-row .tagline {
  margin: 14px 0 18px; font-size: 16.5px; line-height: 1.6; color: var(--ink-soft);
  max-width: 46ch;
}

/* Rights badge. The whole point of the page: two of these are free to print. */
.ar-badge {
  display: inline-flex; align-items: center; gap: 7px;
  font-size: 10px; letter-spacing: 0.16em; text-transform: uppercase;
  padding: 7px 12px; border-radius: 999px; border: 1px solid var(--line);
  color: var(--ink-soft); background: var(--bone-2);
}
.ar-badge::before {
  content: ""; width: 6px; height: 6px; border-radius: 50%; background: var(--brass);
}
.ar-badge.pd { border-color: #bcd0b6; background: #eef3ea; color: #40563a; }
.ar-badge.pd::before { background: #6f9163; }
.ar-badge.lic { border-color: #d8c69c; background: #f8f2e4; color: #6b5525; }
.ar-badge.lic::before { background: var(--brass); }

.ar-explain { border-top: var(--rule); background: var(--bone-2); padding: 44px 0; margin-top: 20px; }
.ar-explain .cols { display: grid; gap: 32px; }
.ar-explain h3 { font-size: 25px; margin: 10px 0 10px; }
.ar-explain p { color: var(--ink-soft); font-size: 16px; line-height: 1.68; margin: 0; max-width: 52ch; }

/* ── One artist ── */
.ar-detail-hero { position: relative; background: var(--ink); }
.ar-detail-hero img {
  width: 100%; min-height: 54vh; max-height: 62vh; object-fit: cover;
  display: block; opacity: 0.94;
}
.ar-detail-hero .cap {
  position: absolute; left: 0; right: 0; bottom: 0; padding: 46px 0 22px;
  background: linear-gradient(to top, rgba(10,9,8,0.94) 0%, rgba(10,9,8,0.55) 45%, rgba(10,9,8,0) 100%);
}
.ar-detail-hero .crumb { margin: 0 0 10px; }
.ar-detail-hero .crumb a {
  color: #e8cf9a; font-size: 11px; letter-spacing: 0.2em; text-transform: uppercase;
  text-decoration: none; text-shadow: 0 1px 8px rgba(0,0,0,0.7);
}
.ar-detail-hero h1 {
  color: var(--white); font-size: clamp(32px, 5.4vw, 62px); margin: 0;
  text-shadow: 0 2px 16px rgba(0,0,0,0.5);
}
.ar-detail-hero .meta {
  display: flex; flex-wrap: wrap; gap: 6px 14px; margin: 12px 0 0;
  font-size: 10.5px; letter-spacing: 0.16em; text-transform: uppercase;
  color: #ddd7ca; text-shadow: 0 1px 8px rgba(0,0,0,0.6);
}
.ar-detail-hero .meta span + span::before { content: "· "; color: var(--brass-lit); }

.ar-lede { display: grid; gap: 30px; padding: 30px 0 6px; }
.ar-lede .standfirst {
  font-family: var(--serif); font-size: 21px; line-height: 1.45; color: var(--ink);
  margin: 0 0 18px;
}
.ar-lede .bio p { font-size: 16.5px; line-height: 1.75; color: var(--ink-soft); margin: 0 0 16px; }
.ar-lede .side { border-top: var(--rule); padding-top: 22px; }
.ar-lede .side .rights {
  font-size: 13px; line-height: 1.6; color: var(--muted); margin: 14px 0 0; max-width: 44ch;
}
.ar-lede .side dl { margin: 20px 0 22px; }
.ar-lede .side dl > div {
  display: flex; justify-content: space-between; gap: 16px;
  padding: 11px 0; border-bottom: var(--rule);
}
.ar-lede .side dt {
  font-size: 10px; letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--muted); padding-top: 4px;
}
.ar-lede .side dd { margin: 0; font-family: var(--serif); font-size: 17px; text-align: right; }

.ar-note {
  margin: 26px 0 0; padding: 22px 0 0; border-top: var(--rule);
  font-family: var(--serif); font-size: 19px; line-height: 1.55; color: var(--ink);
  max-width: 62ch;
}
.ar-note::before {
  content: ""; display: block; width: 46px; height: 1px;
  background: var(--brass); margin-bottom: 18px;
}

.ar-works-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 16px; margin: 40px 0 4px;
}
.ar-works-head h2 { font-size: clamp(24px, 3vw, 34px); margin: 0; }
.ar-works-head .hint { font-size: 12px; color: var(--muted); margin: 0; }

/* The pieces. A masonry column flow: her images are a mix of 4:3 rooms, squares
   and tall studio shots, and a fixed-ratio grid would crop every one of them. */
.ar-gallery { columns: 2 200px; column-gap: 14px; padding: 18px 0 10px; }
/* A long set goes two-up on a phone. `columns: 2 200px` means "2 columns, but
   only if each can be 200px", and 200 does not fit twice in a 342px phone
   column — so a big set falls to one image per screen. 148px does fit. */
.ar-gallery.dense { columns: 2 148px; column-gap: 10px; }
.ar-gallery.dense button { margin-bottom: 10px; }
.ar-gallery.dense .t { font-size: 12px; padding: 20px 9px 8px; }
.ar-gallery button {
  display: block; width: 100%; margin: 0 0 14px; padding: 0; border: 0;
  background: none; cursor: zoom-in; break-inside: avoid; position: relative;
  line-height: 0; border-radius: 3px; overflow: hidden;
}
.ar-gallery img {
  width: 100%; height: auto; display: block;
  transition: transform 0.6s cubic-bezier(.2,.7,.2,1);
}
/* The title sits on the image with its own scrim: her mockups are bright rooms,
   so plain text on top would vanish (the SA-5 caption lesson). */
.ar-gallery .t {
  position: absolute; left: 0; right: 0; bottom: 0; padding: 26px 12px 10px;
  font-family: var(--serif); font-size: 14px; line-height: 1.25; color: var(--white);
  text-align: left; text-shadow: 0 1px 8px rgba(0,0,0,0.6);
  background: linear-gradient(to top, rgba(12,11,9,0.82) 0%, rgba(12,11,9,0) 100%);
}

.ar-others { border-top: var(--rule); margin-top: 34px; padding: 26px 0 46px; }
.ar-others .row { display: grid; gap: 12px; margin-top: 16px; }
.ar-others a {
  display: flex; align-items: center; gap: 14px; text-decoration: none;
  padding: 10px; border: var(--rule); border-radius: 3px; background: var(--bone-2);
  min-height: 44px;
}
.ar-others img { width: 74px; height: 56px; object-fit: cover; border-radius: 2px; flex: 0 0 auto; }
.ar-others b { display: block; font-family: var(--serif); font-weight: 400; font-size: 17px; }
.ar-others small {
  display: block; font-size: 10px; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--muted); margin-top: 3px;
}

@media (hover: hover) {
  .ar-row .shot:hover img { transform: scale(1.04); }
  .ar-gallery button:hover img { transform: scale(1.035); }
  .ar-others a:hover { border-color: var(--brass); }
}

@media (min-width: 900px) {
  .ar-hero { padding: 62px 0 40px; }
  .ar-hero .lead { font-size: 17px; margin-top: 18px; }
  .ar-stats { display: flex; flex-wrap: wrap; gap: 34px; margin-top: 34px; }
  .ar-stats div { min-width: 96px; }
  .ar-stats b { font-size: 34px; }

  /* The alternating spread. Every second row swaps sides by reordering the grid
     children, so the markup order stays sensible for a screen reader and for
     the phone, where the image must always come first. */
  .ar-row { grid-template-columns: 1.15fr 1fr; gap: 46px; padding: 54px 0; align-items: center; }
  .ar-row:nth-of-type(even) .shot { order: 2; }
  .ar-row .tagline { font-size: 17px; }

  .ar-explain { padding: 60px 0; }
  .ar-explain .cols { grid-template-columns: 1fr 1fr; gap: 54px; }

  .ar-lede { grid-template-columns: 1.5fr 1fr; gap: 56px; padding: 44px 0 10px; }
  .ar-lede .standfirst { font-size: 24px; }
  .ar-lede .bio p { font-size: 17px; }
  .ar-lede .side { border-top: 0; padding-top: 6px; }

  .ar-note { font-size: 21px; margin-top: 34px; }
  .ar-gallery { columns: 3 260px; column-gap: 16px; }
  .ar-gallery button { margin-bottom: 16px; }
  .ar-gallery .t { font-size: 15px; padding: 30px 16px 13px; }
  .ar-others .row { grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); }
}


/* ===================== SA-7: the customer's own wall ===================== */
/* Two pages: /my-room (take the photo) and /my-room/{token} (try designs on it).
   The visualiser itself is the existing .preview component — nothing here
   restyles it, so the two stay in step. */

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
/* A camera button is meaningless on a desktop: `capture` there just opens the
   same file picker the other button already opened. */
.only-touch { display: none; }
@media (hover: none) and (pointer: coarse) { .only-touch { display: inline-flex; } }

.wall-upload { max-width: 720px; margin: 0 auto 56px; }

.wall-drop {
  border: 2px dashed var(--line); border-radius: 3px; background: var(--bone-2);
  padding: 56px 28px; text-align: center; transition: border-color 0.2s, background 0.2s;
}
.wall-drop.over { border-color: var(--brass); background: var(--white); }
.wall-drop h3 { font-size: 22px; margin: 18px 0 6px; }
.wall-icon { width: 54px; height: 54px; color: var(--brass); }
.wall-hint { color: var(--muted); font-size: 13.5px; margin: 0; }
.wall-actions {
  display: flex; gap: 12px; justify-content: center; flex-wrap: wrap; margin-top: 26px;
}

.wall-busy { text-align: center; padding: 64px 20px; }
.wall-busy p { margin: 0 0 6px; }
.spin {
  width: 34px; height: 34px; margin: 0 auto 22px; border-radius: 50%;
  border: 2px solid var(--line); border-top-color: var(--brass);
  animation: wall-spin 0.9s linear infinite;
}
@keyframes wall-spin { to { transform: rotate(360deg); } }
/* Respect the OS setting: a spinner is decoration, and for some people it is
   a headache. Pulse instead of rotate. */
@media (prefers-reduced-motion: reduce) {
  .spin { animation: wall-pulse 1.4s ease-in-out infinite; }
  @keyframes wall-pulse { 50% { opacity: 0.35; } }
}

.wall-error {
  margin: 22px 0 0; padding: 14px 16px; border-radius: 2px;
  background: #fdf2f0; border: 1px solid #e6c4bd; color: #8a3122; font-size: 14.5px;
}

.wall-tips {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 30px;
  border-top: var(--rule); padding-top: 34px;
}
.wall-tips h4 { font-size: 16px; margin: 0 0 8px; }
.wall-tips p { color: var(--muted); font-size: 14.5px; margin: 0; }
.wall-privacy {
  color: var(--muted); font-size: 13px; margin: 34px 0 0; padding-top: 22px;
  border-top: 1px solid var(--line-soft);
}

/* ── the wall's own page ── */
.wall-status { font-size: 16px; }
.wall-status.ok::before,
.wall-status.check::before {
  content: ""; display: inline-block; width: 8px; height: 8px; border-radius: 50%;
  margin-right: 9px; vertical-align: 0.12em;
}
.wall-status.ok::before { background: var(--brass); }
.wall-status.check::before { background: #c98a2e; }

.wall-bar { margin-top: 20px; max-width: 400px; }
.wall-bar-row { display: flex; gap: 10px; margin-top: 10px; }
.wall-bar-row .btn { flex: 1 1 0; text-align: center; }
.wall-saved {
  margin: 12px 0 0; font-size: 12.5px; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--brass);
}

.wall-coll {
  width: 100%; margin-bottom: 14px; padding: 10px 12px; font-family: var(--sans);
  font-size: 14px; color: var(--ink); background: var(--white);
  border: 1px solid var(--line); border-radius: 2px;
}

/* 267 swatches in a 240px rail is a mile of page. Cap it and scroll inside —
   the picture must stay on screen while they browse, or the picker is useless.
   Applies to /visualiser as well as /my-room: the catalogue grew from 4 designs
   to 267 in SA-3 and that page was never revisited, so it had the same defect. */
body[data-page="my-room"] .vis-swatches,
body[data-page="visualiser"] .vis-swatches {
  max-height: min(62vh, 620px); overflow-y: auto; padding-right: 4px;
}

@media (max-width: 900px) {
  .wall-tips { grid-template-columns: 1fr; gap: 22px; }
  .wall-drop { padding: 40px 20px; }
  .wall-bar, .wall-bar-row .btn { max-width: none; }

  /* 267 swatches stacked under the wall is a 10,000px page, and the wall — the
     only thing on the screen that matters — scrolls away on the first flick.
     Two rows that scroll sideways instead: the picture stays put while you
     thumb through the catalogue, which is the way this gets used, standing in
     the room. Same lesson as the SA-6 artist galleries, reached the other way
     round: there the fix was to pack a column, here it is to stop having one. */
  body[data-page="my-room"] .vis-swatches,
  body[data-page="visualiser"] .vis-swatches {
    max-height: none;
    grid-template-columns: none;
    grid-auto-flow: column;
    grid-template-rows: repeat(2, auto);
    grid-auto-columns: 104px;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scroll-snap-type: x proximity;
    padding: 0 0 8px;
    -webkit-overflow-scrolling: touch;
  }
  body[data-page="my-room"] .vis-swatches button,
  body[data-page="visualiser"] .vis-swatches button { scroll-snap-align: start; }
}

/* A wide/panorama upload takes in several walls and we can only paper one, so
   the design lands on a slice. Measured on the first real uploads: normal 16:9
   shots gave 69-78% wall coverage, 2.16:1 panoramas gave 26-38%. Tell them,
   rather than let them conclude the tool is broken. */
.wall-warn {
  margin: 14px 0 0; padding: 13px 16px; border-radius: 2px; font-size: 14.5px;
  background: #fdf7ec; border: 1px solid #e3d0a8; color: #6b5322;
}
.wall-warn a { color: #6b5322; text-decoration: underline; white-space: nowrap; }

/* While a newly chosen design is still downloading the old one stays on the
   wall (see setDesign in preview.js). This is the only hint that anything is
   happening — deliberately faint, because the common case is a cached image
   that swaps instantly and should not flash a spinner at anyone. */
.preview.swapping .stagebox::after {
  content: ""; position: absolute; left: 0; right: 0; top: 0; height: 2px;
  background: linear-gradient(90deg, transparent, var(--brass-lit), transparent);
  animation: wall-swap 1s ease-in-out infinite; pointer-events: none; z-index: 5;
}
@keyframes wall-swap { 0%, 100% { opacity: 0.25; } 50% { opacity: 1; } }
@media (prefers-reduced-motion: reduce) {
  .preview.swapping .stagebox::after { animation: none; opacity: 0.7; }
}

/* ---- canvas stage (SA-7) ----
   One bitmap instead of a stack of blended layers. The <img>/.pattern elements
   still exist so the rest of preview.js does not have to branch, but nothing
   paints them: the canvas is the picture. The outline and handles stay as DOM
   on top, since they are not blended and drag needs real hit targets. */
.preview .wallcanvas {
  position: absolute; inset: 0; width: 100%; height: 100%;
  display: block; z-index: 1;
}
.preview.canvasmode .stage > img.layer,
.preview.canvasmode .stage > .pattern { display: none; }
/* The stage keeps its size and transform so the SVG outline and the handles
   still land in room coordinates — only its raster children are hidden. */
.preview.canvasmode .stage { background: transparent; z-index: 2; }
/* .stage has a transform, so it opens its own stacking context and competes
   with the canvas as a sibling. Without the z-index above it is `auto` and the
   canvas (z-index 1) paints over the outline and the drag handles — they are
   still there and still take pointer events, just invisible, which is a
   maddening thing to debug. */
