/* aisle.css -- The Shortlist prototype's own components, and
 * nothing else. Loaded by one page, lab/inspiration-to-aisle.html, after
 * style.css, for the same reason dispatch-cockpit.css is its own file:
 * reach. Nothing on any other page is a phone-sized shopping companion.
 * Until 2026-10-29 it was prototype/aisle.css, on a page of the
 * prototype's own; the page around the phone, and how the phone sits in
 * its hero, are inspiration-to-aisle.css's.
 *
 * WHAT IT INHERITS. The page is a style.css page: the hero, the chapter
 * grounds, the measure, .btn and .btn-primary, the global focus ring on
 * links and buttons, and the reduced-motion blanket all come from there.
 * This file adds the phone, the screens inside it, and the few controls
 * the app has that style.css does not: the chips, the back button, the
 * comparison table.
 *
 * EVERY SIZE IS A STEP, every space a --space-*, every color a token and
 * every duration a --motion-*. The lamps are pictures, not
 * drawn in CSS, and no text ever sits on them.
 *
 * NO MEANING IN COLOR ALONE. A match's closeness is a sentence ("Close
 * match", "A stretch") before it is a color; a stale count says "last
 * counted 2 days ago" in the caution ink, and the words carry it on a
 * monochrome screen. The caution ground is not spent here: nothing on these
 * screens is a refusal.
 *
 * NO TRACKED CAPITALS. The screen titles, the progress line's two halves,
 * the panel labels and the table's heads are sentence case at their own
 * size (TYPOGRAPHY.md, "Tracked capitals never name a group").
 *
 * STATE IS ON ATTRIBUTES. aria-pressed on the chips, aria-current on the
 * progress line, [data-saved] on a card, and hidden on the comparison,
 * because scripts/states.mjs reads an .is-* class as a state to force, and
 * every state here is reached instead through scripts/lib/reachable.mjs.
 *
 * THE RATIOS, from COLOR.md section 2, on the grounds they sit on:
 *   charcoal     on white 15.33, warm 14.02, tea-light 12.90
 *   muted-gray   on white  6.47, warm  5.91, tea-light  5.44
 *   accent-text  on white  5.82, warm  5.32, tea-light  4.90
 *   caution-text on white  6.45, warm  5.90, tea-light  5.43
 *   warm         on accent-text 5.32, on accent-hover 6.90
 *   a chip's border, muted-gray on white: 6.47 (a control boundary owes 3:1)
 *   a card's edge, --rule-card over white on white: 1.48 (a card owes 1.2)
 */

/* ---------- The phone ----------
   The same object the build write-ups frame their captures in: one --bezel
   of --device-chrome, the inner corner derived from the outer, --shadow-
   device under it (style.css, "Device chrome"). 24rem is a large phone; on
   a viewport narrower than that the frame takes the container's width and
   the screens were designed to hold at 311px, the width left inside the
   bezel at 375. The 42rem is the least a screen is, and from 64rem, in the
   write-up's hero, the most as well (inspiration-to-aisle.css). */
.ai-frame {
  --shell-radius: 26px;
  width: 100%;
  max-width: 24rem;
  margin: 0 auto;
  padding: var(--bezel);
  background: var(--device-chrome);
  border-radius: var(--shell-radius);
  box-shadow: var(--shadow-device);
}
.ai-app {
  min-height: 42rem;
  padding: var(--space-lg) var(--space-md) var(--space-xl);
  background: var(--color-white);
  border-radius: calc(var(--shell-radius) - var(--bezel));
  color: var(--color-charcoal);
  font-family: var(--font-sans);
  font-size: var(--text-base);
  line-height: var(--leading-ui);
}

/* The app's name and the two halves of the trip, over every screen. The
   halves are a progress line, not a navigation: the one the reader is in
   takes the sage and a 2px bar; the other stays quiet. */
