:root {
  --paper: #f8f7f4;
  --ink: #12110f;
  --body: #3c3a37;
  --soft: #8b8880;
  --faint: #b9b6af;
  --line: #e8e5df;
  --accent: #8a5a2b;
  --flag: #b23b2e;         /* the one red on the page: something is missing here and nothing goes on until it is said */
  --lift: 0 10px 30px rgba(18, 17, 15, .10);
  --gut: 22px;             /* the paper each row of pictures keeps to itself, left and right */
  /* The page is paper and ink and has no dark half. Said outright, because the parts of it the browser
     draws itself — the room dropdown chief among them — read the machine's setting instead: on a Mac in
     dark mode that list came down as a grey slab of its own, in another palette entirely, over a page
     that never changed. Declared light, every native control is drawn in the one the page is in. */
  color-scheme: light;
}

* { box-sizing: border-box; margin: 0; padding: 0; }
/* a sheet is open over everything: the page under it holds still */
html.locked { overflow: hidden; }
/* The page scrolls; it does not say so in furniture. A bar of grey down the right of every screen is
   the browser's chrome standing on the work — the same reason the rows of pictures carry none. */
html { scrollbar-width: none; -ms-overflow-style: none; }
/* The paper is on the canvas itself, not only on the body box, and the body is never shorter than the
   window: a page that stops short of the window leaves a band of the browser's own grey down the right
   of it or under it, and the work is meant to sit on paper edge to edge. */
html { background: var(--paper); }
body { min-height: 100vh; }
html::-webkit-scrollbar, body::-webkit-scrollbar { width: 0; height: 0; display: none; }
[hidden] { display: none !important; }

body {
  background: var(--paper);
  color: var(--ink);
  font: 400 16px/1.6 Poppins, system-ui, sans-serif;
  -webkit-font-smoothing: antialiased;
}

/* ── header: name left, where you are centre, you right ── */
/* The page is held off the edge by about the width of its own corner radius — no more. The work is
   the widest thing on it and every point given back to a margin is taken off the pictures. */
.topbar, main { max-width: none; margin: 0 auto; padding-left: 26px; padding-right: 26px; }
.topbar { display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 16px; padding-top: 22px; padding-bottom: 34px; }
.logo { background: none; border: 0; text-decoration: none; font: inherit; font-weight: 600; font-size: 17px; letter-spacing: -.01em; color: var(--ink); cursor: pointer; justify-self: start; padding: 0; }
.topbar:has(.who) { grid-template-columns: 1fr auto; }
/* the job you are in; the rest of them are a hover away */
.picker { position: relative; display: inline-block; }
.picker > button { text-transform: capitalize; color: var(--ink); }
/* what the job has cost so far, on the job's own name: a running figure, so it is set in the numerals
   that do not jump about as it climbs, and held back a shade — it is a note on the name, not the name */