.ai-bar {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-md);
  margin-bottom: var(--space-lg);
  padding-bottom: var(--space-sm);
  border-bottom: 1px solid var(--rule-hairline);
}
.ai-brand {
  margin: 0;
  font-size: var(--text-sm);
  font-weight: var(--weight-bold);
  letter-spacing: var(--tracking-normal);
  color: var(--color-charcoal);
}
/* The app's icon before its name, since 2026-10-29: the homepage card's own
   (style.css, "The app's identity"), Phosphor's list with a heart, its
   window the whole charcoal tile since 2026-10-29, drawn in code so it
   takes the site's inks. It is 1lh
   square and sits at the top of the line, so it fills the name's line box:
   centred on the name by construction, and the bar keeps its height. The
   name beside it is the text, so the svg is aria-hidden. */
.ai-brand-icon {
  display: inline-block;
  width: 1lh;
  height: 1lh;
  margin-right: var(--space-xs);
  vertical-align: top;
}
.ai-brand-icon rect { fill: var(--color-charcoal); }
.ai-brand-icon path { fill: var(--color-warm); }
.ai-progress {
  display: flex;
  gap: var(--space-md);
  margin: 0;
  padding: 0;
  list-style: none;
}
.ai-progress li {
  position: relative;
  padding-bottom: var(--space-2xs);
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  line-height: var(--leading-ui);
  color: var(--color-muted-gray);
}
.ai-progress li::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: -1px;
  height: 2px;
  background: transparent;
}
.ai-progress li[aria-current="step"] { color: var(--color-accent-text); }
.ai-progress li[aria-current="step"]::after { background: var(--color-accent-text); }

/* ---------- A screen ----------
   The stage is replaced whole on every press (aisle.js). A new screen rises
   --space-xs over --motion-move on the entering curve, which is a change
   that travels; a save or a hide re-renders in place and does not animate,
   so the head never jumps under the pointer that pressed it.

   THE SCREEN'S RHYTHM. Every block stands --space-md under the one before,
   and the screen's one filled action --space-lg, so a note never touches
   the card or list above it, nor a caption the button. Three classes deep
   on purpose: the app's paragraphs and lists reset their own margins two
   deep (below), and until 2026-10-29 that reset beat a one-class rule
   here, so every note, caption, and list after the first block sat flush
   against whatever was above it. */
.ai-stage > * { margin: 0; }
.ai-app .ai-stage > * + :not(.ai-act) { margin-top: var(--space-md); }
.ai-app .ai-stage > * + .ai-act { margin-top: var(--space-lg); }
@keyframes ai-arrive {
  from { opacity: 0; translate: 0 var(--space-xs); }
}
.ai-stage.ai-arrive > * { animation: ai-arrive var(--motion-move) var(--ease-enter) both; }
@media (prefers-reduced-motion: reduce) {
  .ai-stage.ai-arrive > * { animation: none; }
}

/* Two classes deep where the rule has to beat .case-section p, which is
   class-and-element and would otherwise set the app's lines at --text-lg
   on reading leading (the lesson dispatch-cockpit.css records). */
.ai-app .ai-head { margin-bottom: var(--space-lg); }
.ai-app .ai-head-row { display: flex; align-items: center; gap: var(--space-sm); margin-bottom: var(--space-xs); }
.ai-app .ai-head-row .ai-title { margin: 0; }
/* The badge is the place's drawing cut to a circle; 3.5rem is two 24px
   lines of the title, so it sits level with a title that wraps. */
.ai-app .ai-badge {
  flex: none;
  display: block;
  width: 3.5rem;
  height: 3.5rem;
  border-radius: 50%;
  object-fit: cover;
}
.ai-app .ai-title {
  margin: 0 0 var(--space-xs);
  font-family: var(--font-sans);
  font-size: var(--text-xl);
  font-weight: var(--weight-semibold);
  line-height: var(--leading-tight);
  letter-spacing: var(--tracking-heading);
  color: var(--color-charcoal);
  text-wrap: pretty;
}
.ai-app .ai-title:focus { outline: none; }
.ai-app .ai-title:focus-visible { outline: 2px solid var(--color-focus-ring); outline-offset: 3px; border-radius: 4px; }
.ai-app .ai-dek,
.ai-app .ai-note,
.ai-app .ai-fine,
.ai-app .ai-empty {
  margin: 0;
  font-size: var(--text-base);
  line-height: var(--leading-body);
  color: var(--color-charcoal);
}
.ai-app .ai-note { font-size: var(--text-sm); line-height: var(--leading-ui); color: var(--color-muted-gray); }
.ai-app .ai-fine { font-size: var(--text-xs); line-height: var(--leading-ui); color: var(--color-muted-gray); }

/* The way back, a text button at the top of a screen. 44px tall so the
   target is a finger's, with the chevron centred on the line. */
.ai-app .ai-back {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2xs);
  min-height: 2.75rem;
  margin: calc(var(--space-sm) * -1) 0 0 calc(var(--space-2xs) * -1);
  padding: 0 var(--space-xs) 0 var(--space-2xs);
  border: 0;
  background: transparent;
  font: inherit;
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  color: var(--color-accent-text);
  cursor: pointer;
}
.ai-app .ai-back svg { width: 1lh; height: 1lh; }
.ai-app .ai-back:focus-visible { color: var(--color-charcoal); }
@media (hover: hover) and (pointer: fine) {
  .ai-app .ai-back:hover { color: var(--color-accent-hover); }
}

/* A link inside a sentence, as a button: Undo, bring them all back. It
   stays the size of its words, and a pseudo-element under it makes the
   target a finger's, 44px tall and at least 44px wide, as the console's
   controls did (#322). */
.ai-app .ai-link {
  position: relative;
  padding: 0;
  border: 0;
  background: transparent;
  font: inherit;
  color: var(--color-accent-text);
  text-decoration: underline;
  text-underline-offset: 0.15em;
  cursor: pointer;
}
.ai-app .ai-link::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: max(100%, 2.75rem);
  height: 2.75rem;
  transform: translate(-50%, -50%);
}
.ai-app .ai-link:focus-visible { color: var(--color-charcoal); }
@media (hover: hover) and (pointer: fine) {
  .ai-app .ai-link:hover { color: var(--color-accent-hover); }
}

/* ---------- The photo ----------
   The lamp at a friend's house, as a phone would take it: a photograph of
   her living room (drawn in ChatGPT, img/lab/aisle-maya-photo.webp) filling
   the frame, with when and where under it. The frame is a card on white,
   so it takes the card edge. */
.ai-app .ai-photo { margin: 0; }
.ai-app .ai-photo-frame {
  display: grid;
  place-items: center;
  aspect-ratio: 4 / 3;
  overflow: hidden;
  background: var(--color-warm);
  border: 1px solid var(--rule-card);
  border-radius: var(--radius-xl);
}
.ai-app .ai-photo-img { display: block; width: 100%; height: 100%; object-fit: cover; }
.ai-app .ai-photo-cap {
  margin: var(--space-sm) 0 0;
  font-size: var(--text-sm);
  line-height: var(--leading-ui);
  color: var(--color-charcoal);
}
.ai-app .ai-photo-when { display: block; margin-bottom: var(--space-2xs); color: var(--color-muted-gray); }

/* ---------- The lamps ----------
   Product shots on white. Multiply drops the white into whatever ground the
   picture sits on, so a saved card's tea-light shows through around the
   lamp rather than a white box. */
.ai-art { display: block; width: 100%; height: auto; mix-blend-mode: multiply; }

/* ---------- The results ---------- */
.ai-app .ai-results,
.ai-app .ai-saved,
.ai-app .ai-alts,
.ai-app .ai-why-list,
.ai-app .ai-person-list {
  list-style: none;
  margin: 0;
  padding: 0;
}
.ai-app .ai-results { display: grid; gap: var(--space-sm); }
.ai-app .ai-card {
  display: grid;
  grid-template-columns: var(--space-4xl) minmax(0, 1fr);
  gap: var(--space-sm);
  padding: var(--space-sm);
  background: var(--color-white);
  border: 1px solid var(--rule-card);
  border-radius: 12px;
}
/* A saved card: the feature ground and the sage edge, 4.90 for its small
   type and well over the card floor against the white around it. */