.picker > button .spent { margin-left: 7px; font-style: normal; font-size: 12px; font-variant-numeric: tabular-nums; color: var(--soft); }
.picker .menu { position: absolute; right: 0; top: 100%; z-index: 20; display: grid; min-width: 12rem; padding: 8px; background: #fff; border: 1px solid var(--line); border-radius: 14px; box-shadow: var(--lift); opacity: 0; pointer-events: none; transform: translateY(-4px); transition: opacity .14s ease, transform .14s ease; }
.picker:hover .menu, .picker.open .menu { opacity: 1; pointer-events: auto; transform: none; }
.picker .menu button { width: 100%; padding: 8px 10px; border-radius: 10px; text-align: left; text-decoration: none; text-transform: capitalize; color: var(--body); }
.picker .menu button:hover { background: var(--paper); color: var(--ink); }
.picker .menu .new { color: var(--accent); text-transform: none; }
/* One job to a row: its name, and the cross that throws it away standing on the right-hand end of the
   same row. The row is the thing pointed at, so the name lights up under the pointer whichever half of
   it the pointer is over — and the cross appears only then, because a menu with a cross on every line
   of it reads as a list of things to delete rather than a list of jobs to open. */
.picker .menu .job { position: relative; display: flex; align-items: center; border-radius: 10px; }
.picker .menu .job:hover { background: var(--paper); }
.picker .menu .job > button { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.picker .menu .job:hover > button { color: var(--ink); }
.picker .menu .job > button:hover { background: none; }
/* The job they are standing in, said the way the page says everything else: by weight, not by a badge. */
.picker .menu .job.on > button { color: var(--ink); font-weight: 600; }
.picker .menu .job .x { flex: none; width: auto; padding: 6px 9px; margin-right: 2px; border-radius: 8px; color: var(--faint); font-size: 12px; line-height: 1; opacity: 0; transition: opacity .12s, color .12s, background .12s; }
.picker .menu .job:hover .x, .picker .menu .job .x:focus-visible { opacity: 1; }
.picker .menu .job .x:hover { background: rgba(178, 59, 46, .1); color: var(--flag); }
/* …and on a screen with no pointer there is no hover to reveal it, so it simply stands there. */
@media (hover: none) { .picker .menu .job .x { opacity: 1; } }
.topbar nav { display: flex; gap: 26px; justify-self: center; }
.topbar nav a { color: var(--soft); text-decoration: none; font-size: 15px; }
.topbar nav a:hover { color: var(--ink); }
.topbar > .btn { justify-self: end; }
.who { justify-self: end; font-size: 14px; color: var(--soft); }
.who button { background: none; border: 0; font: inherit; font-size: 14px; color: var(--soft); cursor: pointer; text-decoration: underline; }
.who button:hover { color: var(--ink); }

main { padding-bottom: 40px; }
.stage.centre { text-align: center; }

/* ── type ─────────────────────────────────────────────── */
/* One title, one size, one weight, wherever it stands: the homepage, a job, the plan, the order. A
   page that changes the size of its own heading reads as a different site each time. */
h1, .home h1, h1.hero { font-size: clamp(30px, 4.2vw, 54px); font-weight: 600; letter-spacing: -.02em; line-height: 1.15; margin-bottom: 12px; }
h1.left { text-align: left; }
/* and under every one of them, the line that says what the page is for */
.lede { font-size: clamp(16px, 1.4vw, 19px); color: var(--soft); max-width: 46rem; margin-bottom: 34px; }
.lede.mid { margin-left: auto; margin-right: auto; text-align: center; }
.stage.centre .lede { margin-left: auto; margin-right: auto; }
.note { font-size: 14px; color: var(--soft); margin-top: 18px; }
.note.mid { max-width: 34rem; margin-left: auto; margin-right: auto; }
.link { background: none; border: 0; font: inherit; font-size: 14px; color: var(--ink); cursor: pointer; text-decoration: underline; padding: 0; }
.link:disabled { color: var(--faint); text-decoration: none; cursor: default; }
.crumb { margin-bottom: 10px; }
.crumb button { background: none; border: 0; font: inherit; font-size: 14px; color: var(--soft); cursor: pointer; }
.crumb button:hover { color: var(--ink); }
.err { color: #9b2c1f; background: #fdeeea; border-radius: 14px; padding: 14px 18px; margin-top: 26px; font-size: 15px; display: inline-block; }

input[type=text] { font: inherit; font-size: 16px; padding: 15px 18px; border: 1px solid var(--line); border-radius: 14px; background: none; color: var(--ink); }
input[type=text]:focus { outline: 2px solid rgba(138, 90, 43, .25); }
input[type=text].inline { border: 0; border-bottom: 2px solid var(--ink); border-radius: 0; padding: 0 4px 2px; background: none; font: inherit; }
input[type=text].inline:focus { outline: 0; border-bottom-color: var(--accent); }

/* ── buttons ──────────────────────────────────────────── */
.btn { display: inline-flex; align-items: center; justify-content: center; gap: 10px; background: var(--ink); color: #f4efe6; border: 0; border-radius: 14px; padding: 14px 24px; font: inherit; font-size: 15px; font-weight: 500; cursor: pointer; text-decoration: none; transition: transform .12s ease, opacity .12s ease; }
.btn:hover { transform: translateY(-1px); }
.btn:disabled { background: var(--faint); cursor: default; transform: none; }
.btn.ghost { background: none; color: var(--ink); text-decoration: underline; padding-left: 6px; padding-right: 6px; }
.btn.small { padding: 10px 20px; font-size: 14px; }
.btn.big { padding: 16px 28px; font-size: 16px; }
/* the second call on a page that already has a black one: the same weight, drawn rather than filled */
.btn.line { background: none; color: var(--ink); box-shadow: inset 0 0 0 1.5px var(--ink); }
.btn.line:disabled { color: var(--faint); box-shadow: inset 0 0 0 1.5px var(--line); background: none; }
/* A share is invisible work: pressed, the button says what it did, because a button that shows nothing
   has done nothing as far as anyone pressing it can tell. */
.btn.line.said { background: var(--ink); box-shadow: none; color: #f4efe6; }
/* the mark on its own: square, so it is a button the size of its glyph and not a pill of empty room */
.btn.icon { padding: 14px; gap: 0; }
/* The broom, said on the button that swings it and not only in the word beside it — drawn on the paper
   itself, not on a white card laid over it, and the mark set at the size of the type it stands next
   to. */
.btn.broomed svg { width: 18px; height: 18px; fill: currentColor; }

/* ── cards: jobs, then the rooms inside one ───────────── */
.cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(12.75rem, 1fr)); gap: 17px; margin-top: 22px; }
.card { position: relative; }
.card .open { display: grid; gap: 4px; width: 100%; text-align: left; background: none; border: 0; padding: 0; font: inherit; cursor: pointer; }
.card img, .card .blank { width: 100%; aspect-ratio: 4/3; object-fit: cover; border-radius: 15px; display: block; background: #efece6; margin-bottom: 9px; transition: transform .18s ease, box-shadow .18s ease; }
.card .open:hover img, .card .open:hover .blank { transform: translateY(-3px); box-shadow: var(--lift); }
.card b { font-size: 14px; font-weight: 500; text-transform: capitalize; }
.card small { font-size: 12px; color: var(--soft); }
.card .x { position: absolute; top: 10px; right: 10px; width: 28px; height: 28px; border: 0; border-radius: 99px; background: rgba(18,17,15,.7); color: #f4efe6; font-size: 13px; cursor: pointer; opacity: 0; transition: opacity .15s; }
.card:hover .x { opacity: 1; }
.card.new { display: grid; place-content: center; gap: 5px; aspect-ratio: 4/3; border: 1px dashed var(--faint); border-radius: 15px; background: none; font: inherit; color: var(--soft); cursor: pointer; text-transform: capitalize; align-self: start; }
.card.new b { font-size: 22px; font-weight: 400; }
.card.new:hover { border-color: var(--accent); color: var(--accent); background: rgba(18,17,15,.03); }

/* ── the job: the picture, its photos beneath, the looks beside ── */
/* The whole job fits the screen: the picture takes what is left after the words and the bar. */
/* The room is said the way the homepage says the product: one big line, the pictures inside it. */
h1.hero { text-align: center; text-transform: capitalize; margin: 0 auto 12px; }
/* Headlines are words. The little pictures set into them were charming on the way in and noise on
   every page after it — a photograph of the room a foot below says the same thing better. */
/* the line under the job's own title has the room the title used to take */
h1.hero + .lede { margin-bottom: 34px; }
/* Their photos one under the other, each row a whole page wide now the rail is gone. */
.job { display: grid; grid-template-columns: minmax(0, 1fr); gap: 22px; align-items: start; }
/* One look open: the render and its elements travel together, centred, nothing else on the screen. */
.job:has(.pair.one) { grid-template-columns: max-content 25.5rem; justify-content: center; }
.side h2 { margin-top: 0; }
/* One row per photo, one under the other — the column scrolls with the page, so ten photos read as
   a stack of ten rooms rather than a picker with nine of them hidden. */
.show { position: relative; display: grid; gap: 18px; justify-items: stretch; align-content: start; }

/* ── another photo, from the foot of the stack ─────────── */
.show > .add { display: grid; place-content: center; gap: 2px; justify-self: center; width: min(100%, 26rem); padding: 20px; border: 1px dashed var(--faint); border-radius: 16px; background: none; font: inherit; font-size: 13px; color: var(--soft); cursor: pointer; }
.show > .add b { font-size: 20px; font-weight: 400; }
.show > .add:hover { border-color: var(--accent); color: var(--accent); background: rgba(18,17,15,.03); }
/* the camera, beside the camera roll: drawn rather than dashed, because on a phone standing in the
   room it is the likelier of the two and should not read as the afterthought under it */
.show > .add.snap { border-style: solid; border-color: var(--line); color: var(--ink); }
.show > .add.snap b { display: grid; place-items: center; justify-self: center; }
.show > .add.snap svg { width: 22px; height: 22px; fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linejoin: round; }
.show > .add.snap:disabled { opacity: .5; cursor: default; }

/* the live compare is dragged, never animated */
/* the same frame the side-by-side panels get, measured the same way — one picture instead of two */
.ba.live { justify-self: center; height: var(--h, 300px); aspect-ratio: var(--ar, 4/3); width: auto; max-width: 100%; border-radius: 18px; cursor: ew-resize; touch-action: none; }
.ba.live .before { clip-path: inset(0 calc(100% - var(--x, 50%)) 0 0); animation: none; }
.ba.live .wiper { left: var(--x, 50%); animation: none; }
/* a row being drawn, compared: a ring on the picture, which stays theirs to drag while it draws */
.ba.live .drawing { position: absolute; left: 50%; top: 50%; z-index: 3; display: grid; place-items: center; width: 48px; height: 48px; margin: -24px 0 0 -24px; border-radius: 99px; background: rgba(255,255,255,.88); box-shadow: 0 2px 10px rgba(18,17,15,.18); }
.ba.live .drawing .ring { width: 24px; height: 24px; border: 3px solid rgba(18,17,15,.2); border-top-color: var(--ink); border-radius: 99px; animation: spin 900ms linear infinite; }
.ba.live .wiper::after { content: ''; position: absolute; top: 50%; left: 50%; width: 34px; height: 34px; transform: translate(-50%, -50%); border-radius: 99px; background: rgba(255,255,255,.92); box-shadow: 0 2px 10px rgba(18,17,15,.25); }

.mid { display: grid; justify-self: center; justify-items: center; gap: 9px; }
/* The two ways to look, drawn rather than spelt, standing under the subtitle in the middle of the
   page: it is the only control on this screen, so it is where the eye already is. */
.ways { display: flex; justify-content: center; align-items: center; gap: 10px; margin-bottom: 30px; }
.lede:has(+ .ways) { margin-bottom: 14px; }
.modes { display: inline-flex; gap: 2px; background: none; border: 1px solid var(--line); border-radius: 99px; padding: 2px; vertical-align: middle; }
.modes svg { display: block; width: 17px; height: 17px; fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linejoin: round; }
/* one side of the compare frame is solid: that is the wipe, said in a picture the size of a full stop */
.modes .half { fill: currentColor; stroke: none; }
/* the stages stand in the middle of the header whatever else is up there, so they are in the same
   place on every page — the name and the way out keep the column on the right */
.topbar:has(.mid) { grid-template-columns: 1fr auto 1fr; }

/* ── where in the job they are, across the top of every page ── */
/* The stages of a job in order, each of them the way to itself: a stage with nothing in it yet
   is greyed rather than missing, so what is coming is never a surprise, and the ones behind them stay
   lit — walking back to the photos is a click, not a hunt through the menu. */
.stages { display: flex; align-items: center; gap: 0; list-style: none; }
.stages li { display: flex; align-items: center; }
/* The rule between two stages keeps clear of both of them. Run right up to the buttons it reads as
   touching the stage they are on — that one is a filled pill, so the line met its rounded edge and
   looked like it was growing out of it rather than joining it to the next. */
.stages li + li::before { content: ''; width: 18px; height: 1px; margin: 0 8px; background: var(--line); }
/* Every stage is a word somebody has to be able to read, whether or not it can be pressed yet. A
   stage nothing has reached was painted in the hairline grey the rules are drawn in — which on this
   paper is barely a mark at all, so the last stage of the job read as a gap in the bar rather than as
   the work still to come. Greyed is one step lighter than the rest, never invisible. */
.stages button { background: none; border: 0; font: inherit; font-size: 13px; color: var(--soft); cursor: pointer; padding: 5px 9px; border-radius: 99px; white-space: nowrap; }
/* pointing at a stage darkens the word, nothing more: a white pill beside the black one read as a
   second stage you were also on */
.stages button:hover:not(:disabled) { color: var(--ink); }
.stages button:disabled { cursor: default; color: #9a978f; }
.stages li.done + li::before, .stages li.on + li::before { background: var(--faint); }
.stages li.on button { color: #f4efe6; background: var(--ink); font-weight: 500; }
.stages li.on button:hover { color: #f4efe6; background: var(--ink); }
.modes button { background: none; border: 0; border-radius: 99px; font: inherit; font-size: 13px; color: var(--soft); padding: 7px 15px; cursor: pointer; }
.modes button.on { background: var(--ink); color: #f4efe6; }

/* before, then one panel per look they ticked */
/* Columns sized to the PHOTO, not to the row: a portrait shot in a 1fr column left a slab of grey
   either side of it. Every panel shares one height, so they still line up for comparison. */
/* How many across and how tall is measured, not guessed — see layout() — so the panels fill the room
   left beside the looks whatever shape the photo is and however many looks are ticked. */
/* A gutter each side, so a row never runs flush into the edge of the page: the panels start inside it,
   the row scrolls to a stop inside it, and the mask keeps those two strips empty paper — a render
   sliding past is gone before it reaches them rather than half-drawn against the margin. */
/* Nothing snaps: where the row is left is where the row stays — a pull back to the nearest panel took
   the scroll off them the moment they stopped pushing it. */
.pair { display: grid; grid-auto-flow: column; grid-auto-columns: max-content; justify-content: safe center; gap: 10px; width: 100%; overflow-x: auto; overscroll-behavior-x: none; padding: 0 var(--gut) 4px;
  -webkit-mask-image: linear-gradient(to right, transparent var(--gut), #000 var(--gut), #000 calc(100% - var(--gut)), transparent calc(100% - var(--gut)));
  mask-image: linear-gradient(to right, transparent var(--gut), #000 var(--gut), #000 calc(100% - var(--gut)), transparent calc(100% - var(--gut))); }
/* the rows are dragged and wiped, never read off a bar — and a scrollbar under every photo made the
   stack a page of grey rules. The row still scrolls; it just does not say so in furniture. */
.pair, .strip, .show { scrollbar-width: none; -ms-overflow-style: none; }
.pair::-webkit-scrollbar, .strip::-webkit-scrollbar, .show::-webkit-scrollbar { width: 0; height: 0; display: none; }
/* The room as it stands stays where it is: the renders slide past it rather than carrying it off the
   left edge, and a hairline in the gap says where the photograph ends and the drawings begin. */
/* The photograph is given twice the air the renders have between them, and the line standing in it is
   drawn twice as heavy: it is the seam between what the room is and what it could be, not a gap.
   Heavier again, and standing further off: at two pixels a hairline twenty across from the picture
   read as the edge of something rather than as a seam drawn on purpose, and the room and the first
   drawing of it sat close enough to be taken for one row of pictures. */
.pair:not(.one) > figure:first-child { position: sticky; left: 0; z-index: 3; margin-right: 56px; background: var(--paper); box-shadow: 66px 0 0 var(--paper); }
.pair:not(.one) > figure:first-child::after { content: ''; position: absolute; top: 12.5%; bottom: 12.5%; right: -40px; width: 4px; background: var(--faint); }
/* The photograph is the room; the drawings of it are held a little smaller, so the eye goes to the
   one that is real and the rest read as proposals beside it. */
.pair:not(.one) > figure:not(:first-child) { height: calc(var(--h, 300px) * .9); align-self: center; }
/* Every panel is the shape of the photo itself, and only as wide as that shape needs — columns sized
   to the picture keep the panels shoulder to shoulder instead of adrift in half a screen each. The
   height also gives way as more looks are ticked, so the row never runs off the side. */
.pair figure { position: relative; aspect-ratio: var(--ar, 4/3); height: var(--h, 300px); }

/* ── the looks drawn of a photo: a wall beside it, not a strip to push ── */
/* Everything drawn of this photo hangs beside it at a thumb's size, wrapping onto as many lines as it
   takes — six looks and their redraws are all on the screen at once, and how many there are is read
   off the wall rather than discovered by pushing the row sideways. In a single row the fourth look
   sat off the right edge of the page and read as never having been drawn.
   A thumb is small on purpose: it is there to be picked out of the set, and pointing at one opens it
   out over the page at the size the work is actually judged at. */
.pair.wall { grid-auto-flow: row; grid-template-columns: max-content minmax(0, 1fr); align-items: start;
  overflow: visible; -webkit-mask-image: none; mask-image: none; }
/* Every room is shown on the same upright plate, whatever shape the phone was held in — measured once
   for the whole page, so the column reads as a column instead of a ragged edge of landscapes and
   portraits. Relative, not static: the seam beside it is drawn off this figure and hangs somewhere
   else without. Nothing scrolls past it any more, so it has no reason to stick or to carry paper. */
.pair.wall > figure:first-child { position: relative; box-shadow: none;
  height: var(--bh, var(--h, 300px)); aspect-ratio: 3/4; }
/* and it opens like everything else in the row — it is the first frame of the row's own reel */
.pair > figure.was { cursor: zoom-in; }
/* A render on its way in: the row it is going to land in, drawn from the moment it is asked for, on the
   same plate at the same height as every row above it — so the wall grows by a row on the press rather
   than a minute later. */
/* Its margins are left to the rules above, which are where the room beside the seam comes from — and
   where the seam goes on a phone, which is under the plate rather than beside it. Written flat here as
   margin: 0, the plate lost that gutter and the line came down through the words. */
.pair.wall.coming { align-items: center; }
.pair.wall.coming .plate { display: grid; place-items: center; border-radius: 14px; background: #efece6; }
/* …and the circle turning on it, which is the whole point of the row: a plate held open with nothing
   moving on it reads as a picture that failed to load, not as one being drawn. */
.pair.wall.coming .plate .ring { width: 34px; height: 34px; border: 3px solid rgba(18, 17, 15, .16);
  border-top-color: var(--ink); border-radius: 99px; animation: spin 900ms linear infinite; }
.pair.wall.coming .said { color: var(--soft); font-size: 14px; }
/* The room this photograph is of, worn on the photograph. It was asked on the upload page's wall until
   that page went; the question did not go with it, because every look drawn on this row is read against
   the answer. Bottom right, opposite the Before caption, in the same dark plate the caption is. */
.pair > figure.was > .kind { position: absolute; left: 12px; bottom: 12px; z-index: 3; max-width: calc(100% - 24px); padding: 5px 11px; border: 0; border-radius: 8px; box-shadow: none; background: rgba(18,17,15,.72); color: #f4efe6; font: inherit; font-size: 13px; text-transform: capitalize; cursor: pointer; appearance: none; -webkit-appearance: none; transition: background .15s; }
.pair > figure.was > .kind:hover, .pair > figure.was > .kind:focus-visible { background: rgba(18,17,15,.92); }
/* …and red only once it is in somebody's way, which is the press of Continue with rooms still
   unnamed. Red before that is a fault reported about a photograph nobody has done anything to yet. */
.pair > figure.was > .kind.unset.nag { background: var(--flag); color: #fff; }
.pair > figure.was > .kind.unset.nag:hover, .pair > figure.was > .kind.unset.nag:focus-visible { background: #9a3025; }
.pair > figure.was > .kind option { background: #fff; color: var(--ink); }
.pair > figure.was img { transition: outline-color .14s; }
.pair > figure.was:hover img { outline: 2px solid var(--ink); outline-offset: 3px; }
/* The wall stands exactly as tall as the photograph and hangs its looks inside that — one line across
   the top of a row and one across the bottom, whichever half has more in it. More looks than the wall
   holds scroll inside it rather than making this row taller than the one under it. */
.pair.wall .gall { grid-template-columns: repeat(var(--cols, 3), minmax(0, 1fr)); grid-auto-rows: var(--th);
  gap: 10px; height: var(--bh, auto); align-content: safe center; overflow-y: auto; scrollbar-width: none; }
/* Hung on the middle of the photograph, not on its top edge: the looks are drawn a little shorter than
   the room they are of, and a short row sitting up against the top line left the photo standing alone
   over a band of paper. Safe, so a wall too full to fit still starts at the top and scrolls from there
   rather than centring its overflow off both ends. */
.pair.wall .gall::-webkit-scrollbar { width: 0; height: 0; display: none; }
/* One shape for every thumb on the page, not the shape of the room it happens to be of: a wall of
   pictures all the same size is a set to pick from — one where each is its own shape is a scrapbook. */
/* the measured height where there is one, and the shape itself as the fallback until it is measured */
.pair.wall .gall figure { height: 100%; width: 100%; aspect-ratio: 4/3; align-self: stretch; }
/* The picture fills its frame and has no say in the size of it. Left in the flow, a render drawn a
   little taller than the rest set the height of its whole line of thumbs, and the wall came out in
   lines of two different sizes. */
.pair.wall .gall figure > img, .pair.wall .gall .pending { position: absolute; inset: 0; width: 100%; height: 100%; }
.pair.wall .gall img { border-radius: 12px; }
.pair.wall .gall figcaption { left: 8px; bottom: 8px; font-size: 11px; padding: 4px 8px; }
/* The badges are drawn to the thumb, so nothing on a tile covers the render it sits on. The bin at
   the top left, the heart at the top right, and the arrow that draws in the bottom right — the same
   corner on a tile with a render in it as on one still waiting for one. */
.pair.wall .gall .bin { width: 26px; height: 26px; top: 8px; left: 8px; }
.pair.wall .gall .go.again { right: 8px; bottom: 8px; padding: 4px 9px; font-size: 12px; }
.pair.wall .gall .asks { border-radius: 12px; padding: 9px; }
/* ── the takes of a look, turned on the tile itself ────── */
/* The wall keeps one place per genre, so there is nowhere under a tile to hang a reel's controls:
   they stand on the foot of the render instead, above the caption line so neither has to move. White
   and outlined rather than filled, because they sit on a photograph and the greys the page uses for
   controls disappear into one. */
.takes { position: absolute; left: 50%; bottom: 38px; z-index: 3; transform: translateX(-50%);
  display: flex; align-items: center; gap: 7px; padding: 4px 7px; border-radius: 99px;
  background: rgba(18, 17, 15, .5); opacity: 0; transition: opacity .15s; }
.pair figure:hover > .takes, .pair figure:focus-within > .takes { opacity: 1; }
@media (hover: none) { .takes { opacity: 1; } }
.takes .step { position: static; display: grid; place-items: center; width: 18px; height: 18px; padding: 0; border: 0; border-radius: 99px; background: none; color: #fff; cursor: pointer; opacity: .7; transition: opacity .15s; }
.takes .step:hover { opacity: 1; }
.takes .step svg { width: 14px; height: 14px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.takes .step.next svg { transform: rotate(180deg); }
.takes .dots { display: flex; align-items: center; gap: 6px; }
.takes .dots button { position: static; width: 7px; height: 7px; padding: 0; border: 0; border-radius: 99px; background: none; box-shadow: inset 0 0 0 1.5px rgba(255, 255, 255, .9); cursor: pointer; transition: background .15s; }
.takes .dots button.on { background: #fff; }

.pair.wall .gall .asks .btn { padding: 8px 9px; font-size: 11px; }
.pair.wall .gall .go.again.ring { width: 26px; height: 26px; padding: 0; }
.pair.wall .gall .keep { width: 26px; height: 26px; top: 8px; right: 8px; bottom: auto; }
.pair.wall .gall .keep svg { width: 15px; height: 15px; }
.pair.wall .gall .bin svg { width: 15px; height: 15px; }
/* a thumb opens: the cursor says so before the click does, and the frame lifts under the pointer */
.pair.wall .gall figure.shot { cursor: zoom-in; }
/* …and a genre that has been drawn says so under itself: one short black rule in the gap below the
   tile. The wall keeps a place for every genre whether or not it has been drawn, so at a glance a row
   of six is six pictures — the drawn ones and the stock shots standing in for the rest — and the only
   thing separating them is a dashed border somebody has to look for. A mark under the tile reads as a
   tick against that genre from across the room, and it is drawn in the gap the grid already leaves so
   nothing moves and no picture is covered.
   Not on the customize page: its cards are the same `figure.shot`, and a rule under every product on
   the shelf is a mark that means nothing there. */
.pair.wall:not(.fitwall) .gall figure.shot::after { content: ''; position: absolute; left: 50%; top: calc(100% + 6px);
  width: 44px; height: 2px; border-radius: 2px; background: var(--ink); transform: translateX(-50%); }
/* …and the last line of tiles is given the room under it that the lines above it get from the grid gap,
   or the mark is drawn past the bottom of a wall that scrolls inside itself and cut in half. The same
   strip is taken off the height the thumbs are cut to — see WALL_FOOT — so nothing overflows for it. */
.pair.wall .gall { padding-bottom: 10px; }
.pair.wall .gall figure.shot img { transition: outline-color .14s, transform .14s; }
.pair.wall .gall figure.shot:hover img { outline: 2px solid var(--ink); outline-offset: 3px; }
/* nothing drawn yet: the offer to draw it is the same tile, at the same size as the ones it would join,
   and at full strength like them. Held at half, six genres read as a page still loading — and what
   these tiles are for is showing what the style looks like, which is the one thing halving them takes
   away. A dashed border round the tile, the genre's name in the corner and the arrow that draws it
   say it is an offer; the picture does not have to be faded to say it too. */
.pair.wall .gall figure:has(.pending) { transition: opacity .15s; }
.pair.wall .gall .pending .btn, .pair.wall .gall .pending .spin { font-size: 12px; padding: 8px 11px; }

/* throwing one away — the photo on the left, the render on the right, same corner on both. It stays
   out of the way until the picture is pointed at: a bin on every panel would read as the page's own. */
.bin { position: absolute; top: 12px; left: 12px; z-index: 2; display: grid; place-items: center; width: 32px; height: 32px; padding: 0; border: 0; border-radius: 99px; background: rgba(18,17,15,.62); cursor: pointer; opacity: 0; transition: opacity .15s, background .15s; }
.bin svg { width: 18px; height: 18px; fill: none; stroke: #f4efe6; stroke-width: 1.6; stroke-linecap: round; }
figure:hover > .bin, .ba:hover > .bin, .mosaic .tile:hover > .bin, .bin:focus-visible { opacity: 1; }
/* compared, the before and the after share one frame: the photo's bin, then the render's beside it */
.ba > .bin ~ .bin { left: 52px; }
.bin:hover { background: #9b2c1f; }
.bin:disabled { cursor: default; opacity: 0; }
/* …and on a screen with no pointer to point with, it stands on every picture: "until it is hovered"
   means "never" on a phone, and a way to get rid of a photo that never appears is not a way at all. */
/* …and it keeps its place while the job is busy rather than vanishing: hidden for the fifteen
   seconds a sweep takes, every bin on the page blinks out and the wall looks like it lost them. */
@media (hover: none) { .bin { opacity: 1; } .bin:disabled { opacity: .35; } }
/* compared, a photo with more than one render of it asks which one before it wipes to either */
.pair.choose figure.pickable .tick { border-color: var(--ink); }
.pair.choose figure.pickable:hover img { outline: 2px solid var(--ink); outline-offset: 3px; }
/* more than one take: the ticked one is the room, side by side and compared */
.pair figure.pickable { cursor: pointer; }
.pair figure.pickable:not(.picked) img { opacity: .82; }
.pair figure.pickable.picked img { outline: 2px solid var(--ink); outline-offset: 3px; }
.tick { position: absolute; right: 12px; bottom: 12px; display: grid; place-items: center; width: 26px; height: 26px; border-radius: 99px; background: rgba(18,17,15,.45); border: 1px solid rgba(244,239,230,.6); color: #f4efe6; font-size: 14px; line-height: 1; }
.pair figure.picked .tick { background: var(--ink); border-color: var(--ink); color: #f4efe6; }
/* the look they opened: its first take, and every redraw of it beside that one */
.veil .ring { width: 46px; height: 46px; border: 3px solid rgba(18, 17, 15, .18); border-top-color: var(--ink); border-radius: 99px; animation: spin 900ms linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }
.side .link { display: block; margin: -4px 0 16px; }
.pair img { width: 100%; height: 100%; object-fit: cover; border-radius: 18px; background: #efece6; display: block; }
.pair figcaption { position: absolute; left: 12px; bottom: 12px; background: rgba(18,17,15,.72); color: #f4efe6; font-size: 13px; padding: 5px 11px; border-radius: 8px; text-transform: capitalize; }
/* the after side before it exists: the look, greyed back, with the way to draw it */
/* No photo of its own yet, so it can't be sized by one — it borrows the room's shape instead. */
.pending { position: relative; display: grid; place-items: center; height: 100%; border: 1px dashed var(--faint); border-radius: 18px; overflow: hidden; background: #efece6; }
/* A look nobody has drawn yet: the whole tile is the offer, so the whole tile is the press. The arrow
   in the corner is a fingertip on a postcard, and a press that lands anywhere else on a tile that
   plainly means "draw this one" did nothing at all. */
figure.offer { cursor: pointer; }
figure.offer:hover .pending { border-color: var(--ink); }
/* The look itself, shown outright. Held back even a little, six genres read as a row of pictures the
   page was in the middle of loading — and what these panels are for is showing what the style looks
   like, which is the one thing a veil over them takes away. The dashed border, the caption and the
   arrow say it has not been drawn on their room yet; the picture no longer has to say it too. */
.pending .ghost { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; border-radius: 0; }
.pending .btn, .pending .spin { position: relative; text-transform: capitalize; max-width: 90%; text-align: center; font-size: 14px; padding: 12px 16px; }
/* the way to draw it: an arrow in the corner, cut to the same badge the caption is drawn as, so the
   two corners of a panel read as one pair rather than a label and a slab */
.pending .go { position: absolute; right: 12px; bottom: 12px; padding: 5px 11px; border-radius: 8px; background: rgba(18,17,15,.72); color: #f4efe6; border: 0; font-size: 13px; line-height: 1.6; }
.pending .go:hover { background: var(--ink); }
/* The two ways to draw a room that has furniture in it, asked on the tile rather than in a panel
   hanging off the arrow: the wall these thumbs sit in scrolls, and anything standing outside a tile
   is clipped by it. Stacked, because at a thumb's width they will not sit side by side. */
.asks { position: absolute; inset: 0; z-index: 4; display: grid; align-content: center; justify-items: center;
  gap: 6px; padding: 12px; border-radius: 18px; background: rgba(18,17,15,.55); }
.asks .btn { width: 100%; max-width: 14rem; padding: 9px 10px; font-size: 12px; text-align: center; text-transform: none; }
/* Inside a drawn tile every button is dressed as a 26px round badge in the corner — right for the bin
   and the save mark, wrong for these two, which are the question itself and not a stamp on the work. */
.shot .asks .btn { position: static; width: 100%; height: auto; border-radius: 10px; background: var(--ink); color: #f4efe6; }
/* on the shade the two read as two, rather than as one button and one outline of nothing */
.asks .btn.line { background: #f4efe6; color: var(--ink); box-shadow: none; }
/* …and the way out of the question stays up while it is open, whether or not the tile is hovered */
.pending .asks + .go, .pair figure > .asks + .go.again { opacity: 1; }
/* like every other button on a picture: out of sight until that picture is pointed at, so the page is
   a wall of rooms rather than a wall of buttons. One already drawing keeps its ring up — that is not
   a button now, it is work going on. */
.pending .go { opacity: 0; transition: opacity .15s, background .15s; }
.pair figure:hover .pending .go, .pending .go:focus-visible, .pending .go.ring { opacity: 1; }
@media (hover: none) { .pending .go { opacity: 1; } }
/* being drawn: the same corner, turning — the room it is drawing stays whole underneath */
.pending .go.ring { width: 28px; height: 28px; padding: 0; border: 3px solid rgba(18,17,15,.2); border-top-color: var(--ink); border-radius: 99px; background: none; animation: spin 900ms linear infinite; }

/* …and the tile itself says so, because the corner alone does not. A render takes the better part of a
   minute, and for all of it a wall of asked-for looks sat there as a wall of stock photographs with a
   ring the size of a thumbnail in one corner of each — which reads as a page that took the press and
   did nothing.
   So the tile draws itself instead. A hairline of ink runs down the picture and the paper it has
   already crossed is the room at full strength, while everything still ahead of it is held back under
   a wash — so the look comes in from the top down, the way a render arrives, and does it again until
   the real one lands in its place. The wash and the line are the one element, which is what keeps them
   locked together: the line is simply the top edge of what has not been drawn yet. */
.pending .inking { position: absolute; inset: 0; z-index: 1; overflow: hidden; border-radius: inherit; pointer-events: none; }
/* What is left to draw: the wash, with the pen line along its top edge and a little glow under it, the
   whole sheet of it sliding down and off the bottom until nothing is held back. */
.pending .inking .nib { position: absolute; left: 0; right: 0; top: 0; height: 100%;
  background: linear-gradient(to bottom, rgba(248,247,244,.78), rgba(248,247,244,.34) 13%, rgba(248,247,244,.34));
  border-top: 1px solid rgba(18,17,15,.38);
  animation: inking 2.2s ease-in-out infinite; }
/* Down, off, and a beat of the room whole before the next pass — the line never runs back up, because
   a line running back up reads as a scanner passing over a room rather than as a room being put down.
   It goes out rather than jumping, so the return to the top is not a flash on a wall of six tiles. */
@keyframes inking {
  0%   { transform: translateY(-6%); opacity: 1; }
  70%  { transform: translateY(100%); opacity: 1; }
  84%  { transform: translateY(100%); opacity: 0; }
  85%  { transform: translateY(-6%); opacity: 0; }
  100% { transform: translateY(-6%); opacity: 1; }
}
/* …and the look under it breathes, so a tile at work is never a still picture even between passes */
.pending:has(.inking) .ghost { animation: limn 4.4s ease-in-out infinite; }
@keyframes limn { 50% { transform: scale(1.035); } }

/* A veil is the same sentence over a picture that already exists — a redraw of a render on the page —
   so it gets the same line. Only the line, though: the veil is already most of the way to white, and a
   second wash on top of it takes away the render somebody is waiting to see change. */
.veil { overflow: hidden; }
.veil::after { content: ''; position: absolute; left: 0; right: 0; top: 0; height: 22%;
  background: linear-gradient(to bottom, rgba(18,17,15,.10), rgba(18,17,15,0));
  border-top: 1px solid rgba(18,17,15,.42);
  animation: inking 2.2s ease-in-out infinite; pointer-events: none; }
/* the ring is what the veil is for saying; the line passes under it, not over it */
.veil .ring { position: relative; z-index: 1; }

/* A page somebody has asked to hold still holds still: the ring already says the work is going on, and
   a line sweeping a tile is the part of that sentence that is decoration. The wash stays — it is what
   says the look has not been drawn here yet — it just stops moving. */
@media (prefers-reduced-motion: reduce) {
  .pending .inking .nib { animation: none; transform: none; }
  .veil::after { animation: none; opacity: 0; }
  .pending:has(.inking) .ghost { animation: none; }
}
.modes button:disabled { color: var(--faint); cursor: default; }

/* A render worth keeping, marked with a heart in the top right of every picture that can be kept —
   the same corner on every page, so the mark is looked for in one place and not hunted for. Hollow
   while the render is only on the page, filled in once it is in the gallery. */
.keep { position: absolute; right: 12px; top: 12px; bottom: auto; z-index: 2; display: grid; place-items: center; width: 28px; height: 28px; padding: 0; border: 1px solid rgba(244,239,230,.6); border-radius: 99px; background: rgba(18,17,15,.45); cursor: pointer; box-shadow: 0 1px 5px rgba(18, 17, 15, .3); transition: transform .12s, opacity .15s, background .15s, border-color .15s; }
.keep svg { width: 16px; height: 16px; fill: none; stroke: #f4efe6; stroke-width: 1.9; stroke-linecap: round; stroke-linejoin: round; }
.keep:hover { transform: scale(1.08); background: rgba(18,17,15,.72); }
/* saved: the heart fills, so a glance down the page says which renders are in the gallery */
.keep.on { background: var(--ink); border-color: var(--ink); }
.keep.on svg { fill: #f4efe6; }

/* Another version of this look, from the same corner an empty tile is drawn from: the arrow bottom
   right means "draw this look here", and on a tile that already has one it means draw it again. Drawn
   exactly like that one, because it is the same press — a circling arrow was a word about the page
   rather than about the picture, and it stood where nothing else on this wall stands. */
/* top and size said outright: the old `.shot button` badge rule reaches every button on a picture and
   would pin this one to the corner opposite the one it belongs in */
.go.again { position: absolute; right: 12px; bottom: 12px; top: auto; z-index: 2; width: auto; height: auto; padding: 5px 11px; border: 0; border-radius: 8px; background: rgba(18,17,15,.72); color: #f4efe6; font: inherit; font-size: 13px; line-height: 1.6; cursor: pointer; transition: opacity .15s, background .15s; }
.go.again:hover { background: var(--ink); }
.go.again:disabled { cursor: default; }
/* asked for again: the button itself is the ring, so the render they have stays whole underneath */
.go.again.ring { display: grid; place-items: center; width: 28px; height: 28px; padding: 0; border: 3px solid rgba(244,239,230,.34); border-top-color: #f4efe6; border-radius: 99px; background: rgba(18,17,15,.5); animation: spin 900ms linear infinite; }
/* on the job every button sits out of sight until its own picture is pointed at — the row is a wall of
   rooms, and a badge in all four corners of each of them is a page of buttons, not a page of rooms.
   A render being asked for again keeps its ring up: that one is not a button now, it is work going on. */
.pair figure > .keep, .ba > .keep, .pair figure > .go.again { opacity: 0; }
.pair figure:hover > .keep, .ba:hover > .keep, .pair figure:hover > .go.again,
.pair figure > .keep:focus-visible, .pair figure > .go.again:focus-visible, .pair figure > .go.again.ring { opacity: 1; }
/* No pointer to hover with, so the marks stand on the picture all the time — but quietly, at half
   strength, until one is actually pressed. A row of solid badges over a render is a row of badges
   competing with the thing they are about. */
@media (hover: none) { .pair figure > .keep, .ba > .keep, .pair figure > .go.again { opacity: .5; } }
/* a render already in the gallery keeps its mark up, pointed at or not: the filled mark is not a button
   offering something, it is the picture saying where it already is */
.pair figure > .keep.on, .ba > .keep.on, .mosaic .tile.saved .keep.on { opacity: 1; }
.card, .pair figure, .gall figure { position: relative; }
.ba.live img { object-position: center; }
.empty { display: grid; place-content: center; gap: 8px; width: 100%; aspect-ratio: 16/9; max-height: 52vh; border: 1px dashed var(--faint); border-radius: 20px; background: none; font: inherit; color: var(--soft); cursor: pointer; }
.empty b { font-size: 28px; font-weight: 400; }
.empty:hover { border-color: var(--accent); color: var(--accent); background: rgba(18,17,15,.03); }

.strip { display: flex; justify-content: center; gap: 8px; width: 100%; overflow-x: auto; padding: 34px 0 2px; }
.thumb { position: relative; flex: none; width: 88px; height: 62px; padding: 0; border: 0; border-radius: 12px; overflow: hidden; background: #efece6; cursor: pointer; opacity: .55; transition: opacity .15s, box-shadow .15s; }
.thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.thumb:hover { opacity: .85; }
.thumb.on { opacity: 1; box-shadow: 0 0 0 2px var(--ink); }
.thumb .x { position: absolute; top: 5px; right: 5px; width: 20px; height: 20px; border-radius: 99px; background: rgba(18,17,15,.7); color: #fff; font-size: 12px; line-height: 20px; }
.thumb.add { display: grid; place-content: center; border: 1px dashed var(--faint); background: none; color: var(--soft); font-size: 22px; opacity: 1; }
.thumb.add:hover { border-color: var(--accent); color: var(--accent); }
/* The job's renders as one line under the title of the customize page, rather than a column of rooms
   a screen and a half apart. Hung on the left edge the board below it is hung on: a line of thumbs
   floating in the middle over a left-aligned card is two pages, not one. Bigger than the sheet's,
   too: this line is chosen from, not reviewed. */
.home > .strip { justify-content: flex-start; padding: 0 0 4px; margin: -14px 0 28px; }
.home > .strip .thumb { width: 104px; height: 74px; }
/* Which one is being worked on is said by it being the only bright picture in the line, not by a ring
   of ink cut round it: at thumbnail size that ring reads as a black border the photograph itself has,
   and a hard rule of ink is the loudest thing on a page whose every other edge is soft. The rest are
   dimmed further to make up for the ring going, so the choice is still unmistakable at a glance. */
.home > .strip .thumb.on { box-shadow: none; }
.home > .strip .thumb:not(.on) { opacity: .38; }
.home > .strip .thumb:not(.on):hover { opacity: .75; }
/* a render drawn out of another sits next to it and says so, so a variation is not read as one more room */
.home > .strip .thumb.kid::before { content: '↳'; position: absolute; top: 2px; left: 6px; z-index: 2; color: #f4efe6; font-size: 13px; line-height: 1; text-shadow: 0 1px 4px rgba(18, 17, 15, .9); }

h2 { font-size: 12px; font-weight: 400; letter-spacing: .12em; text-transform: uppercase; color: var(--soft); margin: 0 0 10px; }

/* ── the flow ─────────────────────────────────────────── */
.shots { display: flex; flex-wrap: wrap; gap: 14px; }
.shot { position: relative; }
.shot img { width: 190px; height: 140px; object-fit: cover; border-radius: 16px; display: block; }
.shot button { position: absolute; top: 8px; right: 8px; width: 26px; height: 26px; border: 0; border-radius: 99px; background: rgba(18, 17, 15, .72); color: #fff; font-size: 14px; cursor: pointer; }
.drop { display: grid; place-content: center; gap: 4px; width: 190px; height: 140px; border: 1px dashed var(--faint); border-radius: 16px; background: none; font: inherit; font-size: 14px; color: var(--soft); cursor: pointer; }
.drop b { font-size: 22px; font-weight: 400; }
/* the phone's two doors, one under the other */
.drop.big + .drop.big { margin-top: 14px; }
.drop:hover { border-color: var(--accent); color: var(--accent); background: rgba(18,17,15,.03); }

.two { display: grid; grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr); gap: 34px; align-items: start; }
/* the finished job: every view of the room, then everything in it */
.gall { display: grid; gap: 12px; }
.gall figure { position: relative; }
.gall img { width: 100%; display: block; border-radius: 18px; }
.total { display: flex; justify-content: space-between; align-items: baseline; gap: 20px; border-top: 1px solid var(--ink); margin-top: 14px; padding-top: 14px; font-size: 15px; }
.total b { font-size: 20px; font-variant-numeric: tabular-nums; }

/* ── the order: one sheet, no pictures ────────────────── */
/* Every piece, what it costs, where it is bought, and what the lot comes to with Desai's cut on it.
   A page of renders here would be a page of decisions already made. */
/* Two columns of lines want the width of the page, not the measure of a paragraph. */
.bom { max-width: 80rem; margin-top: 26px; text-align: left; }
/* the piece, how many of it, what one costs and what the line comes to. The row is the button: press
   it and the shop it is bought from opens. */
/* The row itself is the way to the shop — pressed anywhere but the quantity box, it opens the listing
   in a tab of its own. A button on the end said the same thing in less room and made every line look
   like a form to fill in. */
/* Two lines across, each with the thing itself at the head of it. One column down a wide screen left
   two thirds of the page empty and the bill running off the bottom of it. */
.bom .els { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 30px; align-content: start; }
/* One line per piece, as it always was: what it is, how much of it, what one costs, what the line comes
   to. Two of these across the page rather than one, so the columns are held a little tighter. */
.bom .el { grid-template-columns: minmax(0, 1fr) 7.4rem 8rem 5rem; gap: 12px; padding: 12px 4px; align-items: center; cursor: pointer; }
.bom .el input.qty { width: 3.6rem; padding: 7px 6px; font-size: 14px; }
.bom .el .each { white-space: nowrap; }
@media (max-width: 900px) {
  .bom .els { grid-template-columns: minmax(0, 1fr); }
}
.bom .el:hover .what small { text-decoration: underline; }
/* which room the line is for, on the line — one order covers the whole house, and a tile wanted in two
   bathrooms is one thing to buy for both, so the row carries every room it answers for */
.bom .what .in { display: inline-block; margin-left: 8px; padding: 2px 7px; border-radius: 6px; background: rgba(18,17,15,.07); color: var(--body); font-size: 10px; letter-spacing: .08em; vertical-align: middle; }
/* how much of it, and what it is sold by — the number is theirs to correct on the line itself */
.bom .much { display: flex; align-items: center; gap: 8px; }
.bom .el input.qty { width: 4.6rem; padding: 8px 6px; text-align: center; font-size: 15px; font-variant-numeric: tabular-nums; cursor: text; }
.bom .unit { font-size: 13px; color: var(--soft); }
.bom .unit i { font-style: normal; font-size: 11px; letter-spacing: .06em; text-transform: uppercase; color: var(--faint); }
.bom .el em { font-size: 16px; }
.bom .el .each { font-size: 13px; color: var(--soft); text-align: right; }
/* what one costs to fit, under what one costs: the labour on the room adds up line by line in front of
   them instead of appearing whole at the foot of the sheet */
.bom .el .each .fit { display: block; margin-top: 3px; color: var(--faint); font-size: 12px; }
.bom .el .each .fit i { font-style: normal; font-size: 11px; letter-spacing: .06em; text-transform: uppercase; }
.bom .el .line { text-align: right; font-variant-numeric: tabular-nums; }
/* a figure Desai worked out rather than found: said so on the line, and again on the total it moves */
.bom .el.est .line, .bom .el.est .each { color: var(--soft); }
.bom .el.est .each i, .total small { font-style: normal; font-size: 11px; letter-spacing: .06em; text-transform: uppercase; color: var(--faint); }
.total small { margin-left: 6px; }
/* the fee is not a line of the bill, it is what sits on top of it, so it does not carry the rule */
/* The sums stand at the right-hand end of the bill, not across the whole of it: a word at one edge of
   the page and its number at the other is a line the eye has to walk, and there are three of them one
   under another. Held to a column the width of the numbers they belong to. */
.bom .total { max-width: 26rem; margin-left: auto; }
.bom .note { max-width: 46rem; }
.bom .total.fee { border-top: 0; margin-top: 8px; padding-top: 0; color: var(--soft); }
.bom .total.fee b { font-size: 16px; font-weight: 400; }
.bom .total.fee small { font-size: 12px; }
.bom .total.grand { margin-top: 10px; padding-top: 12px; }
.bom .total.grand b { font-size: 26px; font-weight: 500; }
.bom .btn { margin-top: 18px; }
.veil { position: absolute; inset: 0; display: grid; place-items: center; border-radius: 18px; background: rgba(248, 247, 244, .84); font-size: 15px; color: var(--body); }

.ask { display: flex; gap: 10px; }
.ask input { flex: 1; min-width: 0; }

.els { display: grid; gap: 2px; margin-top: 18px; }
/* swatch · words · price · shop-again · eye — the last two are not always there, and an auto
   column with nothing in it takes no room, so the eye stays on the right of every row either way. */
.el { display: grid; grid-template-columns: 44px minmax(0, 1fr) auto auto auto; align-items: center; gap: 14px; padding: 12px 4px; border-bottom: 1px solid var(--line); text-decoration: none; color: var(--ink); }
.el:hover { background: rgba(18,17,15,.03); }
/* a piece left out of the job: still on the sheet, plainly not counted */
.el.out { opacity: .5; }
.el .eye { opacity: 0; display: grid; place-items: center; width: 30px; height: 30px; padding: 0; border: 0; border-radius: 99px; background: none; color: var(--soft); cursor: pointer; transition: opacity .14s, color .14s; }
.el .eye:hover { color: var(--ink); }
.el:hover .eye, .el .eye[aria-pressed="true"] { opacity: 1; }
.el .eye svg { width: 17px; height: 17px; fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; }
@media (hover: none) { .el .eye { opacity: 1; } }

.el .sw { position: relative; z-index: 1; width: 44px; height: 44px; border-radius: 12px; background: #efece6 center / cover; transition: transform .16s ease, box-shadow .16s ease; }
/* A cut-out at 44px is a smudge. Pointed at, it grows out of the row — anchored at its own left edge
   so it opens across the sheet rather than off the side of it, and over everything else on the row. */
.el .sw:hover { z-index: 6; transform: scale(4); transform-origin: left center; box-shadow: 0 10px 30px rgba(18,17,15,.3); }
@media (hover: none) { .el .sw:hover { transform: none; box-shadow: none; } }
.el .what b { display: block; font-size: 12px; font-weight: 400; text-transform: uppercase; letter-spacing: .1em; color: var(--soft); }
.el .what small { font-size: 15px; color: var(--ink); }
.el em { font-style: normal; font-size: 14px; color: var(--soft); font-variant-numeric: tabular-nums; }
/* The bar under "finding…", while a row is out shopping. Indeterminate — a short segment travelling a
   track rather than a fill creeping to the right — because nothing here knows how far along a search
   is, and a bar that claims a percentage it invented is worse than one that admits it is only working.
   It is laid under the word rather than beside it so the row keeps its columns: the price lands in
   the same place whether the row is searching or has an answer. */
.el em .bar { display: block; overflow: hidden; width: 100%; min-width: 56px; height: 3px; margin-top: 6px; border-radius: 99px; background: var(--line); }
.el em .bar > span { display: block; width: 42%; height: 100%; border-radius: 99px; background: var(--ink); animation: finding 1.15s ease-in-out infinite; }
@keyframes finding { from { transform: translateX(-110%); } to { transform: translateX(250%); } }
/* a row being found again: only that row dims, the rest of the sheet stays theirs to click */
.el.going { opacity: .5; }
.el.going em { font-style: italic; }
/* the row is the way to what it could be bought as, so it says so by being pressable */
.el.pick { cursor: pointer; }
.el.pick.open { border-bottom-color: transparent; }
/* Shop this row again. It only shows once the row has been shopped, and it sits at the end of the
   row rather than in it — the row itself is the press that opens the options. */
.el.pick { grid-template-columns: 44px minmax(0, 1fr) auto auto auto; }
.el.pick .o.shopAgain {
  display: grid; place-items: center; width: 28px; height: 28px; margin-left: 10px;
  border: 0; border-radius: 99px; background: rgba(18,17,15,.06); color: var(--soft);
  font-size: 14px; line-height: 1; cursor: pointer; transition: background .15s, color .15s;
}
.el.pick .o.shopAgain:hover { background: var(--ink); color: #f4efe6; }
.el.pick .o.shopAgain[disabled] { cursor: default; }

/* ── what one row could actually be bought as ─────────── */
/* The real listings for one piece, under the piece: the shop's own photograph, its own name and its
   own price. Indented under the row they belong to, and ruled off at the end, so a list of five
   products reads as five ways to buy that one line and never as five more lines. */
.opts { display: grid; gap: 2px; margin: 0 0 2px 14px; padding-left: 14px; border-left: 2px solid var(--line); border-bottom: 1px solid var(--line); }
/* min-width:0 on the row and on the name: a grid track is as wide as its content unless it is told
   otherwise, so a product name a shop wrote in full pushed the price and the three buttons off the
   side of the sheet — the tick ended up floating in the page beside the panel it belongs to. */
/* The last track is the width of the two buttons and the gap between them, said outright. Left to size
   itself it came out one button short — the tick was laid out over the side of the sheet, in the page
   beside the panel it belongs to — and a row of listings is worth more than a track that measures itself. */
/* 110px, not 74: the actions column was cut for two buttons, and the third — the one that orders —
   was laid on top of the price's unit, so "$80.16 / box" read as "$80.16 / bo⊕". */
.opt { display: grid; grid-template-columns: 40px minmax(0, 1fr) auto 110px; align-items: center; gap: 12px; padding: 9px 4px; min-width: 0; }
.opt > * { min-width: 0; }
.opt:hover { background: rgba(18,17,15,.03); }
/* the photograph of the thing, small — and large the moment it is looked at, because what a tile
   actually looks like is the whole of the decision and a 40px square does not carry it */
.opt .ot { width: 40px; height: 40px; border-radius: 10px; background: #efece6 center / cover; transition: transform .16s ease, box-shadow .16s ease; transform-origin: left center; }
.opt .ot:hover { transform: scale(3.4); border-radius: 4px; box-shadow: 0 12px 34px rgba(18,17,15,.28); position: relative; z-index: 5; }
.opt .what b { display: block; font-size: 14px; font-weight: 400; text-transform: none; letter-spacing: 0; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.opt .what small { font-size: 12px; color: var(--soft); }
.opt em { font-style: normal; font-size: 15px; color: var(--ink); font-variant-numeric: tabular-nums; }
/* what the price is per, said small beside it — the number is what is read, the unit is what it means */
.opt em i, .el.pick em i { font-style: normal; font-size: 11px; letter-spacing: .04em; color: var(--soft); }
/* Sold by something other than the way this row is measured — a tile priced by the box against a
   sheet counted in square feet. The number is still the shop's; the unit is the warning. */
.opt em.odd i { color: #b4542a; }
.opt em.odd { cursor: help; }
/* two things to do with a listing: open it at the shop, or take it — which prices the row on it and
   draws the room again with it in, since choosing a product is one decision and not two */
.opt .does { display: flex; align-items: center; justify-content: flex-end; gap: 6px; }
.opt .o { flex: none; }
.opt .o { display: grid; place-items: center; width: 30px; height: 30px; padding: 0; border: 0; border-radius: 99px; background: rgba(18,17,15,.06); color: var(--ink); font: inherit; font-size: 14px; line-height: 1; text-decoration: none; cursor: pointer; transition: background .15s, color .15s, transform .12s; }
.opt .o:hover { background: var(--ink); color: #f4efe6; transform: translateY(-1px); }
.opt .o:disabled { cursor: default; transform: none; }
.opt .o .ring { width: 14px; height: 14px; border: 2px solid rgba(18,17,15,.25); border-top-color: var(--ink); border-radius: 99px; animation: spin 800ms linear infinite; }
/* …and the third, where the shop will take an order off a link: the row's own quantity, in a basket.
   Green rather than the ink the other two use — it is the one button on the row that spends money,
   and a contractor about to order sixteen boxes of tile should be able to see which one that is. */
.opt .o.buy { background: rgba(47,122,69,.12); color: #2f7a45; }
.opt .o.buy:hover { background: #2f7a45; color: #f4efe6; }
/* the one the order is written on: ticked, and left ticked */
.opt.took { background: rgba(18,17,15,.04); }
/* not "tick": there is already a .tick in here, absolutely positioned into a picture's
   corner, and it carried this button out of its row and down to the corner of the panel. */
.opt.took .o.take { background: var(--ink); color: #f4efe6; }

/* ── the measured plan: the drawing, then what it is missing ── */
.sheet { background: #fff; border: 1px solid var(--line); border-radius: 20px; padding: 10px; }
.sheet svg { display: block; border-radius: 12px; }
/* what the numbers on the sketch are in — read off it, theirs to correct */
.units { font: inherit; font-size: 12px; letter-spacing: .08em; text-transform: uppercase; color: var(--ink); background: none; border: 0; border-bottom: 1px solid var(--line); cursor: pointer; padding: 0 2px; }
.mets { display: grid; gap: 2px; margin-bottom: 26px; }
/* One wall a row: which way it runs, how long, what opening is in it — and the way out if it is not
   a wall at all. Everything the eyes read is a first draft the contractor can overrule. */
.met { display: grid; grid-template-columns: 1fr 6.5rem 5.5rem 22px; align-items: center; gap: 10px; padding: 9px 4px; border-bottom: 1px solid var(--line); }
.met:hover { background: rgba(18,17,15,.03); }
.met .lab { font-size: 15px; }
.met .lab b { font-weight: 500; }
.met .lab small { display: block; font-size: 12px; color: var(--soft); }
.met .dir { font: inherit; font-size: 12px; color: var(--soft); background: none; border: 0; cursor: pointer; padding: 0 0 0 6px; }
.met input { width: 100%; padding: 9px 10px; border-radius: 11px; font-size: 15px; text-align: right; font-variant-numeric: tabular-nums; }
.met input.door { font-size: 13px; }
.met em { font-style: normal; font-size: 13px; color: var(--soft); }
.met .x { background: none; border: 0; font: inherit; font-size: 13px; color: var(--faint); cursor: pointer; padding: 0; }
.met .x:hover { color: #9b2c1f; }
/* a wall with nothing written against it: the one thing on the page still wanted */
.met.want input { border-color: var(--accent); background: rgba(138,90,43,.05); }
.met.want em { color: var(--accent); }
.qtys { display: grid; gap: 2px; margin-top: 16px; }
.qty { display: grid; grid-template-columns: 1fr auto; align-items: baseline; gap: 14px; padding: 11px 4px; border-bottom: 1px solid var(--line); }
.qty b { font-size: 15px; font-weight: 500; }
.qty small { display: block; font-size: 12px; color: var(--soft); }
.qty em { font-style: normal; font-size: 16px; font-variant-numeric: tabular-nums; }

/* ── the footer under every page ──────────────────────── */
.foot { border-top: 1px solid var(--line); margin-top: 90px; }
.foot .cols, .foot .fine { max-width: none; margin: 0 auto; padding-left: 44px; padding-right: 44px; }
.foot .cols { display: grid; grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr)); gap: 26px; padding-top: 44px; padding-bottom: 34px; text-align: left; }
.foot h4 { font-size: 15px; font-weight: 500; margin-bottom: 12px; }
.foot button { display: block; background: none; border: 0; padding: 5px 0; font: inherit; font-size: 15px; color: var(--soft); cursor: pointer; text-align: left; }
.foot button:hover { color: var(--ink); text-decoration: underline; }
.foot .fine { display: flex; flex-wrap: wrap; gap: 8px 24px; justify-content: space-between; border-top: 1px solid var(--line); padding-top: 22px; padding-bottom: 30px; font-size: 13px; color: var(--soft); text-align: left; }

/* ── how far along, and the two ways to move ──────────── */
/* One line: Back, the way on, and — pushed to the far end — the way to send it. */
.acts { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; width: 100%; max-width: 34rem; margin-top: 22px; }
.stage.centre .acts { margin-left: auto; margin-right: auto; }
/* the two ways out of a reading, under the bar that is running: centred on it, because everything else
   on that step — the sketch, the bar, the line saying what is happening — is centred on the sheet */
.tracing .acts { justify-content: center; margin: 16px auto 0; }
.acts .rail { order: -2; flex: 0 0 100%; height: 3px; border-radius: 3px; background: var(--line); margin-bottom: 6px; }
.acts .rail span { display: block; height: 100%; border-radius: 3px; background: var(--ink); transition: width .3s ease; }
.acts .btn { width: auto; }
.acts .btn.ghost { order: -1; }
.acts .btn.push { margin-left: auto; }
/* The last step's buttons stand under the sums they act on, at the right-hand end of the bill: the
   press that buys the room belongs beside the number it is buying it for. */
.acts.end { width: 100%; max-width: 80rem; margin-left: 0; justify-content: flex-end; }
/* …and nothing in it pushed away from the rest: the share mark holds itself to the far edge on every
   other page, which here left the three of them strung across the whole width. */
.acts.end .btn.push { margin-left: 0; }
/* an icon in a button is drawn, not filled, and is the size of the words beside it */
.ico { width: 17px; height: 17px; flex: none; fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }
/* Bottom right of the screen, so they sit in the same place whatever the work above them is. */
/* Pinned, the row is only as wide as the buttons in it: given the full width of the page it hung off
   the left of the screen, anchored by its right edge. */
.acts.row { position: fixed; right: 44px; bottom: 26px; z-index: 10; display: flex; align-items: center; gap: 14px; width: auto; max-width: none; margin: 0; }
.acts.row .btn { width: auto; }
.acts.row .btn.ghost { order: -1; }

/* ── the homepage everyone lands on ────────────────────── */
/* One thing to a line, down the page in the order it is read: the title, the line under it saying what
   the page is for, then the buttons. Each stands clear of the last — read as three lines rather than
   one block, and the buttons stop landing between a title and the sentence explaining it. */
.home h1 { margin: 10px auto 18px; max-width: 26ch; }
.home .lede { margin: 0 auto 30px; max-width: 34rem; font-size: 16px; }
/* the one thing to do here, and on the photos page the way on beside it */
.cta { display: flex; flex-wrap: wrap; gap: 14px; justify-content: center; align-items: center; }
/* …and on the photos page, pinned in the corner the stages after it pin theirs — the same fixed row as
   `.acts.row`, so Declutter and Continue stand exactly where Back and Continue will. Only as wide as
   the buttons in it, anchored by its right edge, and the wall is given the height back underneath so
   the last row of photographs is not left sitting under the buttons. */
.cta.pinned { position: fixed; right: 44px; bottom: 26px; z-index: 10; width: auto; margin: 0; gap: 14px; flex-wrap: nowrap; justify-content: flex-end; }
.home:has(> .cta.pinned) { padding-bottom: 86px; }
.gallery { display: grid; grid-template-columns: 1fr 1.35fr 1fr; gap: 20px; margin-top: 48px; align-items: center; }
.gallery img { width: 100%; height: 100%; object-fit: cover; border-radius: 20px; display: block; aspect-ratio: 3/4; }
.gallery > :nth-child(2) { aspect-ratio: 4/3.4; }

/* their own photos, once any are up: a masonry of them, each at its own height so the rows stagger */
/* three across like the sample rooms it stands in for, narrowing as the library grows so the whole
   mosaic keeps to the band the gallery had rather than growing a page of full-width photos */
.mosaic { columns: var(--cols, 3); column-gap: 20px; margin-top: 48px; }
/* the whole face of it opens the photo larger, so there is no badge in the corner for that any more */
.mosaic .tile { position: relative; display: block; width: 100%; padding: 0; margin: 0 0 20px; border: 0; background: none; border-radius: 20px; overflow: hidden; cursor: zoom-in; break-inside: avoid; -webkit-column-break-inside: avoid; }
/* on the job in hand, or just kept: the ticked ones are the room being designed */
/* off the job is half there: with no outline on the ticked ones, the fade is what says which is which */
.mosaic .tile:not(.on) img { opacity: .5; }
/* the tick is the button that puts this photo on the job and takes it off again — a button of its
   own, standing on the picture rather than inside it, and always there to be pressed on a phone */
.mosaic .tile > .tick { z-index: 2; padding: 0; font: inherit; font-size: 14px; cursor: pointer; transition: background .15s, transform .12s; }
.mosaic .tile > .tick:hover { transform: scale(1.06); }
.mosaic .tile > .tick:disabled { cursor: default; transform: none; }
.mosaic .tile.on > .tick { background: var(--ink); border-color: var(--ink); color: #f4efe6; }
/* What kind of room the photograph is of, worn on the photograph. Everything a render is read against
   hangs off this one word, so it stands on every tile in the plain — a dropdown behind a sheet is an
   answer nobody corrects, and a guess nobody corrects is a board with three rows on it. */
/* One badge, one colour — no ring. The class is shared with the pale outlined chips the fitting rows
   are picked from, and their inset rule reached this one too: a pale line cut round a dark chip lying
   on a photograph reads as a sticker peeling off the room, not as a word worn by it. */
.mosaic .tile > .kind { position: absolute; left: 10px; bottom: 10px; z-index: 3; max-width: calc(100% - 20px); padding: 5px 10px; border: 0; border-radius: 9px; box-shadow: none; background: rgba(18, 17, 15, .72); color: #f4efe6; font: inherit; font-size: 12px; text-transform: capitalize; cursor: pointer; appearance: none; -webkit-appearance: none; transition: background .15s, opacity .15s; }
.mosaic .tile > .kind:hover, .mosaic .tile > .kind:focus-visible { background: rgba(18, 17, 15, .92); }
/* …and unanswered it is the question rather than the answer — but it is still the same badge, in the
   same ink, on the same corner of the tile. Drawn in paper with a ring around it, it was a second kind
   of object on the wall: a cream chip outlined against the photograph, shouting next to the quiet dark
   plates beside it. What marks it as still to answer is the words in it and the tile it sits full
   strength on, not a colour of its own. */
.mosaic .tile > .kind.unset { background: rgba(18, 17, 15, .82); color: #f4efe6; box-shadow: none; }
.mosaic .tile > .kind.unset:hover, .mosaic .tile > .kind.unset:focus-visible { background: rgba(18, 17, 15, .95); }
/* …and it goes red only once it is in the way. Red on every photograph the moment it was ticked onto
   the job made a wall of warnings out of a wall somebody had just filled — a fault reported before
   anybody had done anything wrong, and by the time it was wrong it was the colour the page had always
   been. So the badge stays the dark plate it is until Continue is pressed with rooms still unnamed;
   then it is red on exactly the photographs holding the press up. */
.mosaic .tile.on > .kind.unset.nag { background: var(--flag); color: #fff; }
.mosaic .tile.on > .kind.unset.nag:hover, .mosaic .tile.on > .kind.unset.nag:focus-visible { background: #9a3025; }
.mosaic .tile:not(.on) > .kind.unset { opacity: .8; }
.mosaic .tile:not(.on) > .kind { opacity: .55; }
/* the list itself is the browser's own, drawn on paper rather than on the badge's dark */
.mosaic .tile > .kind option { background: #fff; color: var(--ink); }
/* four heights over three columns: the row never lines up, and one tall snap can't run the page long */
.mosaic .tile img { width: 100%; height: 100%; display: block; object-fit: cover; border-radius: 20px; aspect-ratio: 3/4; transition: transform .3s ease, opacity .15s ease; }
.mosaic .tile:nth-child(4n+2) img { aspect-ratio: 1/1; }
.mosaic .tile:nth-child(4n+3) img { aspect-ratio: 4/5; }
.mosaic .tile:nth-child(4n) img { aspect-ratio: 5/4; }
.mosaic .tile:hover img { transform: scale(1.03); }
/* An empty place on the wall: where a photograph will stand, and the way to put one there. Ruled
   rather than filled, so eight of them read as a wall waiting rather than as eight grey cards. All
   one shape, and a shallower one than the photographs take: eight boxes cut to the run of upright
   proportions a real wall has come out taller than the screen and turn an empty job into a page that
   has to be scrolled before anything has been done to it. Empty, they are places — the proportions
   are the photographs' business, once there are photographs. */
.mosaic .tile.slot { display: grid; place-content: center; justify-items: center; gap: 4px; aspect-ratio: 4/3; border: 1.5px dashed var(--line); background: none; color: var(--faint); font: inherit; cursor: pointer; transition: border-color .15s, color .15s, background .15s; }
/* …and the wall they stand on is a grid of equal places rather than the staggered masonry the library
   is. Eight places only read as eight places for photographs if a photograph is the size of a place:
   left at the run of upright shapes the masonry gives them, the first one to land came in half again
   the height of the empty boxes beside it and the wall went ragged under the upload it was waiting
   for. The picture is cropped into its place, which is what the tiles here have always done. */
/* The same wall while the broom is picking on it. A tile is either ticked or it is not, so the ones
   that are not stand back rather than being hidden — the point of the board is judging which rooms
   need clearing, which cannot be done with the rest of them taken away. The mark is drawn empty on
   every tile, not only on the ticked ones: a badge that appears on press says what just happened, one
   that is already there says the press is available. */
.mosaic .tile.pickable { cursor: pointer; }
.mosaic .tile.pickable img { opacity: .62; transition: opacity .15s, transform .3s ease; }
.mosaic .tile.pickable:hover img { opacity: .85; }
.mosaic .tile.pickable.picked img { opacity: 1; }
.mosaic .tile.pickable.picked { box-shadow: inset 0 0 0 3px var(--ink); }
.mosaic .tile .mark { position: absolute; top: 10px; right: 10px; z-index: 3; display: grid; place-items: center; width: 30px; height: 30px; border-radius: 99px; background: rgba(18, 17, 15, .38); box-shadow: inset 0 0 0 1.5px rgba(244, 239, 230, .8); color: #f4efe6; font-size: 15px; line-height: 1; transition: background .15s; }
.mosaic .tile.picked .mark { background: var(--ink); box-shadow: none; }

.mosaic.places { columns: auto; display: grid; grid-template-columns: repeat(var(--cols, 4), minmax(0, 1fr)); gap: 20px; }
.mosaic.places .tile { margin: 0; aspect-ratio: 4/3; }
.mosaic.places .tile img { height: 100%; aspect-ratio: auto; }
.mosaic .tile.slot:hover { border-color: var(--ink); color: var(--ink); background: rgba(18, 17, 15, .02); }
.mosaic .tile.slot:disabled { cursor: default; border-color: var(--line); color: var(--faint); background: none; }
.mosaic .tile.slot b { font-size: 24px; font-weight: 400; line-height: 1; }
.mosaic .tile.slot span { font-size: 13px; }
/* The place the swept copy is coming to, held open while it is drawn. Drawn as a place and not as a
   shade over the photograph it came from: the sweep does not touch that photograph, and covering it
   over for a quarter of a minute — in cream, with nothing in the cream to say why — read as the wall
   throwing pictures away, right up until they all came back at once. */
.mosaic .tile.coming { display: grid; place-content: center; justify-items: center; gap: 10px; aspect-ratio: 4/3; border: 1.5px dashed var(--line); background: rgba(18, 17, 15, .02); color: var(--faint); cursor: default; }
.mosaic .tile.coming .ring { width: 28px; height: 28px; border: 2.5px solid rgba(18, 17, 15, .16); border-top-color: var(--ink); border-radius: 99px; animation: spin 900ms linear infinite; }
.mosaic .tile.coming span:last-child { font-size: 13px; }
/* the broom: this room with everything loose in it swept out. The same button on the library tile and
   on the photo in hand, so it is the same picture either way — opposite the tick on the tile, and in
   the far corner of a photo on the job, where the bin is not. */
/* the broom and the two choices behind it, in the top corner opposite the bin. Down in the bottom
   left it stood on top of the room the photograph says it is of — one dark badge over another, with
   the word half covered by a broom nobody could see was a separate button. The two corners along the
   bottom belong to what the photograph *is* (the room) and whether it is on the job (the tick); what
   is *done* to it — thrown away, swept — stands along the top. */
.broomer { position: absolute; top: 12px; right: 12px; z-index: 3; }
.broomer .broom { position: static; }
/* …so the two choices fall from it rather than rising out of the picture, and hang off its right edge
   where they cannot run off the side of the tile */
.sweeps { position: absolute; right: 0; top: calc(100% + 8px); z-index: 4; display: grid; min-width: 11.5rem; padding: 6px; background: #fff; border: 1px solid var(--line); border-radius: 12px; box-shadow: var(--lift); opacity: 0; pointer-events: none; transform: translateY(-4px); transition: opacity .14s ease, transform .14s ease; }
.broomer.open .sweeps { opacity: 1; pointer-events: auto; transform: none; }
.sweeps button { padding: 9px 10px; border: 0; border-radius: 9px; background: none; font: inherit; font-size: 14px; color: var(--body); text-align: left; white-space: nowrap; cursor: pointer; }
.sweeps button:hover { background: var(--paper); color: var(--ink); }
/* the mark on a tile is only there on hover, so the menu under it has to stay while it is open */
.mosaic .tile:has(.broomer.open) .broom, .broomer.open .broom { opacity: 1; }

.broom { position: absolute; top: 12px; right: 12px; z-index: 2; display: grid; place-items: center; width: 30px; height: 30px; padding: 0; border: 0; border-radius: 99px; background: rgba(18,17,15,.62); color: #f4efe6; font: inherit; font-size: 14px; line-height: 1; cursor: pointer; opacity: 0; transition: opacity .15s, background .15s; }
.broom:disabled { cursor: default; }
.broom svg { width: 16px; height: 16px; fill: currentColor; }
.mosaic .tile:hover .broom, .mosaic .tile.going .broom { opacity: 1; }
.broom:hover { background: var(--ink); }
@media (hover: none) { .broom { opacity: 1; } }
/* on the job the photo is a picture in a row, and the broom stands in the same corner of it as it does
   on the wall. Written as a child of the figure, this said nothing at all once the broom was wrapped in
   the span that holds its two choices: the button was there and pressable but drawn at nothing, so the
   broom on the "before" photo could only be found by a finger that already knew where it was. */
.pair figure:hover .broom, .ba:hover .broom { opacity: 1; }
/* while it is being swept the tile waits, and only that tile */
/* the saved gallery: their photos' own mosaic, holding renders — whole, lit, and named in the corner */
.mosaic .tile.saved img { opacity: 1; transition: opacity .18s; }
/* taken out of the gallery, still standing in it: half there until the page is left, so the mark that
   took it out is still under the finger that pressed it */
.mosaic .tile.saved.out img { opacity: .5; }
.mosaic .tile.saved.out .tags, .mosaic .tile.saved.out .cost, .mosaic .tile.saved.out .plan { opacity: .5; }
/* what the room is made of, asked from the corner of the picture itself — and unlike the others
   it stands there whether or not the picture is being pointed at: it is the one thing to do on this
   page, and a button nobody can see until they hover over it is a button nobody presses. */
.mosaic .tile.saved .cost { position: absolute; right: 10px; bottom: 10px; z-index: 3; width: 34px; height: 34px; display: grid; place-items: center; padding: 0; border: 0; border-radius: 99px; background: rgba(18,17,15,.72); color: #f4efe6; font: inherit; font-size: 16px; cursor: pointer; transition: transform .12s, background .15s; }
.mosaic .tile.saved .cost:hover { background: var(--ink); transform: scale(1.06); }
.mosaic .tile.saved .cost:disabled { cursor: default; transform: none; }
.mosaic .tile.saved .cost .ring { width: 16px; height: 16px; border: 2.5px solid rgba(244,239,230,.3); border-top-color: #f4efe6; border-radius: 99px; animation: spin 800ms linear infinite; }
/* what the room measures, beside what it costs: the sketch of it, drawn up. It stands where the $
   stands, whether or not the picture is being pointed at — the two things to do to a saved render. */
.mosaic .tile.saved .plan { position: absolute; right: 52px; bottom: 10px; z-index: 3; width: 34px; height: 34px; display: grid; place-items: center; padding: 0; border: 0; border-radius: 99px; background: rgba(18,17,15,.72); color: #f4efe6; font: inherit; font-size: 15px; cursor: pointer; transition: transform .12s, background .15s; }
.mosaic .tile.saved .plan:hover { background: var(--ink); transform: scale(1.06); }
/* where the sheet beside the picture reads the room by itself there is no dollar sign under the plan,
   so the plan takes the corner rather than standing a button's width in from an empty one */
.mosaic .tile.saved:not(:has(.cost)) .plan { right: 10px; }
.mosaic .tile.saved .plan .ring { width: 16px; height: 16px; border: 2.5px solid rgba(244,239,230,.3); border-top-color: #f4efe6; border-radius: 99px; animation: spin 800ms linear infinite; }
/* both come up together on the picture being pointed at: throw away, keep */
.mosaic .tile.saved .bin, .mosaic .tile.saved .keep { opacity: 0; transition: opacity .15s; }
.mosaic .tile.saved:hover .bin, .mosaic .tile.saved:hover .keep,
.mosaic .tile.saved .bin:focus-visible, .mosaic .tile.saved .keep:focus-visible { opacity: 1; }
@media (hover: none) { .mosaic .tile.saved .bin, .mosaic .tile.saved .keep { opacity: 1; } }
/* …except the heart on the pictures down the left of the shopping page, which stands there whether or
   not the picture is being pointed at. That heart is the only way off this page: it is what put the
   render here and unpressing it is what takes the render — and its whole sheet of pieces — off the
   customize page too. Hidden until hover, the one control that undoes the page was a control nobody
   could see they had. The bin stays on hover, because throwing a render away is not the thing anybody
   comes to this page to do. */
.mosaic.paired .tile.saved .keep { opacity: 1; }
/* Two badges in the one corner, stacked: the room the render is of over the look it is done in. The
   room is the one that groups the pictures, so it stands on top of the one that only describes them.
   In the corner itself, priced or not — the $ and the sketch are away at the other end of the same
   line, and lifting the badges clear of buttons they never touched left them floating up the picture. */
.mosaic .tile.saved .tags { position: absolute; left: 12px; bottom: 12px; z-index: 2; display: grid; gap: 5px; justify-items: start; max-width: calc(100% - 108px); }
.mosaic .tile.saved .lab { max-width: 100%; display: grid; gap: 1px; padding: 6px 11px; border-radius: 10px; background: rgba(18,17,15,.72); color: #f4efe6; text-align: left; }
.mosaic .tile.saved .lab b { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 13px; font-weight: 500; text-transform: capitalize; }
/* Named, the room is the light badge and the style the dark one under it: two dark pills in a corner
   read as one smudge, and of the two it is the room that has to be read at a glance. Unnamed, it is
   not a fact about the picture but an invitation, so it is drawn rather than filled. */
/* One badge, one colour: ink through, no outline. A pale rule round a dark chip and a white
   chip beside it read as two different controls doing two different things, which they are not. */
.mosaic .tile.saved .room { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; padding: 5px 11px; border: 0; border-radius: 10px; background: rgba(18,17,15,.72); box-shadow: none; color: #f4efe6; font: inherit; font-size: 12px; font-weight: 500; cursor: pointer; transition: background .15s, color .15s, transform .12s; }
.mosaic .tile.saved .room.on { background: var(--ink); box-shadow: none; color: #f4efe6; font-weight: 600; }
.mosaic .tile.saved .room:hover { transform: translateY(-1px); background: var(--ink); color: #f4efe6; box-shadow: none; }
.mosaic .tile .veil { border-radius: 20px; z-index: 1; }
.broom .ring { width: 15px; height: 15px; border: 2px solid rgba(244,239,230,.35); border-top-color: #f4efe6; border-radius: 99px; animation: spin 900ms linear infinite; }
.mosaic .tile.going .broom { z-index: 2; }

/* ── the gallery priced: two columns, pictures and bills ─ */
/* One room to the line, and the line is the page: the room's renders down the left and the one sheet
   of what it is made of down the right, so what a room costs is always level with the room it is of.
   The mosaic's own column flow is off here — the pairing has to stay level to read as pairing, and
   two rooms across left both the picture and the bill too narrow to read either. */
.mosaic.paired { columns: auto; display: grid; grid-template-columns: minmax(0, 1fr); gap: 46px; align-items: start; text-align: left; }
/* The picture's own width, measured off the picture once it has one, so the board begins where the room
   ends. Held to a height rather than a width, a tall room is drawn far narrower than the half of the row
   it was dealt, and the board started a hand's width to its right with nothing in between — two things
   on a page, rather than one room and what it is made of. Until it is measured, half and half as before. */
.roomset { display: grid; grid-template-columns: var(--pw, minmax(0, 1fr)) minmax(22rem, 1fr); gap: 0 34px; align-items: stretch; }
/* The bill stands exactly as tall as the room it is of, and does its scrolling inside itself. The
   column is laid out by the picture alone — the sheet is taken out of the flow and told to fill what
   is left — because a list that sets the height of its own row grows the page every time a row is
   opened, and the picture beside it ends up floating at the top of a column of prices. */
/* As tall as the PICTURE, and not as tall as the picture and its stepping together. The dots under a
   room with several takes are furniture beneath the photograph, not part of it, and counting them in
   left the sheet hanging a row of dots lower than the picture it stands beside. So the reel is
   dissolved into the grid: the picture takes the first row, its dots the second, and the bill is
   pinned to the picture's row alone. */
.roomset .reel { display: contents; }
.roomset .tile.saved { grid-area: 1 / 1; }
.roomset .reeler { grid-area: 2 / 1; }
/* The column beside the render, and the card fills the whole of it: the board is exactly as tall as
   the picture it belongs to and does its scrolling inside itself. The card's own doings had a bar of
   their own across the top of this cell for a while, and a bar across the top is a card that ends a
   bar's height short of the picture — so they moved into the first row's heading, where the line was
   already half empty and already the same on every board. */
.roomset .bill { grid-area: 1 / 2; position: relative; min-height: 0; }
.roomset .bill .billstack { position: absolute; inset: 0; display: flex; flex-direction: column; }
.roomset .bill .sheet { flex: 1; min-height: 0; display: flex; flex-direction: column; }
/* whichever of the two is on show scrolls; the switch above it and the total under it do not. The rows
   stack from the top of it: left to fill the height they were dealt they came out spaced like a menu,
   with a hundred pixels of paper between a tile and the floor under it. And no bar down the side —
   the list cut off mid-row is what says there is more of it, the same as everywhere else here. */
.roomset .sheet .els, .roomset .sheet .bom { flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain; align-content: start; scrollbar-width: none; }
.roomset .sheet .els::-webkit-scrollbar, .roomset .sheet .bom::-webkit-scrollbar { width: 0; height: 0; display: none; }
.roomset .sheet .bom { display: flex; flex-direction: column; max-width: none; margin-top: 0; }
.roomset .sheet .bom .els { flex: 1; }
/* the switch above the sheet: the two ways to read one room. Only as wide as the two icons it holds —
   stretched the width of the sheet, a two-way switch read as a page-wide button.
   It stands over the card's top right corner rather than inside it. Sitting at the head of the list it
   was the first thing in a pane that is otherwise all room and all price, and it took a row's worth of
   the height the list has to scroll in — which is the one thing that pane is short of. Lifted out of
   the flow it costs the list nothing, and a control that changes what the card shows reads better on
   its edge than as its first line. */
.modes.wide { position: absolute; right: 0; bottom: 100%; display: flex; width: fit-content; margin: 0 0 8px; }
.modes.wide button { display: grid; place-items: center; padding: 7px 19px; }
.modes.wide svg { width: 16px; height: 16px; }
/* ── a room's takes: one at a time, stepped through ───── */
/* A room is one thing being bought for, so its renders are a slideshow of the room rather than a wall
   of separate pictures competing with the bill standing beside them. */
/* The stepping stands under the picture, drawn and not boxed: a chevron either side of the circles,
   nothing filled and nothing framed, so the only block of colour on the row is the take you are on. */
.roomset .reeler { display: flex; align-items: center; justify-content: center; gap: 14px; margin-top: 12px; }
.roomset .step { display: grid; place-items: center; width: 22px; height: 22px; padding: 0; border: 0; background: none; color: var(--faint); cursor: pointer; transition: color .15s; }
.roomset .step:hover { color: var(--ink); }
.roomset .step.next svg { transform: rotate(180deg); }
/* how many takes there are and which is up, under the picture rather than standing on it */
.roomset .dots { display: flex; align-items: center; gap: 8px; }
.roomset .dots button { width: 8px; height: 8px; padding: 0; border: 0; border-radius: 99px; background: none; box-shadow: inset 0 0 0 1.5px var(--faint); cursor: pointer; transition: box-shadow .15s, background .15s; }
.roomset .dots button:hover { box-shadow: inset 0 0 0 1.5px var(--ink); }
.roomset .dots button.on { background: var(--ink); box-shadow: none; }
.mosaic.paired .tile { margin: 0; }
/* The render keeps its own shape here. Squared off and cropped to fill, the tile showed a room that
   does not exist — a picture pressed on a square opened as the portrait it really is, taller at the
   top and bottom than the thing that was clicked, which reads as the app having opened something
   else. It is held to a height instead, which shortens a tall room without lying about it. */
.mosaic.paired .tile img { aspect-ratio: auto; }
.mosaic.paired .tile.saved { width: fit-content; max-width: 100%; margin-inline: auto; }
/* …and square off a wide column is a tall picture, so on a desktop the bottom of the render went under
   the fold — and the bill with it, since the two share a row and the bill is sized off the picture.
   The render is capped at what the screen has left under the pitch (measured in layout(), because the
   stylesheet cannot know how many lines the headline took). A cap, not a size: a window with room to
   spare still gets the square the tile is drawn as, and only one without gets the picture cropped —
   the width is the column's either way, so this is never a smaller render, only a shorter one. */
.mosaic.paired { --rh: clamp(260px, calc(100svh - 330px), 620px); }
.mosaic.paired .tile.saved img { width: auto; max-width: 100%; height: auto; max-height: var(--rh); object-fit: contain; }
/* ── pricing-v2: the room as the names of the things in it, and six pictures of whichever is pressed ── */
.sheet.board { display: flex; flex-direction: column; gap: 14px; }
/* The pieces, as words. No description under any of them: the row next door describes a thing because
   it is a bill and a bill has to say what was bought; here the word is only a door to the pictures,
   and six pictures of an oven say more about an oven than a sentence can. */
.pieces { display: flex; flex-wrap: wrap; gap: 8px; }
/* Not capitalised word by word: the pill now carries the sentence the eyes wrote — "Stainless steel
   range hood" — and title case turned that into a product name nobody wrote. */
.piece { display: inline-flex; align-items: center; justify-content: center; gap: 7px; padding: 9px 15px; border: 0; border-radius: 99px; box-shadow: inset 0 0 0 1.5px var(--line); background: none; font: inherit; font-size: 15px; text-align: center; color: var(--ink); cursor: pointer; transition: box-shadow .14s, background .14s, color .14s; }
.piece:first-letter { text-transform: uppercase; }
.piece:hover { box-shadow: inset 0 0 0 1.5px var(--ink); }
/* Chosen or open, the pill fills and the word turns white — a ring alone was the same pill in a
   slightly darker outline, which is not something anybody notices across a row of eight of them.
   Ticked stays ticked while another piece is being looked at: the page is one basket of changes, so a
   choice already made has to still read as made when the pictures under it are somebody else's. */
.piece.open, .piece.took { background: var(--ink); color: #f4efe6; box-shadow: none; }
.piece .on { display: grid; place-items: center; width: 17px; height: 17px; border-radius: 99px; background: #f4efe6; color: var(--ink); font-size: 10px; line-height: 1; }
.piece:disabled { cursor: default; color: var(--soft); }
.piece .ring { width: 13px; height: 13px; border: 2px solid rgba(18,17,15,.2); border-top-color: var(--ink); border-radius: 99px; animation: spin 800ms linear infinite; }

/* The six. Three across and two down, which is why it is six. */
.shopts { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; }
.shopt { position: relative; display: grid; gap: 6px; padding: 0 0 8px; border: 0; border-radius: 14px; box-shadow: inset 0 0 0 1.5px var(--line); background: none; font: inherit; text-align: left; cursor: pointer; overflow: hidden; transition: box-shadow .14s, transform .12s; }
.shopt:hover { transform: translateY(-1px); box-shadow: inset 0 0 0 1.5px var(--faint); }
.shopt.took { box-shadow: inset 0 0 0 2.5px var(--ink); }
.shopt img, .shopt .noimg { display: block; width: 100%; aspect-ratio: 1/1; object-fit: cover; background: #efece6; }
.shopt .cap { padding: 0 10px; font-size: 12px; line-height: 1.35; color: var(--body); overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
/* Dear or cheap for this kind of thing, off the six that came back — not a number somebody would have
   to hold in their head for every category in a house. */
.shopt .band { position: absolute; top: 8px; left: 8px; padding: 3px 8px; border-radius: 99px; background: rgba(18,17,15,.72); color: #f4efe6; font-size: 12px; font-weight: 500; letter-spacing: .04em; }
.shopt .on { position: absolute; top: 8px; right: 8px; display: grid; place-items: center; width: 22px; height: 22px; border-radius: 99px; background: var(--ink); color: #f4efe6; font-size: 12px; line-height: 1; }

/* ── customize: what the room is made of, and six drawn ways of any piece asked about ── */
/* The panel scrolls inside the height the render sets, the way the bill beside a render does. Closed,
   it is a list of the things in the room and reads at a glance; the pictures arrive under whichever
   of them is being reconsidered, and more than one may stand open at a time — choosing a worktop is
   choosing what meets the cabinets. */
.sheet.board.fit { overflow: hidden; }
.fitscroll { flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain; scrollbar-width: none; display: grid; gap: 4px; align-content: start; padding-right: 2px; }
.fitscroll::-webkit-scrollbar { width: 0; height: 0; display: none; }
.fitrow { margin-bottom: 12px; }
/* The heading line: the row's own name, and — on the first row alone — the card's buttons in the half
   of it nothing was ever using. No "Hide" on it: nothing folds away any more, the aisles are the row,
   and a press that hides what somebody came to the board for only ever gets made by accident. */
.fitrow .rowtop { display: flex; align-items: center; gap: 10px; padding: 8px 4px; border-bottom: 1px solid var(--line); }
/* A section head is a category, so it is set like one — the same small caps the sheet next door puts
   over a row's kind, rather than a sentence in the body face. */
.fitrow .rowtop h4 { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12px; font-weight: 500; text-transform: uppercase; letter-spacing: .1em; color: var(--ink); }
/* …and the first row's line holds a button's height from the start, so the first tick does not shove
   the list down under the finger that pressed it. Set on the buttons rather than left to the words
   inside them, so the empty line and the full one are the same height to the pixel. */
.fitrow.first .rowtop { height: 48px; }       /* 32px of button between 8px of paper — held, not merely allowed */
.fitrow .swatches, .fitrow .note, .fitrow .kinds { margin-top: 10px; }
/* Open, a row is two columns: what it can be bought as on the left, what is on sale for it on the
   right. The pictures take the larger share — they are what is being looked at — and the aisles stay
   where they were pressed instead of being pushed up the panel by the wall arriving under them.
   Narrow, there is no room for two of anything and it falls back to one under the other. */
.fitopen { display: grid; grid-template-columns: minmax(150px, 5fr) minmax(0, 7fr); gap: 14px; align-items: start; }
.fitopen > .fitasks { min-width: 0; }
/* A quarter larger than they were, because what is written on them has to fit: a tile is $2.99 / sq ft
   and at 84px that ran off the side of its own picture, which reads as a price nobody can be sure of. */
.fitopen > .swatches { grid-template-columns: repeat(auto-fill, minmax(105px, 1fr)); }
@media (max-width: 620px) { .fitopen { grid-template-columns: 1fr; gap: 0; } }
/* Everything the card can be told to do, standing in the empty right-hand half of the first row's
   heading: go shopping for what has been ticked, and draw the ticks in. One line, never two: wrapped,
   the buttons grew the heading by their own height the moment a tick was made, and the whole row —
   picture and card together — stepped down the page. The heading beside them gives up its width first
   and ellipses if it has to; the row's name is repeated in every row below it, the buttons nowhere. */
.fitrow .doers { flex: none; display: flex; flex-wrap: nowrap; align-items: center; justify-content: flex-end; gap: 8px; padding-right: 4px; }
/* …and each is as wide as its own words, beaten into shape against the paired sheet's own rule, which
   stretches every button in a card to the card's full width — right for a page's one answer, and here
   it laid a black bar over the row's name. */
.mosaic.paired .sheet .fitrow .doers .btn { flex: none; width: auto; height: 32px; padding: 0 13px; border-radius: 10px; font-size: 13px; gap: 8px; white-space: nowrap; }
.fitrow .doers .btn .ring { width: 13px; height: 13px; border: 2px solid rgba(244,239,230,.32); border-top-color: #f4efe6; border-radius: 99px; animation: spin 800ms linear infinite; }
.fitrow .doers .btn.line .ring { border-color: rgba(18,17,15,.2); border-top-color: var(--ink); }
.fitrow .doers .btn.shop.going { background: var(--ink); opacity: .72; }
/* ── the customize wall: the styles page's row, with the room's parts on it ────── */
/* Where in the room they are, and the two presses that draw what has been chosen — on one line above
   the wall, because the wall stands exactly as tall as the picture beside it and there is nothing
   under it to hang a button from. */
/* One height, always: the line holds a button's worth of room whether there is a button on it or not,
   so opening a card writes into a line that was already there instead of pushing the picture down. */
.fitcrumb { display: flex; align-items: center; gap: 12px; height: 42px; margin: 0; }
.fitcrumb b { font-size: 15px; font-weight: 600; }
.fitcrumb .doers { margin-left: auto; display: flex; align-items: center; gap: 8px; }
.fitcrumb .doers .btn { width: auto; height: 34px; padding: 0 14px; border-radius: 10px; font-size: 13px; gap: 8px; white-space: nowrap; }
.fitcrumb .doers .btn .ring { width: 13px; height: 13px; border: 2px solid rgba(244,239,230,.32); border-top-color: #f4efe6; border-radius: 99px; animation: spin 800ms linear infinite; }
.fitcrumb .doers .btn.line .ring { border-color: rgba(18,17,15,.2); border-top-color: var(--ink); }
/* Half the rule the outlined buttons carry everywhere else. On this line the outline is doing less
   work: the arrow back and "Make a new render" stand beside a filled black button and over a wall of
   pale cards, so 1.5px of ink read as a second solid button rather than as the quiet alternative to
   the one next to it. Thin, they are outlines again. */
.fitcrumb .btn.line { box-shadow: inset 0 0 0 .75px var(--ink); }
.fitcrumb .btn.line:disabled { box-shadow: inset 0 0 0 .75px var(--line); }
/* A card with nothing bought for it yet is its name on empty paper, set at the size a name on a card
   is read at rather than at the size a caption is. */
/* …and grey paper, not the warm paper the rest of the app is drawn on. A wall of six empty cards is a
   large flat area of one colour, and at that size the cream the small plates elsewhere are drawn on
   reads pink. Neutral grey says "nothing here yet" without tinting the room beside it. */
.fitwall .gall figure.offer .pending { border-radius: 12px; background: #ededec; }
/* ── the wall while the room is still being read ────────
   The wait stands in the wall, across the whole of it, in the same empty paper the cards are drawn on —
   so the space the pieces are about to fill is the space that says they are coming. A circle turning
   above the words, because a grey sentence on its own is indistinguishable from a page that has
   finished and found nothing. */
.fitwall .gall > .note.wait { grid-column: 1 / -1; display: grid; justify-items: center; align-content: center;
  gap: 12px; height: 100%; min-height: 132px; margin: 0; padding: 18px; border-radius: 12px;
  background: #ededec; color: var(--soft); font-size: 14px; text-align: center; }
.fitwall .gall > .note.wait .ring { width: 30px; height: 30px; border: 3px solid rgba(18, 17, 15, .16);
  border-top-color: var(--ink); border-radius: 99px; animation: spin 900ms linear infinite; }
/* The mark of the thing itself, in the middle of a card that has no photograph on it yet — drawn, not
   filled, at the weight the rest of the app draws its controls. */
/* An aisle carries no mark, so its name is the whole card: set in the middle of it, at the size of a
   thing to press rather than the size of a label under a picture. */
/* …and no half-width nudge on it: the caption every other card has is pulled back by half of itself to
   sit centred under the picture, and a name that fills its whole card is centred already — nudged as
   well, every aisle name stood half a card to the right of the aisle it names. */
/* Relative rather than static: the empty plate under it is positioned and paints over anything left in
   the flow, so a name set in the card's own box disappeared behind the card. */
.fitwall .gall figure.offer:not(:has(.mark)) figcaption { position: relative; z-index: 1; inset: auto; transform: none; display: grid; place-items: center; width: 100%; max-width: 100%; height: 100%; padding: 10px 12px; background: none; color: var(--body); font-size: 14px; text-align: center; white-space: normal; }
.fitwall .gall .pending .mark { width: 42px; height: 42px; fill: none; stroke: var(--soft); stroke-width: 1.4; stroke-linecap: round; stroke-linejoin: round; }
/* The name under the picture rather than in the corner of it: these cards are read as a set — six
   names across one line — and a label ranged left off each one is six ragged starts. */
.pair.wall.fitwall .gall figcaption { left: 50%; right: auto; transform: translateX(-50%); max-width: calc(100% - 16px); text-align: center; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* …except the price on a thing that is for sale, which stands in the corner every caption in this app
   stands in. Centred, it read as a title for the picture rather than as its price, and it sat under
   the middle of the photograph — which on a tile of tile is the part somebody is looking at. */
.pair.wall.fitwall .gall figure.shot figcaption { left: 8px; transform: none; text-align: left; }
/* …and on a card with no photograph under it, the name is not a white label on a dark pill: there is
   nothing for it to be legible against but paper. */
.pair.wall.fitwall .gall figure.offer figcaption { background: none; color: var(--body); font-size: 13px; }
/* …and one line of air under the title, not the two the page puts between everything else: the crumb
   and the wall are one thing. */
.fitset { display: grid; gap: 14px; }
/* The tick in the corner says which one was chosen, and nothing else needs to: a black rule cut round
   the picture is a heavier mark than the thing it is marking, and on a wall of six it reads as a
   border the photograph itself has. */
/* The card the wall is pointed at: darker paper and an ink edge, so the aisle being stood in is read
   off the wall the way an open drawer is read off a chest — by weight, not by a badge. */
.pair.wall.fitwall .gall figure.in, .pair.wall.fitwall .gall figure.in:has(.pending) { opacity: 1; }
/* The cards are shorter than the room they are about, and centred against it — the same relation the
   renders on the styles wall keep to the photograph they are of. Cut to exactly the photograph's
   height, the two halves of the row read as one band of equal weight, and the room stopped being the
   subject of its own page: the picture is what is being changed, and the cards are the ways of
   changing it. Only where the wall is one grid of cards — standing in a piece it is already two lines
   inside that height, each of them shorter again. */
.pair.wall.fitwall .gall:not(.lanes) { grid-auto-rows: calc(var(--th, 130px) * .84); }
/* The card the wall is pointed at is the same card, marked on its name — a rule under the word, the
   way the open one of anything is said. It was the whole card filled a shade darker instead, and a
   grey slab standing in a line of pale ones read as a card that had been disabled rather than as the
   one being stood in: the heaviest mark on the line was on the thing the eye least needed. The card
   is left exactly as its neighbours are and the name carries the mark. */
.fitwall .gall figure.offer.in:not(:has(.mark)) figcaption, .fitwall .gall figure.in figcaption {
  color: var(--ink); font-weight: 600;
  text-decoration: underline; text-decoration-thickness: 1.5px; text-underline-offset: 5px; }
.fitwall .gall .keep.on { display: grid; place-items: center; position: absolute; top: 8px; right: 8px; z-index: 2; width: 26px; height: 26px; border-radius: 99px; background: var(--ink); color: #f4efe6; font-size: 14px; }
/* The drawing of this room, in the bottom right of the picture it is of. */
.fitwall > figure.was .plan { position: absolute; right: 10px; bottom: 10px; z-index: 3; display: grid; place-items: center; width: 32px; height: 32px; padding: 0; border: 0; border-radius: 99px; background: rgba(18,17,15,.62); color: #f4efe6; font: inherit; font-size: 15px; cursor: pointer; transition: background .15s; }
.fitwall > figure.was .plan:hover { background: var(--ink); }
.fitwall > figure.was .plan .ring { width: 15px; height: 15px; border: 2px solid rgba(244,239,230,.35); border-top-color: #f4efe6; border-radius: 99px; animation: spin 900ms linear infinite; }
/* …and a picked listing is said by its tick alone: the ink rule the wall draws round a picture under
   the pointer is a hover state, and on the one tile that is already chosen it reads as a border. */
.pair.wall.fitwall .gall figure.shot:hover img, .pair.wall.fitwall .gall figure.shot.took img,
.pair.wall.fitwall .gall figure.shot img { outline: none; }

/* The mark that asks what this thing is: top left, the corner the bin stands in on every other tile —
   small, quiet, and the only thing on the card that opens the pop. */
.fitwall .gall .info { position: absolute; top: 8px; left: 8px; z-index: 3; display: grid; place-items: center; width: 22px; height: 22px; border-radius: 99px; background: rgba(18,17,15,.5); color: #f4efe6; font-size: 12px; line-height: 1; cursor: help; opacity: 0; transition: opacity .15s, background .15s; }
.fitwall .gall .info i { font-family: Georgia, 'Times New Roman', serif; font-style: italic; font-weight: 600; }
.fitwall .gall figure:hover .info, .fitwall .gall .info:hover { opacity: 1; }
.fitwall .gall .info:hover { background: rgba(18,17,15,.85); }
@media (hover: none) { .fitwall .gall .info { opacity: 1; } }

/* the shop's own page for a thing on sale, in the corner the redraw arrow stands in everywhere else */
.fitwall .gall .go.again { display: grid; place-items: center; width: 26px; height: 26px; padding: 0; text-decoration: none; }

/* Standing in a piece, the wall is two lines and not a grid: the aisles along the top, and under them
   what the aisle being stood in sells. Each line is walked sideways rather than wrapped, so the aisles
   stay one line however many of them there are — wrapped, eight aisles took two lines of the wall and
   pushed the things actually for sale off the bottom of it. The height is split in two, so both lines
   are on the plate at once and neither of them is the taller. */
/* Two lines, each holding half the plate's height whatever is in it. Sized to their contents, the
   second line was a row of tiles one moment and a sentence the next — and the whole row above it, the
   picture included, stepped down the page the moment a press landed. A line that changes what it holds
   should not change how much room it takes. */
.pair.wall.fitwall .gall.lanes { display: grid; grid-template-columns: 1fr; grid-template-rows: 1fr 1fr; gap: 12px;
  height: var(--bh, auto); align-self: center; align-content: stretch; overflow: hidden; }
.pair.wall.fitwall .gall.lanes .lanebox { display: flex; align-items: center; min-height: 0; }
.pair.wall.fitwall .gall.lanes .lanebox > .lane { width: 100%; }
.pair.wall.fitwall .gall.lanes .lane { display: flex; align-items: flex-start; gap: 10px;
  overflow-x: auto; overflow-y: hidden; scrollbar-width: none; scroll-snap-type: x proximity; }
/* The line and the mark that says there is more of it: the mark stands on the last tile that fits,
   drawn over the picture rather than beside the line, because a chevron in the margin is a control
   about the page and this one is about the row it is sitting on. It is drawn only where the line
   actually has more in it — see laneEdge(). */
/* What the room comes to, at the end of the line above it: the one number a contractor is on this page
   for, standing where the eye finishes reading the row rather than buried at the foot of a sheet. */
/* One optical line: the number, the word and the eye set on the middle of the digits rather than on
   three different rules. Hung off the baseline, the 12px "est." and the 22px button sat a couple of
   pixels under a 17px number — enough, next to a row of pills that are dead centred, to read as a
   line that had come apart. The two lighter things are lifted by the difference between the box the
   digits sit in and the digits themselves, which is the only reason they looked low. */
.fitcrumb .tally { display: flex; align-items: center; gap: 6px; margin-left: 12px; font-size: 17px; line-height: 1; }
.fitcrumb .tally b { font-weight: 600; line-height: 1; }
.fitcrumb .tally small { color: var(--soft); font-size: 12px; line-height: 1; }
/* …and the eye that says how much of it is still a guess. Quiet until it is pointed at: it is a
   footnote on the number, not a second number. */
.fitcrumb .tally .info { position: relative; display: grid; place-items: center; width: 22px; height: 22px; padding: 0; border: 0; background: none; align-self: center; color: var(--soft); cursor: pointer; transition: color .15s; }
.fitcrumb .tally .info:hover { color: var(--ink); }
/* What the number is made of, opened off the eye: one card to a line, ticked or marked as an estimate,
   in the order the cards stand in on the wall. Hung under the eye and ranged to its right edge, which
   is the edge the number is set to. */
.fitcrumb .tally { position: relative; }
.fitcrumb .tally .peek { position: absolute; top: calc(100% + 8px); right: -6px; z-index: 30; display: none;
  min-width: 16rem; padding: 12px 14px; border-radius: 14px; background: rgba(18,17,15,.94); color: #f4efe6;
  font-size: 13px; text-align: left; box-shadow: 0 16px 40px rgba(18,17,15,.28); }
.fitcrumb .tally .info:hover .peek, .fitcrumb .tally .info:focus-visible .peek, .fitcrumb .tally .peek.open { display: grid; gap: 7px; }
/* Pressed rather than pointed at, the panel stays open and each line shows its own arithmetic: how much
   of the thing the room takes, what one costs, and what one costs to fit. Wider, because a line of
   working is a sentence of numbers and wrapping it is what makes a column of them unreadable. */
.fitcrumb .tally .peek.open { min-width: 38rem; padding: 18px 20px; font-size: 21px; cursor: default; }
/* The working under a line starts where the line's own name starts — the marker column and the gap
   after it, and not a hair to either side of that. Half a character out, a column of them reads as a
   second list that happens to be nearby. And set at the size of the list it belongs to: a number a
   contractor is checking a quote against is not a footnote. */
.fitcrumb .tally .peek .work { grid-column: 1 / -1; display: grid; grid-template-columns: 1fr auto auto; align-items: baseline; gap: 4px 12px; margin: 5px 0 0; padding-left: calc(3.2rem + 10px); color: rgba(244,239,230,.62); font-size: 19px; }
.fitcrumb .tally .peek .work em { font-style: normal; font-variant-numeric: tabular-nums; color: rgba(244,239,230,.62); }
.fitcrumb .tally .peek .line:has(.work) { padding-bottom: 4px; }
.fitcrumb .tally .info.on { color: var(--ink); }
.fitcrumb .tally .peek .line { display: grid; grid-template-columns: 3.2rem 1fr auto; align-items: baseline; gap: 8px 10px; }
.fitcrumb .tally .peek .line em { font-style: normal; font-variant-numeric: tabular-nums; color: rgba(244,239,230,.62); }
.fitcrumb .tally .peek .line.on em { color: #f4efe6; }
.fitcrumb .tally .peek .line i { font-style: normal; font-size: 16px; letter-spacing: .02em; color: rgba(244,239,230,.55); }
.fitcrumb .tally .peek .line.on i { font-size: 19px; color: #f4efe6; }
/* …and a card part way there is neither: the count stands in the tick's place, in the tick's colour,
   because two of four priced is a real thing to have done and "est." says nothing was. */
.fitcrumb .tally .peek .line.part i { color: rgba(244,239,230,.85); font-variant-numeric: tabular-nums; }
.fitcrumb .tally .peek .line.on { font-weight: 500; }
.fitcrumb .tally .peek .foot { display: flex; align-items: baseline; gap: 10px; margin-top: 3px; padding-top: 8px; border-top: 1px solid rgba(244,239,230,.16); color: rgba(244,239,230,.6); font-size: 18px; }
/* the note keeps the line; the total sits at the end of it, in the column the panel's own numbers run down */
.fitcrumb .tally .peek .foot > span { flex: 1; min-width: 0; }
.fitcrumb .tally .peek .foot b { font-weight: 400; color: rgba(244,239,230,.6); }
.fitcrumb .tally .peek .foot em { font-style: normal; font-weight: 600; font-size: 22px; font-variant-numeric: tabular-nums; color: #f4efe6; }
.fitcrumb .tally .peek .foot i { font-style: normal; font-size: 15px; color: rgba(244,239,230,.55); }
.fitcrumb .tally .info svg { width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }
/* ── the order: the bill, and the rooms it buys ────── */
/* The list down one side and the renders down the other. The bill is the working document, so it takes
   the width; the pictures are what it is a bill FOR, and they sit where the eye lands after a line. */
/* …and the way to buy one line, on the line. It is the only press on the row that goes anywhere, so it
   is drawn as a button and the rest of the row is text to read. */

.lanebox { position: relative; min-width: 0; }
/* The mark itself and nothing behind it: a filled circle on the corner of a picture is a button about
   the picture — the bin and the heart are drawn that way — and this one is about the line. The stroke
   is drawn twice, dark over a white halo, so it stays legible over a photograph or over paper. */
.lanebox .onward { position: absolute; top: 50%; z-index: 4; display: none; place-items: center;
  width: 30px; height: 30px; margin-top: -15px; padding: 0; border: 0; border-radius: 99px;
  background: none; cursor: pointer; opacity: .75; transition: opacity .15s; }
.lanebox .onward { right: 4px; }
.lanebox .onward.back { right: auto; left: 4px; }
.lanebox.more .onward:not(.back), .lanebox.back .onward.back { display: grid; }
.lanebox .onward:hover { opacity: 1; }
.lanebox .onward svg { width: 22px; height: 22px; fill: none; stroke: var(--ink); stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round; transform: rotate(-90deg);
  filter: drop-shadow(0 0 2px rgba(248,247,244,.95)) drop-shadow(0 0 4px rgba(248,247,244,.8)); }
.lanebox .onward.back svg { transform: rotate(90deg); }
.pair.wall.fitwall .gall.lanes .lane::-webkit-scrollbar { width: 0; height: 0; display: none; }
/* A tile in a line is set by its width and takes its height from its shape — the other way round, a
   line half the plate tall made tiles three hundred pixels wide and put two of them on a wall that
   used to hold six. It never shrinks to share the line either: eight aisles squeezed to fit is eight
   slivers, and the line is there to be walked along. */
.pair.wall.fitwall .gall.lanes .lane figure { flex: 0 0 clamp(112px, 16%, 168px); width: auto; height: auto;
  align-self: flex-start; scroll-snap-align: start; }
/* …and the second line saying why it is empty is a line of words, not a tile: it sits at the top of
   its half rather than being stretched down the middle of it. */
.pair.wall.fitwall .gall.lanes .lane > .note { margin: 0; align-self: center; }

/* Which aisle, asked before anything is searched for: a floor is tile or wood or vinyl long before it
   is a particular product, and one search across all of them is a muddle that costs a trip to make. */
.kinds { display: flex; flex-wrap: wrap; gap: 6px; }
.kind { padding: 6px 11px; border: 0; border-radius: 99px; box-shadow: inset 0 0 0 1.5px var(--line); background: none; font: inherit; font-size: 12px; color: var(--body); cursor: pointer; transition: box-shadow .14s, background .14s, color .14s; }
.kind:hover { box-shadow: inset 0 0 0 1.5px var(--ink); color: var(--ink); }
.kind.on { background: var(--ink); box-shadow: none; color: #f4efe6; }
/* …and an aisle with nothing on the shelf behind it stands at half strength: pressed, it is a search
   and a dozen shop pages rather than a file read, and the wait is the difference. Half strength says
   "this one costs you something" before the press, which is the only moment it can be said. */
.kinds .kind.bare { opacity: .5; }
.kinds .kind.bare:hover, .kinds .kind.bare.on { opacity: 1; }
/* Smaller than they were, and more of them across. A choice between six floors is a comparison, and a
   comparison is made by having them all in the eye at once — three big plates meant scrolling between
   the first and the last, which is remembering rather than looking. */
.swatches { display: grid; grid-template-columns: repeat(auto-fill, minmax(94px, 1fr)); gap: 8px; }
/* The card is the shop's photograph with its price under it: what a thing costs is half of choosing
   it, so that one number is printed and never hidden. Everything else about the listing — what it is
   called and who sells it — comes up over the picture while it is pointed at, because eighteen full
   product titles is a wall of small grey type rather than a wall of materials. */
.swatch { position: relative; display: block; padding: 0; border: 0; border-radius: 14px; box-shadow: inset 0 0 0 1.5px var(--line); background: none; font: inherit; text-align: left; cursor: pointer; overflow: hidden; transition: box-shadow .14s, transform .12s; }
.swatch:hover { transform: translateY(-1px); }
.swatch.took { box-shadow: inset 0 0 0 2.5px var(--ink); }
.swatch img { display: block; width: 100%; aspect-ratio: 1/1; object-fit: cover; border-radius: 14px; background: #efece6; }
/* the shop's own page for it, one press away in its corner — the press on the card itself is the one
   this page is for, which is taking the thing */
.swatch .glass { position: absolute; top: 7px; left: 7px; z-index: 3; display: grid; place-items: center; width: 26px; height: 26px; padding: 0; border: 0; border-radius: 99px; background: rgba(18,17,15,.58); color: #f4efe6; cursor: pointer; opacity: 0; transition: opacity .14s, background .14s; }
.swatch .glass svg { width: 15px; height: 15px; fill: none; stroke: currentColor; stroke-width: 1.9; stroke-linecap: round; }
.swatch:hover .glass, .swatch:focus-within .glass { opacity: 1; }
.swatch .glass:hover { background: var(--ink); }
@media (hover: none) { .swatch .glass { opacity: 1; } }
/* What it costs, printed on the picture and always there — the one thing written on a thumb. The name
   used to be written under it and raised on hover, which put a clipped half-sentence over the price at
   the moment somebody was reading it; the pop beside the thumb says all of that in full. */
.swatch .cost { position: absolute; left: 6px; bottom: 6px; z-index: 3; padding: 3px 7px; border-radius: 8px; background: rgba(18,17,15,.82); color: #f4efe6; font-size: 11px; font-weight: 500; line-height: 1.3; }
.swatch .on { position: absolute; z-index: 9; top: 7px; right: 7px; display: grid; place-items: center; width: 21px; height: 21px; border-radius: 99px; background: var(--ink); color: #f4efe6; font-size: 11px; line-height: 1; }
/* What the thumbnail is, said in full while the pointer is on it. Fixed to the window, not to the
   tile: the tile clips its own corners and the board scrolls inside itself, so a pop drawn in either
   is cut off at exactly the word it was opened to read. Each line is one thing — the name, the shop's
   own title, who sells it, what it costs — so it is read down rather than across. */
#tip { position: fixed; z-index: 80; max-width: 280px; padding: 9px 11px; border-radius: 12px; background: rgba(18,17,15,.94); color: #f4efe6; font-size: 12px; line-height: 1.4; white-space: pre-line; box-shadow: 0 10px 30px rgba(18,17,15,.28); opacity: 0; visibility: hidden; transition: opacity .12s; pointer-events: none; }
#tip.up { opacity: 1; visibility: visible; }
.fitrow .note { display: flex; align-items: center; gap: 9px; }
.fitrow .note .ring { flex: none; width: 15px; height: 15px; border: 2.5px solid rgba(18,17,15,.2); border-top-color: var(--ink); border-radius: 99px; animation: spin 800ms linear infinite; }

/* One button for the whole basket: the room drawn again with everything ticked, at once. */
/* the one way on, turning while the rooms it took with it are drawn */
.cta .btn.going { display: inline-flex; align-items: center; gap: 10px; }
.cta .btn.going .ring { width: 16px; height: 16px; border: 2.5px solid rgba(244,239,230,.3); border-top-color: #f4efe6; border-radius: 99px; animation: spin 800ms linear infinite; }
.btn.remake { align-self: start; gap: 9px; }
.btn.remake .ring { width: 16px; height: 16px; border: 2.5px solid rgba(244,239,230,.3); border-top-color: #f4efe6; border-radius: 99px; animation: spin 800ms linear infinite; }
/* Two ways to draw the ticks, side by side: over this render, or as one of its own. The first is
   filled because it is what most presses mean — a piece corrected; the second is outlined because a
   second render is a second thing on the page, and that is a choice worth looking at before making. */

/* No white block on this page. A sheet of white infill on cream paper reads as a hole cut in the page —
   the bill is a list standing on the paper, so it is ruled and not filled. */
.mosaic.paired .sheet { background: none; padding: 18px 20px; }
.mosaic.paired .sheet .els { margin-top: 0; }
.mosaic.paired .sheet .btn { width: 100%; }
.mosaic.paired .sheet .els + .total + .btn, .mosaic.paired .sheet .els ~ .note { margin-top: 16px; }
/* the room's bill, in the column beside the room: the piece, how much of it, what one costs, what the
   line comes to — the order's own four columns, at the width there is here to draw them in */
.mosaic.paired .sheet .bom .el { grid-template-columns: minmax(0, 1fr) 7rem 6rem 5rem; gap: 10px; padding: 11px 2px; cursor: default; }
/* room for the number the drawing worked out — 155.79 square feet of wall came out as "155." in a box
   sized for a shelf price, and a quantity nobody can read is a quantity nobody can correct */
.mosaic.paired .sheet .bom .el input.qty { flex: none; width: 3.6rem; padding: 7px 4px; font-size: 15px; }
.mosaic.paired .sheet .bom .el .what small { font-size: 15px; }
.mosaic.paired .sheet .bom .el .each { font-size: 13px; }
.mosaic.paired .sheet .bom .el em { font-size: 15px; }
/* the sheet being read: the ring stands in the row where the button was, so only that pair is working */
.mosaic.paired .sheet .note { display: flex; align-items: center; gap: 10px; margin-top: 0; }
.mosaic.paired .sheet .note .ring { flex: none; width: 16px; height: 16px; border: 2.5px solid rgba(18,17,15,.2); border-top-color: var(--ink); border-radius: 99px; animation: spin 800ms linear infinite; }
/* The bill beside the room is read, not glanced at: a contractor is choosing a tile off it and quoting
   from it, so it is set at the size of something meant to be read across a desk rather than at the size
   of a caption. The whole sheet steps up together — the piece, the product, the shop and the price —
   because raising one of them only makes the rest look like small print. */
.mosaic.paired .sheet .el .what b { font-size: 13px; }
.mosaic.paired .sheet .el .what small { font-size: 17px; }
.mosaic.paired .sheet .el em { font-size: 16px; }
.mosaic.paired .sheet .opt .what b { font-size: 16px; }
.mosaic.paired .sheet .opt .what small { font-size: 14px; }
.mosaic.paired .sheet .opt em { font-size: 17px; }
.mosaic.paired .sheet .opt em i, .mosaic.paired .sheet .el.pick em i { font-size: 12px; }
.mosaic.paired .sheet .opt .o { width: 34px; height: 34px; font-size: 16px; }
.mosaic.paired .sheet .total { font-size: 17px; }
.mosaic.paired .sheet .total b { font-size: 24px; }
/* a column half the page wide holds the bill as it is written anywhere else — swatch, name, price —
   so the only thing it asks for is that a long product name wraps rather than being cut off:
   "Lancaster Celery Green 3x6 Polished" came out as "Lancast…", which names nothing. */
.mosaic.paired .sheet .opt .what b { white-space: normal; overflow: visible; line-height: 1.3; }

/* ── the upload sheet: this desktop on the left, their phone on the right ── */
.shade { position: fixed; inset: 0; background: rgba(18, 17, 15, .42); z-index: 40; animation: fade .18s ease-out; }
/* the sheet over it is a room of its own: the page behind is blurred right out, so the drawing and
   the numbers on it are the only things on the screen anyone can read */
.shade.blur { background: rgba(18, 17, 15, .5); backdrop-filter: blur(26px) saturate(.9); -webkit-backdrop-filter: blur(26px) saturate(.9); }
.sheet-up { position: fixed; z-index: 41; top: 50%; left: 50%; transform: translate(-50%, -50%); width: min(760px, calc(100vw - 40px)); background: #fff; border-radius: 24px; padding: 30px; box-shadow: 0 24px 70px rgba(18, 17, 15, .25); text-align: left; animation: rise .22s ease-out; }
/* …but only the once. A sheet is written out again on every press inside it — a pill chosen, a box
   opened — and the whole of #over is rebuilt to do it, which starts these two animations over: the
   shade fades in again and the sheet rises again under the finger that just pressed it. Already open
   and merely redrawn, it holds still. */
#over.still .shade, #over.still .sheet-up { animation: none; }
.sheet-up .x { position: absolute; top: 16px; right: 18px; border: 0; background: none; font: inherit; font-size: 16px; color: var(--soft); cursor: pointer; }
.sheet-up .ways { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; align-items: stretch; }
.sheet-up .ways > * { display: grid; align-content: center; justify-items: center; gap: 10px; text-align: center; padding: 26px 20px; border-radius: 18px; min-height: 300px; }
.sheet-up .drop { width: auto; height: auto; border: 1.5px dashed var(--faint); }
.sheet-up .drop.over { border-color: var(--accent); background: #faf7f1; }
.sheet-up .ways b { font-size: 18px; font-weight: 500; color: var(--ink); }
.sheet-up .ways small { font-size: 13px; color: var(--soft); max-width: 24ch; line-height: 1.5; }
.sheet-up .qr { background: #f6f2ea; }
.sheet-up .qr svg { border-radius: 10px; }
.sheet-up .wait-qr { display: grid; place-content: center; gap: 6px; width: 200px; height: 200px; padding: 16px; border-radius: 10px; background: #fff; color: var(--soft); font-size: 13px; text-align: center; }
/* on a phone the code is a camera instead — and it stands first on the sheet, because someone holding
   the phone in the room is one press from having photographed it */
.sheet-up .qr.snap { order: -1; }
.sheet-up .snap .lens { display: grid; place-items: center; width: 92px; height: 92px; border-radius: 99px; background: #fff; color: var(--ink); }
.sheet-up .snap .lens svg { width: 40px; height: 40px; fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linejoin: round; }
/* the quieter of two buttons in a sheet */
.sheet-up .btn.ghost { background: none; border: 1px solid var(--faint); color: var(--soft); }
.sheet-up .err { display: block; margin-top: 18px; font-size: 14px; }
.sheet-up .tally { display: flex; align-items: center; justify-content: space-between; gap: 16px; margin-top: 22px; font-size: 14px; color: var(--soft); }
/* the job is still being made: the line says so where the count will be, in the same place and the
   same size, so nothing under it moves when the name lands */
.sheet-up .tally .link.off { color: var(--soft); text-decoration: none; cursor: default; }
@keyframes fade { from { opacity: 0; } }
@keyframes rise { from { opacity: 0; transform: translate(-50%, -46%); } }

/* ── which room a render is of ────────────────────────── */
/* A small sheet for a small question. The rooms they have already named are the answer nearly every
   time, so they are pressable words rather than a list to read; the line under them is only for the
   room being named for the first time. */
.sheet-up.narrow { width: min(460px, calc(100vw - 40px)); padding: 26px; }
.sheet-up.narrow h3 { font-size: 20px; font-weight: 600; letter-spacing: -.01em; margin-bottom: 8px; }
.sheet-up.narrow .note { font-size: 13px; line-height: 1.55; color: var(--soft); margin-bottom: 18px; }
.sheet-up .rooms { display: flex; flex-wrap: wrap; gap: 8px; }
.sheet-up .rooms:not(:empty) { margin-bottom: 18px; }
.chip { display: inline-flex; align-items: center; gap: 7px; padding: 8px 14px; border: 0; border-radius: 99px; background: none; box-shadow: inset 0 0 0 1.5px var(--line); color: var(--ink); font: inherit; font-size: 14px; cursor: pointer; transition: box-shadow .15s, background .15s; }
.chip:hover { box-shadow: inset 0 0 0 1.5px var(--ink); }
/* the room it is already in, and the press that takes it out again */
.chip.on { background: var(--ink); box-shadow: none; color: #f4efe6; }
.chip.on span { opacity: .6; font-size: 12px; }
.chip.on:hover span { opacity: 1; }
.sheet-up.narrow .say { display: flex; gap: 10px; }
.sheet-up.narrow .say input { flex: 1; min-width: 0; }

/* ── which room is this one of ──────────────────────────
   The upload sheet again, exactly: the same width, the same two panels at the same size, the same Done
   under them. Only what stands inside the two panels differs — the photograph where the drop zone was,
   the rooms to pick from where the code to scan was — so nothing here sets a size, and one photograph
   is asked about at a time rather than a list of them scrolling inside a taller sheet.

   Its own class names, not `.asks` and `.ask`. Those two already belong to the pair of buttons drawn
   over a tile on the styles page — `position: absolute; inset: 0` — so this sheet's list inherited them
   and laid itself over its own title: the chips sat across the words asking the question. */
/* …and the two panels stand as tall here as they do there. The upload sheet's are 359px because that
   is what a code, its heading and its caption come to; nothing on this sheet is that tall, so left to
   its own content the same modal opened 60px shorter than the one it opens out of — the second of two
   sheets in a row, jumping. The height is the sheet's, not the content's. */
.sheet-up.asking .ways > * { min-height: 359px; }
/* the left panel — the picture, edge to edge in it rather than sat inside the panel's own padding.
   Laid over the panel rather than inside it: a photograph is whatever shape the phone held, and one
   held upright is half as wide as it is tall — in the flow it made the panel that tall and the sheet
   with it, so the same modal was one height for a landscape room and another for a portrait one. The
   panel is the size the sheet says it is and the picture fills it, exactly as the code beside it does. */
.sheet-up .ways > .shotpane { display: block; position: relative; padding: 0; overflow: hidden; background: #efece6; }
.shotpane img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block; }
/* …and the right one on nothing at all: the code needed a plate to be readable on, and pills carry
   their own outline, so a second filled box behind them is a box for the sake of symmetry. */
.sheet-up.asking .rooms { margin-bottom: 0; gap: 9px; width: 100%; justify-content: center; align-content: center; }
/* the question over the pills — its own line, the full width of the panel, so the answers wrap under
   it rather than up beside it */
.sheet-up.asking .rooms .asked { flex: 0 0 100%; margin: 0 0 6px; text-align: center;
  font-size: 20px; font-weight: 600; letter-spacing: -.01em; color: var(--ink); }
.sheet-up.asking .chip { font-size: 15px; padding: 10px 17px; }
/* the room they name themselves: the box takes the place the chip was standing in, on the same line */
.sheet-up.asking .own { display: inline-flex; align-items: center; gap: 8px; }
.sheet-up.asking .own input { width: 11rem; max-width: 100%; padding: 9px 14px; border: 0; border-radius: 99px;
  box-shadow: inset 0 0 0 1.5px var(--line); background: #fff; font: inherit; font-size: 15px; color: var(--ink); }
.sheet-up.asking .own input:focus { outline: 0; box-shadow: inset 0 0 0 1.5px var(--ink); }
/* nothing to the left of Done any more, so the row is the button alone, kept on its own side */
.sheet-up.asking .tally { justify-content: flex-end; }

/* ── a page arriving ──────────────────────────────────── */
/* Everything on a new page comes up from under itself, in the order it is read: the pictures, the
   rows, the columns, the lines of a bill. Only on arrival — the class is taken off a moment later, so
   ticking a style or landing a render never replays the whole page under the hand that did it. */
@keyframes riseIn { from { opacity: 0; transform: translateY(12px); } }
.fresh h1, .fresh .lede, .fresh .cta > *, .fresh .mosaic .tile, .fresh .pair > figure, .fresh .ba.live,
.fresh .el, .fresh .card, .fresh .gall figure, .fresh .qty, .fresh .met, .fresh .show > *,
.fresh .sheet, .fresh .total, .fresh .strip, .fresh .two > *, .fresh .how > *, .fresh .acts,
.fresh .gallery > *, .fresh .shots > * { animation: riseIn .42s cubic-bezier(.2, .7, .3, 1) both; }
/* read in order, not all at once — eight steps of stagger, then everything after them together */
.fresh .mosaic .tile:nth-child(2), .fresh .pair > figure:nth-child(2), .fresh .el:nth-child(2), .fresh .gallery > :nth-child(2), .fresh .card:nth-child(2), .fresh .met:nth-child(2), .fresh .qty:nth-child(2), .fresh .show > :nth-child(2) { animation-delay: 45ms; }
.fresh .mosaic .tile:nth-child(3), .fresh .pair > figure:nth-child(3), .fresh .el:nth-child(3), .fresh .gallery > :nth-child(3), .fresh .card:nth-child(3), .fresh .met:nth-child(3), .fresh .qty:nth-child(3), .fresh .show > :nth-child(3) { animation-delay: 90ms; }
.fresh .mosaic .tile:nth-child(4), .fresh .pair > figure:nth-child(4), .fresh .el:nth-child(4), .fresh .card:nth-child(4), .fresh .met:nth-child(4), .fresh .qty:nth-child(4), .fresh .show > :nth-child(4) { animation-delay: 135ms; }
.fresh .mosaic .tile:nth-child(5), .fresh .pair > figure:nth-child(5), .fresh .el:nth-child(5), .fresh .card:nth-child(5), .fresh .met:nth-child(5), .fresh .qty:nth-child(5), .fresh .show > :nth-child(5) { animation-delay: 180ms; }
.fresh .mosaic .tile:nth-child(6), .fresh .pair > figure:nth-child(6), .fresh .el:nth-child(6), .fresh .card:nth-child(6), .fresh .met:nth-child(6), .fresh .qty:nth-child(6), .fresh .show > :nth-child(6) { animation-delay: 225ms; }
.fresh .mosaic .tile:nth-child(n+7), .fresh .pair > figure:nth-child(n+7), .fresh .el:nth-child(n+7), .fresh .card:nth-child(n+7), .fresh .met:nth-child(n+7), .fresh .qty:nth-child(n+7), .fresh .show > :nth-child(n+7) { animation-delay: 270ms; }
/* the words first, then the work under them */
.fresh .lede { animation-delay: 60ms; }
.fresh .cta > * { animation-delay: 110ms; }
/* …and a board that lands on a wall somebody is already looking at comes up the same way an arriving
   page does. The reading is started at the heart now, so it usually finishes before this page is ever
   opened and the arrival above does the work — but where it lands under an open page, six cards that
   were not there appearing between two frames reads as a glitch rather than as an answer. One class,
   written once, on the draw that follows the reading: see justRead. */
.fitwall .gall.came figure { animation: riseIn .42s cubic-bezier(.2, .7, .3, 1) both; }
.fitwall .gall.came figure:nth-child(2) { animation-delay: 45ms; }
.fitwall .gall.came figure:nth-child(3) { animation-delay: 90ms; }
.fitwall .gall.came figure:nth-child(4) { animation-delay: 135ms; }
.fitwall .gall.came figure:nth-child(5) { animation-delay: 180ms; }
.fitwall .gall.came figure:nth-child(n+6) { animation-delay: 225ms; }
/* …and the same rise on the lower lane alone when the aisle above it changes — with the lane it is
   replacing taken out first, in the 130ms fitKind waits before it writes the new one. Two halves of
   one movement: the shelf they were looking at goes, and the shelf they asked for arrives. */
/* An animation and not a transition: the page is written out from a string, so the lane on its way out
   is a brand new element that already carries the class — and a transition on an element that was born
   at its end state has nothing to move from, which drew the shelf blinking out rather than going. */
@keyframes laneOut { from { opacity: 1; } to { opacity: 0; transform: translateY(6px); } }
.fitwall .gall.lanes .lanebox .lane.off { animation: laneOut .13s ease both; }
/* …and the whole wall the same way when it changes level rather than aisle: pressing a card takes the
   cards on the wall off it and brings up what is inside the one pressed. Both halves are the lane's,
   at the lane's speed, so walking into a card and walking along a line of aisles are one movement at
   two sizes. See fitOver. */
.fitwall .gall { transition: opacity .13s ease, transform .13s ease; }
.fitwall .gall.off { opacity: 0; transform: translateY(6px); }
.fitwall .gall.lanes.turned .lanebox:nth-child(2) figure,
.fitwall .gall.lanes.turned .lanebox:nth-child(2) .note { animation: riseIn .34s cubic-bezier(.2, .7, .3, 1) both; }
.fitwall .gall.lanes.turned .lanebox:nth-child(2) figure:nth-child(2) { animation-delay: 40ms; }
.fitwall .gall.lanes.turned .lanebox:nth-child(2) figure:nth-child(3) { animation-delay: 80ms; }
.fitwall .gall.lanes.turned .lanebox:nth-child(2) figure:nth-child(4) { animation-delay: 120ms; }
.fitwall .gall.lanes.turned .lanebox:nth-child(2) figure:nth-child(5) { animation-delay: 160ms; }
.fitwall .gall.lanes.turned .lanebox:nth-child(2) figure:nth-child(n+6) { animation-delay: 200ms; }
/* the sheet over the page, and the three steps in it, come in the same way */
.sheet-up .steps li, .sheet-up .ways > *, .sheet-up .plan3 > *, .sheet-up .tracing > * { animation: riseIn .4s cubic-bezier(.2, .7, .3, 1) both; }
.sheet-up .steps li:nth-child(2), .sheet-up .ways > :nth-child(2), .sheet-up .plan3 > :nth-child(2), .sheet-up .tracing > :nth-child(2) { animation-delay: 60ms; }
.sheet-up .steps li:nth-child(3), .sheet-up .plan3 > :nth-child(3), .sheet-up .tracing > :nth-child(3) { animation-delay: 120ms; }

/* ── the sketch sheet: the room this render is of, measured ── */
/* The drawing and the measurements beside it need a table, not a dialog box — so this sheet is wider
   than the upload one, and it is ONE size from the first step to the last: a sheet that grows as it
   goes moves the buttons under the hand that is reaching for them. It never runs past the screen
   either, so nothing in it is ever below the fold. */
.sheet-up.wide { width: min(1120px, calc(100vw - 40px)); height: min(760px, calc(100vh - 40px)); overflow: hidden; display: grid; grid-template-rows: auto minmax(0, 1fr); align-content: stretch; }
.sheet-up.wide > .ways, .sheet-up.wide > .tracing, .sheet-up.wide > .plan3 { min-height: 0; }
.sheet-up.wide .ways { align-content: center; }
/* the sketch already read stands beside the ways to replace it, and it is the big half: this step is
   where the drawing is checked against the page it was read off */
.ways.again { grid-template-columns: minmax(0, 1.3fr) minmax(0, 1fr); align-items: stretch; }
/* …and the panel is the picture's own box, not the half of the sheet the picture is centred in: the
   mark that takes the sketch off stands in its top corner, and a box the height of the whole sheet
   would have put that mark in the air a hand's width above the page it belongs to. */
.ways.again .paper { position: relative; display: block; place-self: center; width: fit-content; max-width: 100%; min-height: 0; padding: 0; }
.ways.again .paper img { width: auto; height: auto; max-width: 100%; max-height: min(58vh, 520px); border-radius: 14px; display: block; object-fit: contain; }
.ways.again .paper .wipe { position: absolute; top: 8px; right: 8px; z-index: 2; display: grid; place-items: center;
  width: 30px; height: 30px; padding: 0; border: 1px solid rgba(244,239,230,.6); border-radius: 99px;
  background: rgba(18,17,15,.55); color: #f4efe6; font: inherit; font-size: 15px; line-height: 1; cursor: pointer;
  box-shadow: 0 1px 5px rgba(18,17,15,.3); transition: background .15s, transform .12s; }
.ways.again .paper .wipe:hover { background: var(--ink); transform: scale(1.06); }
.ways.again .paper .wipe:disabled { opacity: .5; cursor: default; }
.ways.again .swap { display: grid; gap: 14px; align-content: center; min-height: 0; padding: 0; }
.ways.again .swap > * { display: grid; justify-items: center; align-content: center; gap: 8px; text-align: center; padding: 18px; border-radius: 18px; min-height: 0; }
.ways.again .swap .drop { border: 1.5px dashed var(--faint); }
.ways.again .swap .qr { background: #f6f2ea; }
.ways.again .swap .qr svg, .ways.again .swap .wait-qr { width: 148px; height: 148px; }
/* the drawing takes the room it has; the sheet does not take the room the drawing wants */
.plan3 { display: grid; grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr) minmax(0, .85fr); gap: 24px; align-items: start; text-align: left; }
.plan3 > div { min-height: 0; max-height: 100%; }
.plan3 > div:first-child { display: grid; grid-template-rows: minmax(0, 1fr) auto; align-self: stretch; }
/* the drawing takes the whole box it is given — an SVG with a viewBox fits itself to it, so a plan is
   as large as the sheet can make it rather than a stamp in the middle of a white card */
.plan3 #draw, .tracing #draw { display: grid; place-items: center; min-height: 0; width: 100%; height: 100%; }
.plan3 #draw svg, .tracing #draw svg { width: 100%; height: 100%; }
.plan3 h2 { margin-top: 0; }
/* ── under the drawing: the page it came off, and the two things to do with it ──
   One row, centred, all of it the size of an aside. The strip's own padding is for a strip standing
   alone on a page; here it is one item in a line, so it is cleared and the row spaces itself. The
   thumbs come down to the height of the button beside them, or a 62px picture sets the height of a
   34px row and the line reads as a strip with a button stuck to it. */
.sketchbar { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 10px; padding-top: 14px; }
.sketchbar .strip { width: auto; padding: 0; gap: 6px; }
.sketchbar .thumb { width: 52px; height: 38px; border-radius: 8px; }
.sketchbar .thumb.add { font-size: 16px; }
/* …and the button is as wide as its own words. Left to the column it filled the width of the drawing
   above it, which made the aside look like the point of the sheet. */
.sketchbar .btn.again { width: auto; align-self: center; white-space: nowrap; }
/* a sheet's rows sit closer than a page's: every one of them has to be on the screen at once */
.plan3 .met { padding: 6px 2px; }
.plan3 .met input { padding: 7px 9px; }
.plan3 .qty { padding: 5px 2px; }
.plan3 .qtys { gap: 0; margin-top: 10px; }
.plan3 .note { margin-top: 8px; }
.plan3 .acts { margin-top: 14px; gap: 8px; max-width: none; }
/* on a short screen the second line of every row goes rather than the rows themselves: which wall a
   number was read off is on the drawing beside it, but a wall that is off the bottom is a wall nobody
   fills in */
@media (max-height: 820px) {
  .plan3 .met { padding: 3px 2px; }
  .plan3 .met input { padding: 5px 8px; }
  .plan3 .met .lab small, .plan3 .qty small { display: none; }
  .plan3 .qty { padding: 3px 2px; }
}
/* the fallback for a room of ten walls, and only that: a column gives way quietly rather than the
   sheet growing past the screen */
.plan3 > div:not(:first-child) { overflow: auto; scrollbar-width: none; }
.plan3 > div::-webkit-scrollbar { width: 0; height: 0; display: none; }
/* Three steps, said once at the top: which one they are on, and which are behind them. A sheet that
   changes under you without saying why reads as three different sheets. */
/* Every step is the way to itself as well as the word for it: the sketch it was read from, the drawing
   on its own, and the measurements — checking one against another is what this sheet is for. */
.steps { display: flex; flex-wrap: wrap; gap: 8px 14px; list-style: none; margin-bottom: 26px; padding-right: 34px; }
.steps li button { display: flex; align-items: center; gap: 9px; padding: 4px 10px; border: 0; border-radius: 99px; background: none; font: inherit; font-size: 14px; color: var(--faint); cursor: pointer; }
.steps li button:hover:not(:disabled) { background: var(--paper); color: var(--ink); }
.steps li button:disabled { cursor: default; }
.steps li b { display: grid; place-items: center; width: 24px; height: 24px; border-radius: 99px; border: 1px solid var(--line); font-size: 12px; font-weight: 500; }
.steps li.done button { color: var(--soft); }
.steps li.done b { background: var(--line); border-color: var(--line); color: var(--body); }
.steps li.on button { color: var(--ink); }
.steps li.on b { background: var(--ink); border-color: var(--ink); color: #f4efe6; }
/* being read: the sketch itself and how far along the reading is — never a drawing, until the reading
   has been checked. The photo takes whatever height the sheet has left and no more. */
.tracing { display: grid; grid-template-rows: minmax(0, 1fr) auto auto; justify-items: stretch; align-content: stretch; gap: 14px; }
/* Centred both ways in the space the sheet gives it. A picture is a replaced element: told to stretch
   it takes its own intrinsic height instead and sits at the top of the row, so a sketch half the
   height of the sheet hung from the stepper with the whole rest of the sheet empty under it. */
.tracing .raw { justify-self: center; align-self: center; max-width: 100%; max-height: 100%; border-radius: 14px; display: block; object-fit: contain; }
.tracing .bar, .tracing .note { justify-self: center; }
.tracing .note { margin: 0; }
/* ── read: the page and the drawing, the same size, side by side ──
   The comparison is the step. Two columns of equal width, each filling the height the sheet has left,
   so neither picture is the big one — a drawing shown larger than the page it came off is the app
   asserting the drawing, and this step exists to doubt it. */
.compare { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; min-height: 0; align-items: stretch; }
.compare figure { display: grid; grid-template-rows: minmax(0, 1fr) auto; gap: 8px; min-height: 0; margin: 0; }
.compare figure > a { display: grid; place-items: center; min-height: 0; }
.compare img { max-width: 100%; max-height: 100%; border-radius: 14px; display: block; object-fit: contain; }
.compare figcaption { font-size: 13px; letter-spacing: .02em; color: var(--soft); text-align: center; }
/* the one question of the step, and the two ways of saying no beside it */
.acts.okd { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 12px; margin: 0 auto; }
.acts.okd .btn { width: auto; }
.agreed { font-size: 14px; color: var(--soft); }
/* A reading is minutes long: the bar says how far it has got, and the light travelling across the
   filled part says it is still going even while one stage takes a while. */
.tracing .bar { width: min(420px, 100%); height: 6px; border-radius: 99px; background: var(--line); }
/* The bar crawls between the three places the reading actually reports from: it eases from where the
   reading is to just short of where it is going and then waits there, so a step that takes ninety
   seconds is still visibly moving without the page claiming progress nobody has measured. */
.tracing .bar span { position: relative; display: block; height: 100%; border-radius: 99px; background: var(--ink); overflow: hidden; transition: width .8s ease;
  animation: creep 50s cubic-bezier(.15,.85,.25,1) forwards; }
@keyframes creep { from { width: var(--at, 22%); } to { width: var(--to, 50%); } }
.tracing .bar span::after { content: ''; position: absolute; inset: 0; background: linear-gradient(90deg, transparent, rgba(244,239,230,.65), transparent); animation: sweep 1.5s linear infinite; }
@keyframes sweep { from { transform: translateX(-100%); } to { transform: translateX(100%); } }
.sheet-up .two { gap: 26px; }
.sheet-up .strip { padding-top: 18px; }

/* a photo from the mosaic, opened in the middle of the screen and nowhere else — the page behind it
   is washed out and blurred away, so the one photo is all there is to look at */
.shade.pale { background: rgba(248, 247, 244, .88); backdrop-filter: blur(22px) saturate(.85); -webkit-backdrop-filter: blur(22px) saturate(.85); }
.lit { position: fixed; z-index: 41; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 14px; padding: 40px; animation: fade .18s ease-out; }
.lit figure { position: relative; display: flex; }
/* The picture, larger, with the words to change it centred underneath — off the picture, not on it.
   Lying on the render it covered the corner of the room somebody redrawing the room is most likely
   looking at, and it had to be pushed to one side and kept narrow to stay clear of the caption naming
   the look. Below it there is nothing to dodge, so it is centred and given the width of the words. */
/* the picture and the words under it, one block as wide as the picture came out */
.litbox { display: flex; flex-direction: column; align-items: center; gap: 14px; max-width: 100%; min-height: 0; }
.lit .say { display: flex; align-items: center; gap: 8px; width: min(var(--w, 30rem), 92vw); padding: 8px; border-radius: 18px; background: rgba(18,17,15,.92); box-shadow: 0 10px 30px rgba(18,17,15,.32); }
.lit .say input { flex: 1; min-width: 0; padding: 12px 14px; border: 0; border-radius: 12px; background: none; color: #f4efe6; }
/* The example is set as an example: italic, and further back than the words that will replace it. At
   full upright weight it read as a sentence already typed into the box — somebody's last instruction
   still sitting there — rather than as the shape of the one being asked for. */
.lit .say input::placeholder { color: rgba(244,239,230,.32); font-style: italic; }
/* What went wrong with an empty box, said in the box: "say what to change" is a sentence about the
   words that are missing, and it belongs where they are typed rather than in a second black bar under
   the first one saying the same thing twice as tall. In the same red the errors elsewhere use. */
/* …and that one is not an example: it is the one thing in the box worth reading, so it stands upright
   and at full strength where the example is italic and held back. */
.lit .say input.wrong::placeholder { color: #ffb4a8; font-style: normal; }
.lit .say input:focus { outline: 0; }
.lit .say .btn { padding: 12px 16px; font-size: 14px; background: #fff; color: var(--ink); white-space: nowrap; }
.lit .say .btn:disabled { background: rgba(255,255,255,.72); }
/* the other way the change can land, and keeping what the redraw made, beside the words that made it:
   the further buttons on a dark bar are drawn in the bar's own colours rather than as more white slabs
   competing with the one press this box is mostly for */
.lit .say .btn.save, .lit .say .btn.line { background: none; color: #f4efe6; box-shadow: inset 0 0 0 1.5px rgba(244,239,230,.5); }
.lit .say .btn.save:hover, .lit .say .btn.line:hover { background: rgba(244,239,230,.12); }
.lit .say .btn.line:disabled { background: none; color: rgba(244,239,230,.45); box-shadow: inset 0 0 0 1.5px rgba(244,239,230,.25); }
.lit .say .btn.line .ring { border-color: rgba(244,239,230,.28); border-top-color: #f4efe6; }
/* Where a redraw is to land, asked over the picture in the same two stacked pills the wall asks its
   furniture question in — bigger, because what is under them here is the whole screen and not a thumb
   on a wall, and each answer is a sentence rather than a two-word label. */
/* A shade and no blur: the picture under the question is the thing being decided about, and blurring
   it takes it away at the moment it is most wanted. */
.lit .asks { gap: 10px; padding: 24px; background: rgba(18,17,15,.5); }
.lit .asks .btn { max-width: 21rem; padding: 14px 18px; font-size: 14px; border-radius: 12px; }
/* what went wrong, under the picture it went wrong on */
.lit .lit-err { width: min(var(--w, 30rem), 92vw); margin: 0; padding: 8px 14px; border-radius: 12px; background: rgba(18,17,15,.92); color: #ffb4a8; font-size: 13px; text-align: center; }
/* drawing: the button itself turns, so the words and the picture both stay put */
.lit .say .btn.going { width: 6.4rem; }
.lit .say .btn .ring { width: 18px; height: 18px; border: 2.5px solid rgba(18,17,15,.22); border-top-color: var(--ink); border-radius: 99px; animation: spin 800ms linear infinite; }
/* …and the picture gives up the height the row underneath now takes, so the two together still stand
   inside the screen rather than the words being pushed off the bottom of it */
.lit img { max-width: min(935px, 78vw); max-height: 68vh; border-radius: 18px; box-shadow: 0 24px 70px rgba(18, 17, 15, .28); cursor: zoom-out; }
/* the way out sits on the photo itself, not off in a corner of the page */
.lit .x { position: absolute; top: 12px; right: 12px; width: 34px; height: 34px; border: 0; border-radius: 99px; background: rgba(18, 17, 15, .58); font: inherit; font-size: 15px; line-height: 34px; text-align: center; color: #f4efe6; cursor: pointer; padding: 0; }
.lit .x:hover { background: rgba(18, 17, 15, .82); }
/* Opened off a wall, a look is one of a set: the arrows step to the next room drawn of that same photo
   without going back to the wall to find it. They stand on the picture, over its two edges, where a
   thumb is nearest to hand — and the same chevron drawn once, turned round for the way back. */
.lit .turn { position: absolute; top: 50%; z-index: 2; display: grid; place-items: center; width: 44px; height: 44px; margin-top: -22px; padding: 0; border: 0; border-radius: 99px; background: rgba(18,17,15,.58); cursor: pointer; transition: background .15s; }
.lit .turn:hover { background: rgba(18,17,15,.86); }
.lit .turn svg { width: 26px; height: 26px; fill: none; stroke: #f4efe6; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.lit .turn.back { left: 12px; }
.lit .turn.back svg { transform: rotate(90deg); }
.lit .turn.on { right: 12px; }
.lit .turn.on svg { transform: rotate(-90deg); }
/* …and none of the three stands on the picture until a hand is near it. Blown up to be looked at, the
   render is the whole point of the screen, and a close and two chevrons parked on it are three dark
   discs in front of the thing they were opened to see. They fade in when the picture is hovered, and
   come back for a keyboard that tabs onto them. Nothing is lost by hiding them: the arrow keys step
   the reel whether or not the chevrons are showing, Escape and a click off the picture both close it.
   Only where a pointer can hover — a phone never does, and there they simply stand. */
@media (hover: hover) {
  .lit .x, .lit .turn { opacity: 0; transition: opacity .15s ease, background .15s; }
  .lit figure:hover .x, .lit figure:hover .turn,
  .lit .x:focus-visible, .lit .turn:focus-visible { opacity: 1; }
}
/* which look this is, and how far along the set — on the picture, in the corner the walls caption in */
.lit figcaption { position: absolute; left: 14px; bottom: 14px; display: flex; align-items: baseline; gap: 8px; padding: 6px 13px; border-radius: 9px; background: rgba(18,17,15,.72); color: #f4efe6; font-size: 14px; text-transform: capitalize; }
.lit figcaption small { font-size: 12px; color: rgba(244,239,230,.66); text-transform: none; font-variant-numeric: tabular-nums; }
/* the room as it stands: the one frame of the reel that is a photograph and not a proposal, said in
   white on black at a weight none of the look names are given */
.lit figcaption.was { background: #f4efe6; color: var(--ink); font-weight: 600; font-size: 15px; letter-spacing: .02em; }
.lit figcaption.was small { color: rgba(18,17,15,.55); font-weight: 400; }

/* The page the scanned code opens: one screen, two doors, nothing else. It is held in the middle of
   the phone rather than stacked against the top — there is nothing below it to scroll to, so a page
   that starts at the top leaves half a screen of empty paper under the only two things on it, which
   reads as a page still loading. Centred, the two doors land where the thumb already is. */
.phone .stage { text-align: left; }
.phone { display: flex; flex-direction: column; min-height: 100svh; }
/* the header and the page carry `margin: 0 auto`, which as flex children would shrink them to their
   contents and centre them — the name in the corner went to the middle of the bar */
.phone > .topbar, .phone > main { width: 100%; }
/* …and no skirt of paper at the foot: narrow pages keep 96px clear for the fixed Back / Continue row,
   and this page has no such row — it only pushed the two doors that far above the middle. */
.phone main { flex: 1; display: flex; flex-direction: column; justify-content: center; padding-bottom: 0; }
.drop.big { width: 100%; height: auto; padding: 40px 20px; margin-top: 26px; position: relative; }
.drop.big input { position: absolute; inset: 0; opacity: 0; cursor: pointer; }
.sent { margin-top: 18px; color: #2f6b4f; font-size: 15px; }

/* The two doors themselves, drawn the way this app draws any pair of calls: the one to press is
   filled, the other is ruled in the same ink. Dashed panels the height of a hand said neither which
   to press nor that either was a button — and a dashed box means "drop a file here" everywhere else
   in the app, which is not a thing a phone can do. Each is a row: the mark, then what it does with
   the how underneath, so the words start on the same line as each other and the eye reads down one
   edge rather than hunting the middle of two large empty rectangles. */
.ways2 { display: grid; gap: 12px; margin-top: 30px; }
.way { position: relative; display: grid; grid-template-columns: auto minmax(0, 1fr); align-items: center; gap: 14px; padding: 18px 20px; border-radius: 18px; box-shadow: inset 0 0 0 1.5px var(--ink); color: var(--ink); cursor: pointer; transition: transform .12s ease, box-shadow .12s ease, background .12s ease; }
.way input { position: absolute; inset: 0; opacity: 0; cursor: pointer; }
.way .ico { display: grid; place-items: center; width: 42px; height: 42px; border-radius: 12px; box-shadow: inset 0 0 0 1.5px rgba(18, 17, 15, .16); }
.way .ico svg { width: 21px; height: 21px; fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; }
.way .say { display: grid; gap: 2px; }
.way b { font-size: 17px; font-weight: 500; }
.way small { font-size: 13px; color: var(--soft); }
/* the one to press: filled, so there is never a question which of the two is the way on */
.way.go { background: var(--ink); color: #f4efe6; box-shadow: none; }
.way.go .ico { box-shadow: inset 0 0 0 1.5px rgba(244, 239, 230, .28); }
.way.go small { color: rgba(244, 239, 230, .66); }
.way:active { transform: scale(.985); }
/* sending: both doors are shut until it lands, and say so by going quiet rather than by vanishing */
.way.off { cursor: default; box-shadow: inset 0 0 0 1.5px var(--line); color: var(--faint); }
.way.off .ico { box-shadow: inset 0 0 0 1.5px var(--line); }
.way.off.go { background: var(--faint); color: #f4efe6; box-shadow: none; }
.way.off.go .ico { box-shadow: inset 0 0 0 1.5px rgba(244, 239, 230, .3); }
.way.off:active { transform: none; }
@media (hover: hover) {
  .way:hover { transform: translateY(-1px); }
  .way.off:hover { transform: none; }
}

/* the middle frame is the product in one gesture: the same room, wiped from before to after */
.ba { position: relative; overflow: hidden; border-radius: 20px; }
.ba img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.ba .before { position: absolute; inset: 0; animation: wipe 3.5s ease-in-out infinite alternate; }
.ba .wiper { position: absolute; top: 0; bottom: 0; width: 2px; background: rgba(255, 255, 255, .92); box-shadow: 0 0 0 1px rgba(18, 17, 15, .12); animation: slide 3.5s ease-in-out infinite alternate; }
.ba .tag { position: absolute; bottom: 14px; background: rgba(18, 17, 15, .72); color: #f4efe6; font-size: 13px; padding: 6px 12px; border-radius: 8px; }
.ba .tag.l { left: 14px; }
/* compared, the after side says which render it is, and is the way back to the others when the photo
   has more than one — a wipe names its two pictures rather than leaving one of them to be guessed. */
.ba .tag.r { right: 14px; text-transform: capitalize; }
.ba button.tag { display: inline-flex; align-items: center; gap: 5px; border: 0; font: inherit; font-size: 13px; cursor: default; }
/* the chevron on the line of the word, not under it */
.ba .tag .chev { width: 11px; height: 11px; fill: none; stroke: currentColor; stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round; }
.ba button.tag.pick { cursor: pointer; }
.ba button.tag.pick:hover { background: var(--ink); }
/* Edge to edge, and at twice the pace. It used to run between 22% and 82%, which never showed either
   room whole: the after was always cut off down the left and the before down the right, so the one
   thing the picture is for — this room, then that room — was the one thing it never quite said. And
   over seven seconds a sixty-percent crawl reads as a page that has stopped rather than as a wipe. */
@keyframes wipe { from { clip-path: inset(0 100% 0 0); } to { clip-path: inset(0 0 0 0); } }
@keyframes slide { from { left: 0%; } to { left: 100%; } }

.how { display: grid; grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); gap: 28px; margin-top: 56px; text-align: left; }
.how h4 { font-size: 12px; font-weight: 400; letter-spacing: .12em; text-transform: uppercase; color: var(--soft); margin-bottom: 8px; }
.how p { font-size: 16px; color: var(--body); }

/* ── the header: said out loud where there is room, closed into one mark where there is not ── */
/* Signed out, the wide bar is the three links in the middle and the way in on the right — three
   children, which is what the 1fr auto 1fr above is for. The mark takes their place on a phone. */
.burger { position: relative; justify-self: end; display: none; }
@media (max-width: 760px) {
  .topbar nav.wide, .topbar > .btn.small.wide { display: none; }
  .burger { display: block; }
  /* two children now, so the mark is on the right rather than in the middle of three columns */
  .topbar:not(:has(.mid)):has(.burger) { grid-template-columns: auto 1fr; }
}
.burger .bars { display: grid; place-items: center; width: 40px; height: 40px; padding: 0; background: none; border: 1px solid var(--line); border-radius: 12px; cursor: pointer; }
.burger .bars svg { width: 18px; height: 18px; fill: none; stroke: var(--ink); stroke-width: 1.6; stroke-linecap: round; }
/* Only where there is a pointer to hover with: on a phone the darkened edge sticks after the tap,
   which reads as a box drawn round the mark rather than as a press. */
@media (hover: hover) { .burger .bars:hover { border-color: var(--ink); } }
/* …and on a phone the mark stands on its own — the three lines are the button, no box round them. */
@media (max-width: 760px) { .burger .bars { border: 0; width: 34px; height: 34px; } }
.burger .menu { position: absolute; right: 0; top: calc(100% + 8px); z-index: 30; display: grid; min-width: 13rem; padding: 8px; background: #fff; border: 1px solid var(--line); border-radius: 14px; box-shadow: var(--lift); opacity: 0; pointer-events: none; transform: translateY(-4px); transition: opacity .14s ease, transform .14s ease; }
.burger.open .menu { opacity: 1; pointer-events: auto; transform: none; }
.burger .menu a { padding: 9px 10px; border-radius: 10px; font-size: 15px; color: var(--body); text-decoration: none; }
.burger .menu a:hover { background: var(--paper); color: var(--ink); }
.burger .menu a.in { margin-top: 4px; background: var(--ink); color: #f4efe6; text-align: center; }

/* ── the work on the home page: a gallery where there is room for one, a reel where there is not ── */
.reelshow { display: none; margin-top: 48px; }
@media (max-width: 760px) { .gallery.wide { display: none; } .reelshow { display: block; } }
/* The middle of the three is the pair, and it is the tallest thing in the row, so the two stills
   beside it are cropped to match rather than the row growing to hold them. */
.gallery.wide > .ba { aspect-ratio: 4 / 3.4; }
/* The pictures take the width they are given: three across, the two stills upright and the pair
   between them wider, which is the proportion the row is drawn to. But a desktop window is wide and
   short, so those columns grew while the screen did not, and the upright stills ran off the bottom of
   the fold — the proof the page is making, drawn and then cut in half by the edge of the screen.
   The row is held to what the screen has left under the pitch, and the pictures are cropped into it.
   Cropped, not shrunk: this is the same three rooms at the same widths in a shorter row, which is a
   different thing from the row of thumbnails that scaling them down would have made. Floored, so a
   short window gets a letterbox rather than a strip, and capped, so a tall one does not get a poster. */
/* The height is given to each picture outright rather than as a hundred percent of the row: a grid
   track that sizes to its content is not a definite height to be a percentage of, so the images kept
   their own and overflowed a row that had already been told how tall to be. */
.gallery.wide { --row: var(--gh, clamp(300px, calc(100svh - 470px), 520px)); grid-template-rows: var(--row); align-items: stretch; }
.gallery.wide > img, .gallery.wide > .ba { height: var(--row); aspect-ratio: auto; }
/* The middle one sits a little short of the two beside it, centred in the gap it leaves. It is the
   only one of the three that moves, and a wipe running the full height of the row read as the row
   itself rather than as the one panel doing something — held in slightly, it is a picture on the page
   with the stills standing either side of it, which is the arrangement the eye lands on first. */
.gallery.wide > .ba { height: calc(var(--row) * .92); align-self: center; }
/* One frame wide, scrolled rather than redrawn, and snapping so it never rests between two rooms. */
.frames { display: grid; grid-auto-flow: column; grid-auto-columns: 100%; overflow-x: auto; scroll-snap-type: x mandatory; scrollbar-width: none; border-radius: 20px; }
.frames::-webkit-scrollbar { display: none; }
.frames > .ba { scroll-snap-align: start; aspect-ratio: 16 / 10; }
/* the count, under the picture and out of its way */
.bubbles { display: flex; justify-content: center; gap: 8px; margin-top: 18px; }
.bubbles button { width: 8px; height: 8px; padding: 0; border: 0; border-radius: 99px; background: none; box-shadow: inset 0 0 0 1.5px var(--faint); cursor: pointer; transition: box-shadow .15s, background .15s; }
.bubbles button:hover { box-shadow: inset 0 0 0 1.5px var(--ink); }
.bubbles button.on { background: var(--ink); box-shadow: none; }
@media (max-width: 760px) { .frames > .ba { aspect-ratio: 4 / 3; } }

/* ── the sections the header points at ────────────────── */
/* They are jumped to from the top bar, so they glide rather than cut, and each stops clear of the
   top edge instead of hard against it. */
html { scroll-behavior: smooth; }
#how, #work, #gains, #mission, #start { scroll-margin-top: 32px; }
/* Centred blocks under a centred hero read as more of the same page; these are editorial instead —
   a label held in the margin, one oversized statement, the detail beneath it in a rhythm of
   hairlines. The asymmetry is what stops them reading as filler. */
.band { margin-top: 120px; text-align: left; display: grid; grid-template-columns: minmax(8rem, 1fr) minmax(0, 4fr); gap: 0 48px; }
.band > .mark { position: sticky; top: 96px; align-self: start; padding-top: 6px; font-size: 12px; letter-spacing: .14em; text-transform: uppercase; color: var(--soft); }
.band > .body { border-top: 1px solid var(--ink); padding-top: 26px; }
.say { font-size: clamp(26px, 3.4vw, 46px); line-height: 1.12; letter-spacing: -.03em; font-weight: 500; max-width: 24ch; }
.say em { font-style: normal; color: var(--soft); }
.after-say { margin-top: 20px; color: var(--body); max-width: 46ch; }

/* icon-led list: the mark sits out in the margin, the rule carries the eye across */
.points { margin-top: 56px; display: grid; gap: 0; }
.points > div { display: grid; grid-template-columns: 4.5rem 1fr; gap: 28px; padding: 26px 0; border-top: 1px solid var(--line); align-items: center; }
.points > div:last-child { border-bottom: 1px solid var(--line); }
.points .fig { display: grid; justify-items: start; align-content: center; }
.points .fig svg { width: 44px; height: 44px; fill: none; stroke: var(--ink); stroke-width: 1.35; stroke-linecap: round; stroke-linejoin: round; }
.points h4 { font-size: 19px; font-weight: 500; letter-spacing: -.01em; margin-bottom: 6px; }
.points p { color: var(--body); max-width: 52ch; }

/* the close: ink, one line, one button, and the terms set quietly beside it */
.close { background: var(--ink); color: #f4efe6; border-radius: 24px; padding: 56px; margin-top: 120px; text-align: left; display: grid; grid-template-columns: minmax(0, 3fr) minmax(0, 2fr); gap: 40px; align-items: end; }
.close h2 { font-size: clamp(26px, 3.2vw, 42px); font-weight: 500; letter-spacing: -.03em; line-height: 1.12; margin: 0; text-transform: none; color: #f4efe6; }
.close .btn { background: #f4efe6; color: var(--ink); margin-top: 26px; }
.close dl { display: grid; grid-template-columns: auto 1fr; gap: 10px 18px; font-size: 14px; margin: 0; }
.close dt { color: rgba(244, 239, 230, .55); }
.close dd { margin: 0; color: #f4efe6; }

@media (max-width: 820px) {
  .band { grid-template-columns: 1fr; gap: 14px; }
  .band > .mark { position: static; }
  .points > div { grid-template-columns: 2.5rem 1fr; gap: 16px; }
  .points .fig svg { width: 34px; height: 34px; }
  .close { grid-template-columns: 1fr; padding: 36px; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .ba .before { clip-path: inset(0 50% 0 0); }
  .ba .before, .ba .wiper, .wait { animation: none; }
  .ba .wiper { left: 50%; }
  /* the searching bar still has to say the row is working, so it stays — as a filled track, held still */
  .el em .bar > span { width: 100%; opacity: .5; animation: none; }
  /* a board landing on an open wall arrives, it does not rise, and an aisle changes outright */
  .fitwall .gall.came figure { animation: none; }
  .fitwall .gall { transition: none; }
  .fitwall .gall.lanes .lanebox .lane.off { animation: none; opacity: 0; }
  .fitwall .gall.lanes.turned .lanebox:nth-child(2) figure,
  .fitwall .gall.lanes.turned .lanebox:nth-child(2) .note { animation: none; }
}

/* ── narrow screens ─────────────────────────────────────── */
@media (max-width: 1180px) {
  /* the bill keeps its width and the render gives up its own: a listing is a photograph, a shop's
     name and a price per square foot on one line, and it is the thing that stops being readable first */
  .roomset { grid-template-columns: minmax(0, .8fr) minmax(21rem, 1fr); gap: 22px; }
}

/* ── phones ───────────────────────────────────────────── */
/* Nothing on a phone is allowed to be wider than the phone: one thing wider than the screen and the
   browser zooms the whole page out to fit it, and every word on every page gets smaller for the sake
   of that one row. So each page here is stacked down the screen rather than shrunk to fit across it. */
@media (max-width: 760px) {
  body { font-size: 15px; }
  .topbar, main, .foot .cols, .foot .fine { padding-left: 20px; padding-right: 20px; }
  .topbar { grid-template-columns: 1fr auto; gap: 10px; padding-top: 16px; padding-bottom: 22px; }
  /* signed out: the name, the two links and the way in, all on the one line */
  /* signed out on a phone: the name on the left, the mark on the right, nothing between them */
  .topbar:not(:has(.mid)) { grid-template-columns: auto 1fr; }
  .topbar nav { gap: 16px; }
  .topbar nav a { font-size: 13px; }
  .topbar .btn.small { padding: 9px 14px; font-size: 13px; }
  /* the name and the account on one line, the stages on their own under them */
  .topbar:has(.mid) { grid-template-columns: auto auto; grid-template-areas: "logo who" "mid mid"; row-gap: 12px; }
  .topbar:has(.mid) .logo { grid-area: logo; }
  .topbar:has(.mid) .who { grid-area: who; }
  .mid { grid-area: mid; justify-self: stretch; }
  .stages { justify-content: space-between; width: 100%; overflow-x: auto; scrollbar-width: none; }
  .stages::-webkit-scrollbar { width: 0; height: 0; display: none; }
  .stages button { font-size: 11px; padding: 4px 6px; }
  .stages li + li::before { width: 8px; margin: 0 4px; }
  .two, .job { grid-template-columns: 1fr; }
  /* On a phone the three rooms are not a row and not a stack: they are one card at a time, swiped.
     The wipe leads — it is the only one of the three that says what the product does rather than what
     it makes, so it is the first thing seen and the reason to push on to the next. Stacked, it sat
     second, under a photograph that could have come from anywhere, and most thumbs never reached it.
     Swiped rather than timed: a card that moves off on its own moves off mid-look. */
  /* a card and a sliver of the next one: the only honest way to say there is more to the side */
  .gallery { grid-auto-flow: column; grid-auto-columns: 88%; grid-template-columns: none;
    gap: 12px; overflow-x: auto; scroll-snap-type: x mandatory; overscroll-behavior-x: contain;
    scrollbar-width: none; margin-left: -20px; margin-right: -20px; padding: 0 20px; }
  .gallery::-webkit-scrollbar { width: 0; height: 0; display: none; }
  .gallery > * { scroll-snap-align: center; }
  .gallery img, .gallery > :nth-child(2) { aspect-ratio: 4/3; }
  /* the wipe first, the rooms after it — said in the order they are laid out, not in the markup, so
     the page reads the same way round on a desktop as it always did */
  .gallery > .ba { order: -1; }
  /* and the tags on it, at the size a phone can carry them */
  .gallery .ba .tag { bottom: 10px; font-size: 12px; padding: 5px 10px; }
  .gallery .ba .tag.l { left: 10px; }
  .gallery .ba .tag.r { right: 10px; }
  .mosaic { columns: 2; column-gap: 14px; }
  /* two places across on a phone, still all one shape */
  .mosaic.places { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; }
  .mosaic .tile { margin-bottom: 14px; }
  /* on a phone the sheet goes under the room it is of, still one room after another */
  .mosaic.paired { grid-template-columns: minmax(0, 1fr); gap: 14px; }
  .roomset { grid-template-columns: minmax(0, 1fr); gap: 14px; }
  /* Stacked, there is no second column to pin the bill beside: picture, stepping, bill, in that
     order down the page, each taking the row it falls into. */
  .roomset .tile.saved, .roomset .reeler, .roomset .bill { grid-area: auto; }
  /* …and stacked, the render is not competing with a bill for the fold, so it keeps its whole square:
     the cap exists to stop one row overflowing the screen, and here there is no such row. */
  .mosaic.paired .tile.saved img { max-height: none; }
  /* Beside the render the bill is a pane of its own height, filled from the top and scrolled inside
     itself. Stacked under it there is nothing to take that height from, and a sheet laid out inside
     an absolute frame with nothing to stretch it came out exactly nothing high: the elements, the
     shopping list and the total were all there and none of them could be seen. Down the page it is
     an ordinary block that is as tall as what is in it. */
  .roomset .bill { position: static; }
  .roomset .bill .billstack { position: static; inset: auto; }
  .roomset .bill .sheet { position: static; inset: auto; }
  /* …and with nothing positioned to hang off, the switch goes back inside the sheet, at its head. */
  .modes.wide { position: static; margin: 0 0 14px auto; }
  .roomset .sheet .els, .roomset .sheet .bom { overflow: visible; flex: none; }
  .mosaic.paired .tile { margin-bottom: 0; }
  .acts { max-width: none; }
  .acts.row { right: 20px; bottom: 16px; }
  .cta.pinned { right: 20px; bottom: 16px; gap: 10px; }
  .home:has(> .cta.pinned) { padding-bottom: 74px; }
  .shots { display: grid; grid-template-columns: 1fr 1fr; }
  .shot img, .drop { width: 100%; }
  .sheet-up { padding: 22px; max-height: 88vh; overflow: auto; }
  .sheet-up .ways { grid-template-columns: 1fr; }
  .sheet-up .ways > * { min-height: 0; padding: 20px; }

  /* a row of rooms: less paper each side and a thinner seam, so the pictures get the screen */
  :root { --gut: 12px; }
  .pair:not(.one) > figure:first-child { margin-right: 32px; box-shadow: 44px 0 0 var(--paper); }
  .pair:not(.one) > figure:first-child::after { right: -22px; }
  /* On a phone the wall goes under the photograph rather than beside it: half a screen each left the
     room too small to read and the thumbs too small to tell apart. The seam between them lies down
     with it — it is the line between what the room is and what it could be either way. */
  .pair.wall { grid-template-columns: minmax(0, 1fr); gap: 18px; }
  .pair.wall > figure:first-child { margin: 0 auto; box-shadow: none; }
  /* stacked, the words go under the seam and under the middle of the plate they belong to */
  .pair.wall.coming .said { text-align: center; }
  /* …and it lies in the gutter between them, not across whatever is under it: at sixteen below a plate
     with fourteen of air beneath it the line was drawn two pixels into the next row, straight through
     the words of a room being drawn and through the top of the first thumbnail on every other row. */
  .pair.wall > figure:first-child::after { top: auto; bottom: -11px; left: 12.5%; right: 12.5%; width: auto; height: 4px; }
  .pair.wall .gall { grid-template-columns: repeat(auto-fill, minmax(7rem, 1fr)); gap: 8px; }
  /* The badges on a picture, at the size a finger can hit. They used to be made smaller here on the
     grounds that the picture is smaller — which is backwards: a pointer lands on a 28px mark exactly,
     a fingertip covers three of them. Shrunk, the broom was pressed at and missed, and a button that
     is missed every time is indistinguishable from a button that does nothing. */
  .keep { width: 38px; height: 38px; right: 8px; top: 8px; bottom: auto; }
  .keep svg { width: 22px; height: 22px; }
  .bin, .broom { width: 38px; height: 38px; }
  .go.again { right: 8px; bottom: 8px; padding: 8px 13px; font-size: 14px; }
  .mosaic .tile > .tick { width: 38px; height: 38px; font-size: 17px; }
  .broom svg { width: 19px; height: 19px; }
  .bin { top: 8px; left: 8px; }
  .broom { bottom: 8px; }
  .pair figure > .broom, .ba > .broom { right: 8px; }
  /* …and on the styles page the broom goes altogether. That row of pictures is scrolled sideways with
     a thumb, so a badge sitting in the corner of the one being swiped is a badge pressed by accident —
     and this one empties the room, which costs a render and fifteen seconds to find out. It is not
     lost: the same broom is on every photo in the library, where there is nothing to swipe past. */
  .pair > figure.was > .broomer { display: none; }
  /* the name of the look, cut to the room it has: on a panel this narrow it ran under the button in
     the other corner and the two read as one smudge */
  .pair figcaption { max-width: 56%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .pair figcaption, .ba .tag { font-size: 11px; padding: 4px 8px; left: 8px; bottom: 8px; }
  .pair > figure.was > .kind { font-size: 11px; padding: 4px 8px; left: 8px; bottom: 8px; }
  /* the fixed Back / Continue row stands on the page: the last thing on it keeps clear of them */
  main { padding-bottom: 96px; }
  .ba .tag.l { left: 8px; }
  .ba .tag.r { right: 8px; }
  .pending .go { right: 8px; bottom: 8px; }

  /* The sketch sheet fills the phone and stacks: drawing, then measurements, then the takeoff. One
     screen cannot hold all three the way a desk can, so here the sheet itself is the thing that
     scrolls — held to one fixed height it squeezed the three into a third of the screen each. */
  .sheet-up.wide { display: block; width: calc(100vw - 16px); height: auto; max-height: calc(100dvh - 16px); padding: 20px 16px; overflow: auto; }
  .plan3 { align-content: start; }
  .tracing { grid-template-rows: auto auto auto; align-content: start; }
  /* …and the two pictures go under one another rather than into half a phone each: side by side on
     this width neither of them is legible, and an unreadable comparison is worse than a scroll. */
  .compare { grid-template-columns: 1fr; }
  .steps { gap: 6px; margin-bottom: 18px; }
  .steps li button { padding: 4px 8px; }
  .steps li:not(.on) button { font-size: 0; gap: 0; }   /* the number alone, until it is the step you are on */
  .plan3, .ways.again { grid-template-columns: minmax(0, 1fr); gap: 18px; }
  .plan3 > div, .plan3 > div:first-child { max-height: none; overflow: visible; display: block; }
  /* the drawing takes its own shape here rather than the height of the box it is in — filling it left
     the strip of sketches under it standing on the measurements */
  .plan3 #draw, .tracing #draw { height: auto; }
  .plan3 #draw svg, .tracing #draw svg { width: 100%; height: auto; }
  .plan3 .qtys { grid-template-columns: 1fr; }
  .ways.again .paper img { max-height: 44vh; }
  .ways.again .swap { gap: 12px; }

  /* the bill on a phone: the piece and its price on one line, where it is bought under them, so no
     name is squeezed into a third of the screen and wrapped three deep */
  .bom .el { grid-template-columns: 1fr auto; gap: 4px 14px; }
  .bom .el .shop { grid-column: 1 / -1; text-align: right; }

  /* one measurement to a line: three boxes across left nothing legible for the wall's own name */
  .met { grid-template-columns: 1fr 4.6rem 4.2rem 18px; gap: 8px; }
  .met .lab { font-size: 14px; }
  .met input { padding: 8px; font-size: 14px; }

  /* a photo opened big: the screen it is on, less the room the words underneath need */
  .lit { padding: 14px; }
  .lit img { max-width: 94vw; max-height: 64vh; }
  /* under the picture here too, at the width of the screen */
  .lit .say { width: 94vw; }
}

@media print {
  .topbar, .acts, .foot, .btn, .strip { display: none !important; }
  body { background: #fff; }
  .sheet { border: 0; padding: 0; }
}

/* ── Pinterest ─────────────────────────────────────────
   The board a client has already sent, opened over the wall it is going to be drawn into. It is a
   picture-picking sheet rather than a form, so it is the widest one here and nearly all of it is the
   pictures: the query above them, what has been taken below them, and the two ways it can land at the
   foot. The wall scrolls inside the sheet, because the sheet may not grow past the screen. */
.ways .btn.pin { display: inline-flex; align-items: center; gap: 8px; background: none; border: 1px solid var(--line); color: var(--ink); padding: 8px 16px; font-size: 13px; }
.ways .btn.pin:hover:not(:disabled) { border-color: var(--ink); background: none; }
.ways .btn.pin svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }

/* the sheet is one column of bands — the title, the search, the wall, what has been taken, the two
   ways it can land — and every gap between them belongs to the grid rather than to whichever band
   happens to be there, so a wall with nothing under it is spaced like a wall with pins taken off it */
/* …and it never grows past the screen: the sheet is as tall as there is room for, and the wall is the
   band that gives way, because a foot pushed off the bottom of a fixed sheet cannot be scrolled to —
   the two buttons the whole sheet is for would simply not be there on a shorter laptop */
.sheet-up.pinboard { width: min(980px, calc(100vw - 40px)); max-height: calc(100vh - 40px); display: flex; flex-direction: column; gap: 16px; overflow-y: auto; overflow-x: hidden; }
/* Nothing here is ever read sideways: the sheet goes down and only down, and every band inside it
   wraps rather than running off its own right edge. And the bar that says so is hidden — the wall
   underneath is a wall of photographs, and a grey rail drawn down the side of it is the only part of
   this sheet that is not somebody's room. */
.sheet-up.pinboard, .pinwrap, .pinto .thumbs { scrollbar-width: none; -ms-overflow-style: none; }
.sheet-up.pinboard::-webkit-scrollbar, .pinwrap::-webkit-scrollbar, .pinto .thumbs::-webkit-scrollbar { width: 0; height: 0; display: none; }
.sheet-up.pinboard .pintop { padding-right: 34px; }
.sheet-up.pinboard h3 { font-size: 20px; font-weight: 600; letter-spacing: -.01em; margin-bottom: 6px; }
.sheet-up.pinboard .note { max-width: 74ch; font-size: 13px; line-height: 1.55; color: var(--soft); margin: 0; }
.sheet-up.pinboard .say { display: flex; gap: 10px; }
.sheet-up.pinboard .say input { flex: 1; min-width: 0; }

/* Which picture on the job the pins land in, read as a styles row is read: the picture being worked on
   large at the head of the band, the seam, and every other picture on the job past it to swap for. */
.pinto { display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: 44px; align-items: start; }
/* relative, not static: the seam is drawn off this figure and hangs somewhere else without. The
   corners are rounded on the picture rather than by clipping the figure, or the seam is clipped too. */
.pinto .now { position: relative; margin: 0; width: 148px; aspect-ratio: 4/3; }
.pinto .now img { width: 100%; height: 100%; object-fit: cover; border-radius: 10px; background: #efece6; }
.pinto .now::after { content: ''; position: absolute; top: 12.5%; bottom: 12.5%; right: -24px; width: 4px; background: var(--faint); }
/* what that picture is, worn on it — the same dark plate a room wears its name in on the styles page */
.pinto .now figcaption { position: absolute; left: 8px; bottom: 8px; max-width: calc(100% - 16px); padding: 4px 9px; border-radius: 7px; background: rgba(18, 17, 15, .72); color: #f4efe6; font-size: 11px; line-height: 1.3; text-transform: capitalize; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pinto .rest { display: grid; gap: 8px; min-width: 0; }
.pinto .lbl { color: var(--soft); font-size: 13px; }
/* …and they wrap, however many there are: a job with five photos and six looks over each of them is a
   strip nobody could push sideways with a mouse */
.pinto .thumbs { display: flex; flex-wrap: wrap; gap: 6px; max-height: 116px; overflow-y: auto; }
.pinto button { flex: 0 0 auto; width: 46px; height: 34px; padding: 0; border: 0; border-radius: 7px; background: #efece6; box-shadow: inset 0 0 0 1.5px var(--line); overflow: hidden; cursor: pointer; transition: box-shadow .15s; }
.pinto button:hover { box-shadow: inset 0 0 0 1.5px var(--faint); }
.pinto button.on { box-shadow: inset 0 0 0 2.5px var(--ink); }
.pinto img { display: block; width: 100%; height: 100%; object-fit: cover; }

/* the wall itself: a masonry of pins, so a tall pin stays tall — cropping every one of them square
   is a wall of couches with their arms cut off, which is the one part being chosen */
.pinwrap { flex: 1 1 auto; min-height: 110px; max-height: min(46vh, 420px); overflow-y: auto; padding-right: 4px; }
/* the bands around it hold their height; only the wall gives */
.sheet-up.pinboard > :not(.pinwrap) { flex: 0 0 auto; }
.pinwall { columns: 5 150px; column-gap: 10px; }
/* an empty wall says why it is empty, in the space the pins would have been: waiting, or nothing back */
.pinstate { display: grid; place-content: center; min-height: 250px; margin: 0; border-radius: 14px; background: #faf7f1; color: var(--soft); font-size: 14px; text-align: center; padding: 0 24px; }
.pinstate.sad { color: #9b2c1f; background: #fdeeea; }
.pinwall figure { position: relative; break-inside: avoid; margin: 0 0 10px; border-radius: 10px; overflow: hidden; background: #efece6; cursor: pointer; }
.pinwall img { display: block; width: 100%; height: auto; }
.pinwall figcaption { position: absolute; inset: auto 0 0; padding: 18px 8px 7px; background: linear-gradient(transparent, rgba(18, 17, 15, .72)); color: #f4efe6; font-size: 11px; line-height: 1.35; opacity: 0; transition: opacity .15s; }
.pinwall figure:hover figcaption { opacity: 1; }
.pinwall figure:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }
/* taken: the mark that says so, and the picture dimmed under it so the wall reads at a glance */
.pinwall .tick { position: absolute; top: 7px; right: 7px; display: grid; place-items: center; width: 21px; height: 21px; border-radius: 99px; background: rgba(255, 255, 255, .82); box-shadow: inset 0 0 0 1.5px var(--line); }
.pinwall .took .tick { background: var(--ink); box-shadow: none; }
.pinwall .took .tick::after { content: '✓'; color: #f4efe6; font-size: 12px; line-height: 1; }
.pinwall .took { box-shadow: inset 0 0 0 3px var(--ink); }
.pinwall .took img { opacity: .82; }
/* the next page, arriving because the wall was scrolled to the end of this one */
.pinmore { margin: 2px 0 0; padding-bottom: 4px; color: var(--soft); font-size: 13px; text-align: center; }
/* an error about the press rather than about the search: a line, not a slab */
.sheet-up.pinboard .err.small { margin: 0; padding: 9px 14px; font-size: 13px; }

/* what has been taken, and the one thing each of them still needs said: which part of the pin */
.pintook { display: grid; gap: 8px; padding-top: 16px; border-top: 1px solid var(--line); }
.pintook .ask { margin: 0 0 2px; color: var(--soft); font-size: 13px; line-height: 1.5; }
.pintook label { display: grid; grid-template-columns: 46px 1fr 24px; align-items: center; gap: 10px; }
.pintook img { width: 46px; height: 46px; border-radius: 7px; object-fit: cover; background: #efece6; }
.pintook input { width: 100%; }
.pintook .x { position: static; border: 0; background: none; font: inherit; font-size: 14px; color: var(--soft); cursor: pointer; }
.pintook .x:hover { color: var(--ink); }

/* the foot: what is taken on the left, and the two ways it can land side by side on the right, which
   is the whole choice this sheet ends in. Ruled off from the wall, unless the taken pins above it have
   already drawn that rule. */
.sheet-up.pinboard .tally { margin: 0; padding-top: 16px; border-top: 1px solid var(--line); justify-content: flex-end; }
.sheet-up.pinboard .tally .link.off { margin-right: auto; }
.sheet-up.pinboard .pintook + .tally { padding-top: 0; border-top: 0; }
.pinacts { display: flex; align-items: center; gap: 10px; }
/* …and the softer of the two is a button beside the other, not an underlined link inside a box */
.sheet-up.pinboard .btn.ghost { text-decoration: none; }

/* A short laptop, with three pins taken and every band on the sheet at once: the sheet still ends in
   its two buttons rather than scrolling to reach them. Everything gives a little — the plate, the strip
   of other pictures, the taken pins, the gaps — because the alternative is the wall giving all of it. */
@media (max-height: 820px) {
  .sheet-up.pinboard { padding: 22px 24px; gap: 12px; }
  .pinto .now { width: 112px; }
  .pinto .thumbs { max-height: 76px; }
  .pinwrap { min-height: 96px; }
  .pintook { padding-top: 12px; }
  .pintook label { grid-template-columns: 38px 1fr 24px; }
  .pintook img { width: 38px; height: 38px; }
  .sheet-up.pinboard .tally { padding-top: 12px; }
}

/* On a short screen the words go first: what the sheet is for is read once, and the two buttons it
   ends in have to be under the hand without scrolling for them. */
@media (max-height: 780px) {
  .sheet-up.pinboard { padding: 22px 26px; }
  .sheet-up.pinboard .note { display: none; }
}

@media (max-width: 720px) {
  .pinwall { columns: 2 120px; }
  .pinstate { min-height: 200px; }
  .pinwrap { max-height: 40vh; }
  /* the picture being worked on and the strip of others stack, seam and all */
  .pinto { grid-template-columns: minmax(0, 1fr); gap: 12px; }
  .pinto .now::after { display: none; }
  .sheet-up.pinboard .tally { flex-wrap: wrap; }
  .pinacts { flex: 1 1 100%; justify-content: flex-end; }
}