.ai-app .ai-card[data-saved] { background: var(--color-tea-light); border-color: var(--color-accent-text); }
.ai-app .ai-card-art { padding-top: var(--space-2xs); }
.ai-app .ai-card-body { min-width: 0; }
.ai-app .ai-card-body > * { margin: 0; }
.ai-app .ai-card-body > * + * { margin-top: var(--space-xs); }
.ai-app .ai-card-name {
  font-size: var(--text-base);
  font-weight: var(--weight-semibold);
  line-height: var(--leading-tight);
  color: var(--color-charcoal);
}
.ai-app .ai-card-price { font-weight: var(--weight-regular); color: var(--color-muted-gray); white-space: nowrap; }
/* How close the match is, in words first. Close takes the sage, near the
   ink, far the muted gray: three steps a monochrome screen still reads as
   three sentences. */
.ai-app .ai-level {
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  line-height: var(--leading-ui);
  color: var(--color-charcoal);
}
.ai-app .ai-level--close { color: var(--color-accent-text); }
.ai-app .ai-level--far { color: var(--color-muted-gray); }
.ai-app .ai-card-line {
  font-size: var(--text-sm);
  line-height: var(--leading-ui);
  color: var(--color-charcoal);
}

/* Why this one: the site's disclosure row at the card's size. */
.ai-app .ai-why { margin-top: var(--space-xs); }
.ai-app .ai-why-row {
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  color: var(--color-accent-text);
  min-height: 2.75rem;
}
.ai-app .ai-why-list {
  display: grid;
  gap: var(--space-2xs);
  margin: var(--space-2xs) 0 0;
  padding-left: var(--space-lg);
  font-size: var(--text-sm);
  line-height: var(--leading-ui);
  color: var(--color-charcoal);
}
.ai-app .ai-why-list li { position: relative; }
.ai-app .ai-why-list li::before {
  content: "";
  position: absolute;
  left: calc(var(--space-lg) * -1);
  top: 0.68em;
  width: 0.5rem;
  height: 2px;
  border-radius: 1px;
  background: var(--color-accent-text);
}

/* ---------- Chips: the app's small controls ----------
   A pill, 44px tall, outlined in the muted gray so its boundary clears 3:1.
   Pressed (aria-pressed="true") it fills with the sage under warm type,
   5.32. The quiet one, "Not it", keeps the outline and takes the gray ink. */
.ai-app .ai-card-acts,
.ai-app .ai-row,
.ai-app .ai-alt-act {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs);
}
.ai-app .ai-chip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 2.75rem;
  padding: var(--space-xs) var(--space-md);
  background: var(--color-white);
  border: 1px solid var(--color-muted-gray);
  border-radius: var(--radius-pill);
  font: inherit;
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  line-height: var(--leading-ui);
  color: var(--color-charcoal);
  cursor: pointer;
  transition: background-color var(--motion-state) var(--ease), color var(--motion-state) var(--ease), border-color var(--motion-state) var(--ease);
}
.ai-app .ai-chip--quiet { color: var(--color-muted-gray); }
.ai-app .ai-chip[aria-pressed="true"] {
  background: var(--color-accent-text);
  border-color: var(--color-accent-text);
  color: var(--color-warm);
}
.ai-app .ai-chip:focus-visible { border-color: var(--color-charcoal); color: var(--color-charcoal); }
.ai-app .ai-chip[aria-pressed="true"]:focus-visible { color: var(--color-warm); border-color: var(--color-accent-text); }
@media (hover: hover) and (pointer: fine) {
  .ai-app .ai-chip:hover { background: var(--wash-on-light-hover); color: var(--color-charcoal); }
  .ai-app .ai-chip[aria-pressed="true"]:hover { background: var(--color-accent-hover); border-color: var(--color-accent-hover); color: var(--color-warm); }
}

/* The screen's one filled action. style.css's .btn-primary, the app's
   width, and a count after the label at the label's own size. The space
   above it is the screen's rhythm (above). */
.ai-app .ai-btn { width: 100%; gap: var(--space-xs); }
.ai-app .ai-btn-count { font-weight: var(--weight-regular); }
.ai-app .ai-btn-count::before { content: "\00b7"; margin-right: var(--space-xs); }

/* The undo line after a hide: a status, on the warm ground. */
.ai-app .ai-undo {
  margin: 0;
  padding: var(--space-sm) var(--space-md);
  background: var(--color-warm);
  border: 1px solid var(--rule-card);
  border-radius: 12px;
  font-size: var(--text-sm);
  line-height: var(--leading-ui);
  color: var(--color-charcoal);
}

/* ---------- The store, and the saved lamps ---------- */
.ai-app .ai-store {
  padding: var(--space-sm) var(--space-md);
  background: var(--color-warm);
  border: 1px solid var(--rule-card);
  border-radius: 12px;
}
.ai-app .ai-store-name { margin: 0; font-size: var(--text-base); font-weight: var(--weight-semibold); line-height: var(--leading-tight); color: var(--color-charcoal); }
.ai-app .ai-store-meta { margin: var(--space-2xs) 0 0; font-size: var(--text-sm); line-height: var(--leading-ui); color: var(--color-muted-gray); }
.ai-app .ai-saved { display: grid; gap: 0; }
.ai-app .ai-saved-item {
  display: grid;
  grid-template-columns: var(--space-3xl) minmax(0, 1fr);
  gap: var(--space-sm);
  padding: var(--space-sm) 0;
  border-bottom: 1px solid var(--rule-hairline);
}
.ai-app .ai-saved-item > div > p { margin: 0; }
.ai-app .ai-saved-item > div > p + p { margin-top: var(--space-2xs); }
.ai-app .ai-saved-name { font-size: var(--text-base); font-weight: var(--weight-semibold); line-height: var(--leading-tight); color: var(--color-charcoal); }
.ai-app .ai-saved-where,
.ai-app .ai-saved-stock { font-size: var(--text-sm); line-height: var(--leading-ui); color: var(--color-charcoal); }
/* A count the store cannot vouch for takes the caution ink, as the cockpit's
   stale hours do: a figure the system will not stand behind, in words and
   in the one status ink the palette keeps for it. */
.ai-app .ai-stock--stale { color: var(--color-caution-text); }

/* The comparison. A data table at 14px, heads in the semibold, rows parted
   by the visible rule. The first column's heads are the features. */
.ai-app .ai-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--text-sm);
  line-height: var(--leading-ui);
  font-variant-numeric: tabular-nums;
}
.ai-app .ai-table th,
.ai-app .ai-table td {
  padding: var(--space-xs) var(--space-xs) var(--space-xs) 0;
  text-align: left;
  vertical-align: top;
  border-bottom: 1px solid var(--color-rule);
  color: var(--color-charcoal);
}
.ai-app .ai-table thead th { font-weight: var(--weight-semibold); }
.ai-app .ai-table tbody th { font-weight: var(--weight-regular); color: var(--color-muted-gray); }
.ai-app .ai-table td:last-child,
.ai-app .ai-table th:last-child { padding-right: 0; }

/* ---------- The alternatives, and the person ---------- */
.ai-app .ai-alt {
  padding: var(--space-sm) 0 var(--space-md);
  border-bottom: 1px solid var(--rule-hairline);
}
.ai-app .ai-alt > p { margin: 0; }
.ai-app .ai-alt > p + p { margin-top: var(--space-xs); }
.ai-app .ai-alt-name { font-size: var(--text-base); font-weight: var(--weight-semibold); line-height: var(--leading-tight); color: var(--color-charcoal); }
.ai-app .ai-alt-line { font-size: var(--text-sm); line-height: var(--leading-ui); color: var(--color-charcoal); }

.ai-app .ai-person {
  padding: var(--space-md);
  background: var(--color-tea-light);
  border: 1px solid var(--rule-card);
  border-radius: var(--radius-xl);
}
.ai-app .ai-person-label { margin: 0 0 var(--space-xs); font-size: var(--text-base); font-weight: var(--weight-semibold); line-height: var(--leading-tight); color: var(--color-charcoal); }
.ai-app .ai-person-list {
  display: grid;
  gap: var(--space-2xs);
  padding-left: var(--space-lg);
  font-size: var(--text-sm);
  line-height: var(--leading-ui);
  color: var(--color-charcoal);
}
.ai-app .ai-person-list li { position: relative; }
.ai-app .ai-person-list li::before {
  content: "";
  position: absolute;
  left: calc(var(--space-lg) * -1);
  top: 0.68em;
  width: 0.5rem;
  height: 2px;
  border-radius: 1px;
  background: var(--color-accent-text);
}
.ai-app .ai-person-note { margin: var(--space-sm) 0 0; font-size: var(--text-sm); line-height: var(--leading-ui); color: var(--color-charcoal); }
.ai-app .ai-done-list { font-size: var(--text-base); line-height: var(--leading-body); }

/* ---------- What the shopper liked, carried across ----------
   Asked once under the photo, as four chips of one choice. On a match it
   is one line before the match's own: "What you liked:" and how this lamp
   does on it. A yes takes the sage and a no the charcoal, and the sentence
   says which either way ("like your photo", "not wood"). */
.ai-app .ai-pref > p { margin: 0; }
.ai-app .ai-pref > p + p { margin-top: var(--space-xs); }
.ai-app .ai-pref-q { font-size: var(--text-base); font-weight: var(--weight-semibold); line-height: var(--leading-tight); color: var(--color-charcoal); }
.ai-app .ai-fit { font-size: var(--text-sm); line-height: var(--leading-ui); color: var(--color-charcoal); }
.ai-app .ai-fit[data-fit="yes"] { color: var(--color-accent-text); }
.ai-app .ai-fit-k { font-weight: var(--weight-semibold); }

/* "Not on the shelf?" under each saved lamp in the store: a link-sized
   button whose target is a finger's, 44px, without making the row taller
   than its line. */
.ai-app .ai-link--tap {
  display: inline-flex;
  align-items: center;
  min-height: 2.75rem;
  margin-block: calc((1lh - 2.75rem) / 2);
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
}

/* The comparison scrolls sideways once it holds three or four lamps, so
   each column keeps a readable width on a phone. The region is focusable
   so a keyboard can scroll it. The row for what the shopper liked comes
   first, on the feature ground, with its reason under the row head. */
.ai-app .ai-table-wrap { overflow-x: auto; }
.ai-app #ai-compare > * + * { margin-top: var(--space-xs); }
.ai-app .ai-table-wrap:focus-visible { outline: 2px solid var(--color-focus-ring); outline-offset: 2px; border-radius: 4px; }
.ai-app .ai-table-wrap[data-wide] .ai-table { min-width: 30rem; }
.ai-app .ai-table tr[data-pick] > * { background: var(--color-tea-light); }
.ai-app .ai-table tr[data-pick] th,
.ai-app .ai-table tr[data-pick] td { color: var(--color-charcoal); font-weight: var(--weight-semibold); }
.ai-app .ai-table tr[data-pick] th { padding-left: var(--space-2xs); }
.ai-app .ai-table-pick { display: block; font-size: var(--text-xs); font-weight: var(--weight-regular); color: var(--color-accent-text); }

/* Asking for help: what the person will see, as checkboxes on the same
   tea-light panel the person's own view sits on. The whole row is the
   label, padded to 44px on one line, so the target is the sentence and
   not the box, and the rows keep one gap however many lines they wrap to. */
.ai-app .ai-share {
  margin: 0;
  padding: var(--space-md);
  min-width: 0;
  background: var(--color-tea-light);
  border: 1px solid var(--rule-card);
  border-radius: var(--radius-xl);
}
.ai-app .ai-share legend { float: left; width: 100%; padding: 0; }
.ai-app .ai-share legend + * { clear: left; }
.ai-app .ai-check {
  display: flex;
  align-items: flex-start;
  gap: var(--space-xs);
  padding: calc((2.75rem - 1lh) / 2) 0;
  font-size: var(--text-sm);
  line-height: var(--leading-ui);
  color: var(--color-charcoal);
  cursor: pointer;
}
.ai-app .ai-check input:focus-visible { outline: 2px solid var(--color-focus-ring); outline-offset: 2px; }
.ai-app .ai-check input {
  flex: none;
  width: 1.125rem;
  height: 1.125rem;
  margin: calc((1lh - 1.125rem) / 2) 0 0;
  accent-color: var(--color-accent-text);
}
