/* ============================================================================
   Mindprint — the pattern browser (D83, design-system/prototypes/pattern-browser-scale).

   This sheet is deliberately short. Everything the browser shares with the rest of
   the storefront — .hero, .filters, .chiprow, .chip, .count, .sort, the .pat-* picker,
   .hbtn, the prompt dock, the .well/.is-unrendered plate treatment — comes from
   storefront.css and header-bar.css unchanged, because "we have a design system, use
   it" is the note this redesign was given. What is here is only what is genuinely new:
   the two-lane workspace, the density control, the card grid, and the details surface
   in its two forms.
   ========================================================================== */

/* ══ The workspace ══
   One grid, two lanes: the wall, and a rail that is zero-width until a pattern opens.
   Animating grid-template-columns (rather than sliding an overlay across) is what
   makes the wall *zoom* instead of re-wrap — the column count is fixed in JS, so the
   same cards stay on the same rows and only get smaller. */

.browser {
  --panel-w: clamp(370px, 30vw, 460px);
  --cols: 5;
  display: grid;
  grid-template-columns: minmax(0, 1fr) 0;
  align-items: start;
  overflow-x: clip;
  transition: grid-template-columns .42s cubic-bezier(.22, .9, .26, 1);
}

.browser[data-panel="open"] { grid-template-columns: minmax(0, 1fr) var(--panel-w); }

.pb-pane {
  grid-column: 1;
  min-width: 0;
  /* `width: 100%` is load-bearing beside the auto margins: a grid item with
     `margin-inline: auto` stops stretching to its track and sizes to its content,
     which collapses a container-typed pane to almost nothing. */
  width: 100%;
  max-width: 1560px;
  margin: 0 auto;
  padding: 0 clamp(20px, 3.5vw, 52px);
  /* The hero and the toolbar reflow against the lane they actually have, not against
     the viewport — at 1280px with the rail open those are 400px apart, and a layout
     keyed to the window clips the picker exactly there. */
  container: pane / inline-size;
}

/* The finite walls (/search, the landing's browse arm) mount this same workspace, but
   their page container is storefront's own .wrap, carried over intact inside the lane.
   The modifier stands the pane down to a bare grid lane — the lane and the container
   would otherwise stack their identical padding and max-width, and the phone padding
   here (12px) is the browser's, not theirs. Double-class specificity keeps this ahead
   of the phone block's own .pb-pane override below. */
.pb-pane.pb-flow {
  max-width: none;
  padding: 0;
}

/* The hero's h1 gives way when the rail takes a third of the lane. */
.browser[data-panel="open"] .hero h1 { font-size: clamp(30px, 3.4vw, 48px); }

/* ══ The toolbar — the one sticky control surface ══
   Page-level context only: the collection, what the cards are wearing, and how
   densely. The chips and sort live in the non-sticky row below, because a refinement
   you chose at the top does not need to ride along to row four hundred. */

.pb-toolbar {
  position: sticky;
  top: var(--admin-bar-h, 66px);
  z-index: 30;
  display: flex;
  align-items: center;
  gap: clamp(14px, 2.2vw, 26px);
  min-height: 52px;
  padding: 8px 0;
  background: var(--mp-black);
}

.pb-toolbar .pb-density { margin-left: auto; }

/* The picker's current value must not become the toolbar's width problem: a long
   drop or garment title truncates rather than pushing the density off the bar.
   min-width: 0 is what lets a flex child actually shrink below its content. */
.pb-toolbar .pat-wrap { min-width: 0; }
.pb-toolbar .pat-btn { max-width: 100%; }

.pb-toolbar .pat-nm {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The shared .pat-menu hangs from its button's RIGHT edge — right for the old hero,
   where the picker sat against the right margin. These pickers sit against the left
   one, so that anchoring walks the menu off the left of the screen at any width
   where the menu is wider than the button. Hang it from the left edge instead; the
   phone block below overrides again to span the whole bar. */
.pb-toolbar .pat-menu { right: auto; left: 0; }

/* The refinement row underneath scrolls away with the page — override the shared
   sheet's sticky, which is right for the galleries whose only toolbar it is. */
.browser .filters {
  position: static;
  padding: 4px 0 14px;
}

/* ══ Hero furniture the pickers need ══ */

.pb-dot {
  width: 13px;
  height: 13px;
  flex: none;
  border-radius: var(--mp-r-pill);
  background: var(--mp-brand-grad);
  box-shadow: 0 0 0 1px rgba(255, 255, 255, .25);
}

.pb-menu-section {
  padding: 9px 10px 4px;
  color: var(--mp-mute);
  font-family: var(--mp-mono);
  font-size: 9px;
  letter-spacing: .16em;
  text-transform: uppercase;
}

/* The count rides with the option name; the tick still hangs off the end. */
.pat-opt b { margin-left: auto; color: var(--mp-mute); font-size: 9px; font-weight: 400; }
.pat-opt b + .tick { margin-left: 8px; }

/* The shown-as menu: the swatch is the option's picture — the garment's plate large
   enough to recognise, and the artwork's checker distinct from any plate. The rule
   under the artwork row keeps "the art itself" apart from the garments below it. */
#garmentMenu .pat-opt .sw { width: 26px; height: 26px; border-radius: 8px; }

#garmentMenu .sw.none {
  background-image:
    repeating-linear-gradient(45deg, #1e1e22 0 5px, #2c2c33 5px 10px);
  box-shadow: inset 0 0 0 1px var(--mp-line-soft);
}

.pb-menu-rule { height: 1px; margin: 5px 4px; background: var(--mp-line); }

.pb-shortcut-link {
  padding: 0;
  border: 0;
  background: none;
  color: var(--mp-mute);
  cursor: pointer;
  font-family: var(--mp-mono);
  font-size: 10px;
  letter-spacing: .12em;
  text-transform: uppercase;
}

.pb-shortcut-link:hover { color: var(--mp-peach); }
.pb-shortcut-link kbd { margin-left: 4px; }

kbd {
  min-width: 20px;
  padding: 2px 5px;
  border: 1px solid var(--mp-line-soft);
  border-radius: 5px;
  background: var(--mp-raise);
  color: var(--mp-dim);
  font-family: var(--mp-mono);
  font-size: 9px;
  letter-spacing: .04em;
  text-align: center;
  box-shadow: inset 0 -1px rgba(255, 255, 255, .08);
}

/* ══ Density ══
   The one genuinely new toolbar control: how many patterns fit across. A slider
   rather than a set of chips because it is a continuum with four stops, and because
   the chips beside it are filters — a fifth chip that changed the zoom instead of the
   set would read as one. */

.pb-density {
  min-height: 36px;
  display: flex;
  align-items: center;
  gap: 9px;
  flex: none;
  color: var(--mp-mute);
  font-family: var(--mp-mono);
  font-size: 9px;
  letter-spacing: .12em;
  text-transform: uppercase;
}

.pb-density input {
  --fill: 33.333%;
  width: 78px;
  height: 36px;
  margin: 0 2px;
  appearance: none;
  border-radius: var(--mp-r-pill);
  outline: none;
  cursor: pointer;
  background:
    linear-gradient(90deg, var(--mp-pink) 0 var(--fill), var(--mp-raise) var(--fill) 100%)
    center / 100% 8px no-repeat;
}

.pb-density input::-webkit-slider-thumb {
  width: 20px;
  height: 20px;
  appearance: none;
  border: 0;
  border-radius: 50%;
  background: var(--mp-ink);
  box-shadow: 0 1px 8px rgba(0, 0, 0, .65);
  cursor: grab;
}

.pb-density input::-moz-range-thumb {
  width: 20px;
  height: 20px;
  border: 0;
  border-radius: 50%;
  background: var(--mp-ink);
  box-shadow: 0 1px 8px rgba(0, 0, 0, .65);
  cursor: grab;
}

.pb-density input:focus-visible { outline: 2px solid var(--mp-peach); outline-offset: 4px; }

/* ══ The wall ══ */

.pb-virtual-space {
  height: 0;
  pointer-events: none;
}

.pb-grid {
  /* The waiting room's band is drawn across this box and masked to the slots' own
     offsetLeft/offsetTop, so the grid has to be both the containing block and the offset parent
     — otherwise both the band and its mask resolve against whatever is positioned further up,
     and the light lands somewhere off the wall entirely. Every absolutely positioned thing
     inside the wall lives in a .pb-card, which is positioned itself, so nothing else moves. */
  position: relative;
  display: grid;
  grid-template-columns: repeat(var(--cols), minmax(0, 1fr));
  /* The slider narrows this along with the cards — a denser wall breathes less. */
  gap: var(--pb-gap, 10px);
  min-height: 240px;
}

.pb-card {
  position: relative;
  min-width: 0;
  aspect-ratio: 1;
  overflow: hidden;
  /* The card sizes its own corner furniture. This is the one grid whose cards change
     size under the visitor's hand — the density slider runs 5 columns to 10 on a
     desktop and 2 to 4 on a phone — so anything measured in pixels is right at one
     end of that slider and wrong at the other. See --pb-fav below. */
  container-type: inline-size;
  /* The card's double tap favourites it, and on a phone that is the *only* way to
     favourite from the wall. Left to the default, a double tap over a card is the
     browser's own zoom gesture and the dblclick either arrives late or not at all. */
  touch-action: manipulation;
  border: 1px solid var(--mp-line);
  border-radius: 12px;
  background: var(--mp-surface);
  transition: transform .25s cubic-bezier(.22, .9, .26, 1), border-color .18s, box-shadow .28s;
}

/* Dressed cards keep the artwork cards' exact square, so switching the shown-as view
   swaps every photo without moving a single tile — spatial memory is the whole point
   of a wall this size. */

/* The scrim the name sits on. Tuned for artwork that runs to the card's edge; over a
   matted garment pane it starts lower, because there is already a dark mat under it. */
.pb-card::after {
  content: "";
  position: absolute;
  inset: 44% 0 0;
  z-index: 1;
  pointer-events: none;
  opacity: 0;
  background: linear-gradient(transparent, rgba(0, 0, 0, .9));
  transition: opacity .2s;
}

/* Over a matted pane it starts lower and lands softer: there is already a dark mat
   under the name, and a scrim tuned for artwork that runs to the card's edge greys
   the bottom third of the mockup for no legibility it hasn't already got. */
.pb-card.on-garment::after {
  inset: 56% 0 0;
  background: linear-gradient(transparent, rgba(0, 0, 0, .82));
}


.pb-card:hover,
.pb-card:focus-within,
.pb-card.is-current { transform: translateY(-2px); border-color: rgba(255, 255, 255, .34); }

.pb-card:hover,
.pb-card:focus-within { box-shadow: 0 18px 46px -18px var(--glow, rgba(251, 94, 190, .45)); }

.pb-card.is-current {
  border-color: var(--mp-pink);
  box-shadow: 0 0 0 1px var(--mp-pink), 0 22px 60px -20px var(--glow, rgba(251, 94, 190, .55));
}

.pb-card:hover::after,
.pb-card:focus-within::after,
.pb-card.is-current::after { opacity: 1; }

.pb-hit {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--mp-ink);
  cursor: pointer;
  text-align: left;
}

.pb-hit:focus-visible { outline: 3px solid var(--mp-peach); outline-offset: -4px; }

.pb-art { position: absolute; inset: 0; display: block; overflow: hidden; background: #0d0d10; }

.pb-art img {
  width: 100%;
  height: 100%;
  display: block;
  object-fit: cover;
  transform: scale(1.02);
  transition: transform 3.4s cubic-bezier(.16, .7, .3, 1);
}

.pb-card:hover .pb-art img,
.pb-card.is-current .pb-art img { transform: scale(1.14); }

/* Dressed in a garment: the same square the artwork fills, wearing the shelf cards'
   matted pane behind the photo. Cover, not contain — an off-square mockup crops its
   long side about the centre rather than changing the tile's shape.

   The mat is what makes the hover read. On /new and /products the garment is held off
   the card's edge, and pointing at it grows the photograph *out* of its frame while
   the pane brightens underneath — the storefront sheet's shared pane rule. The wall's
   dressed cards had borrowed that pane's colour but not its mat: the mockup filled the
   square edge to edge and nothing about it moved on hover, so the one view where you
   compare forty garments at once was the one view whose picture didn't answer the
   pointer. This is that gesture, on this grid's terms.

   The mat is a percentage rather than the shelves' 9px because this is the one grid
   whose cards change size: the density slider runs five columns to ten, roughly 282px
   down to 139px, and an inset that reads as matting at the wide end reads as a border
   at the dense end. 3.2% is the shelves' own 9px at the shelves' own card width, held
   constant across the slider's whole range — which is also why scale(1.05) lands the
   growth where it lands there, a couple of pixels shy of the card's edge, instead of
   needing a number of its own.

   Square corners on the well, per storefront.css's note on the same pane: the card's
   overflow does the clipping, and a well rounded to the card's own radius leaves a lit
   crescent of card surface in each top corner. */
.pb-card .well {
  --pb-mat: 3.2%;
  --mp-pane-r: 9px;
  --mp-pane-tint: .3;
  --mp-pane-lip: .16;
  position: absolute;
  inset: 0;
  display: block;
  overflow: hidden;
  padding: var(--pb-mat);
  background: linear-gradient(180deg, #0C0C10, #08080A);
  transition: --mp-pane-tint .42s ease, --mp-pane-lip .42s ease;
}

/* The pane. Its tint is the pattern's own colour, set per card in _BrowserTiles — a
   wall of different patterns cannot tint as one shelf does. Gradients do not
   transition, so the tint and the lit lip are the @property numbers from tokens.css,
   and it is those that ease; where @property is unsupported the hover snaps. */
.pb-card .well::before {
  content: "";
  position: absolute;
  inset: var(--pb-mat);
  z-index: 0;
  border-radius: var(--mp-pane-r);
  background-color: #0A0A0D;
  background-image: linear-gradient(158deg,
    rgb(var(--accent-rgb, 251 94 190) / var(--mp-pane-tint)) 0%,
    rgba(23, 23, 29, .94) 54%,
    rgba(9, 9, 13, .98) 100%);
  box-shadow:
    inset 0 1px 0 rgb(255 255 255 / var(--mp-pane-lip)),
    inset 0 0 0 1px rgb(var(--accent-rgb, 251 94 190) / calc(var(--mp-pane-lip) * .5));
}

/* The pane and the garment are each held off every edge by the mat, and the inset has
   to be stated on both: an absolutely-positioned child's containing block is the
   padding box, so the well's own padding insets nothing here. */
.pb-card .well img {
  position: absolute;
  inset: var(--pb-mat);
  z-index: 1;
  width: calc(100% - var(--pb-mat) * 2);
  height: calc(100% - var(--pb-mat) * 2);
  display: block;
  object-fit: cover;
  border-radius: var(--mp-pane-r);
  transition: transform .5s cubic-bezier(.22, .9, .26, 1);
}

/* Focus-within and the open card get it too, not only the pointer: the wall is driven
   by arrow keys as much as by the mouse, and the card you are standing on should look
   the way the card you are pointing at looks. */
.pb-card:hover .well,
.pb-card:focus-within .well,
.pb-card.is-current .well {
  --mp-pane-tint: .6;
  --mp-pane-lip: .3;
}

.pb-card:hover .well img,
.pb-card:focus-within .well img,
.pb-card.is-current .well img { transform: scale(1.05); }

/* No imagery at all for this pairing: the pattern itself tiles behind a dark scrim
   with the caption over it — the same honest D59 spirit as the plate, one rung down.
   The fill rides on the pane, so it is matted like a photograph would be; the caption
   is padded off the mat rather than off the card. */
.pb-card .well.ph {
  display: grid;
  place-items: center;
  padding: calc(var(--pb-mat) + 8px);
  color: var(--mp-dim);
  font-family: var(--mp-mono);
  font-size: 9px;
  letter-spacing: .1em;
  text-align: center;
  text-transform: uppercase;
  text-shadow: 0 1px 6px rgba(0, 0, 0, .8);
}

.pb-card .well.ph::before {
  background-image:
    linear-gradient(rgba(10, 10, 13, .72), rgba(10, 10, 13, .72)),
    var(--mp-behind, none),
    linear-gradient(158deg,
      rgb(var(--accent-rgb, 251 94 190) / var(--mp-pane-tint)) 0%,
      rgba(23, 23, 29, .94) 54%,
      rgba(9, 9, 13, .98) 100%);
  background-size: auto, 38% auto, auto;
  background-repeat: no-repeat, repeat, no-repeat;
}

.pb-card .well.ph > span { position: relative; z-index: 1; }

/* The base .is-unrendered rule (and its badge) live in header-bar.css, which every page
   loads. Here the pseudo-element is already the pane, so the two become one: the plate
   rides on top of the pattern tiled behind it — and so, since the mat, the plate is
   matted exactly as a photographed pairing is, and lights the same way on hover. */
.pb-card .well.is-unrendered::before {
  background-image:
    linear-gradient(rgba(10, 10, 13, .68), rgba(10, 10, 13, .68)),
    var(--mp-behind, none),
    linear-gradient(158deg,
      rgb(var(--accent-rgb, 251 94 190) / var(--mp-pane-tint)) 0%,
      rgba(23, 23, 29, .94) 54%,
      rgba(9, 9, 13, .98) 100%);
  background-size: auto, 38% auto, auto;
  background-repeat: no-repeat, repeat, no-repeat;
  opacity: 1;
}

/* The browser never exposes our rendering compromise as customer copy. The plate is an honest
   visual preview and the click offers the finished render; a process badge only adds chrome and
   was explicitly rejected in the approved interaction. */
.pb-card .well.is-unrendered::after,
.pb-garment .well.is-unrendered::after { content: none; }

.pb-pos {
  position: absolute;
  top: 9px;
  left: 10px;
  z-index: 3;
  padding: 3px 6px;
  border-radius: 6px;
  background: rgba(0, 0, 0, .52);
  color: var(--mp-dim);
  font-family: var(--mp-mono);
  font-size: 9px;
  letter-spacing: .08em;
  opacity: 0;
  transition: opacity .2s;
}

.pb-card:hover .pb-pos,
.pb-card:focus-within .pb-pos,
.pb-card.is-current .pb-pos { opacity: 1; }

.pb-meta {
  position: absolute;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 3;
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: 12px 12px 11px;
  pointer-events: none;
  opacity: 0;
  transform: translateY(8px);
  transition: opacity .2s, transform .24s cubic-bezier(.22, .9, .26, 1);
}

.pb-card:hover .pb-meta,
.pb-card:focus-within .pb-meta,
.pb-card.is-current .pb-meta { opacity: 1; transform: none; }

.pb-meta strong {
  padding-right: 30px;
  overflow: hidden;
  font-size: clamp(12px, 1.1vw, 16px);
  line-height: 1.05;
  letter-spacing: -.015em;
  text-overflow: ellipsis;
  text-shadow: 0 2px 10px rgba(0, 0, 0, .8);
  white-space: nowrap;
}

.pb-meta span {
  overflow: hidden;
  color: var(--mp-peach);
  font-family: var(--mp-mono);
  font-size: 8.5px;
  letter-spacing: .1em;
  text-overflow: ellipsis;
  text-transform: uppercase;
  white-space: nowrap;
}

/* The card's heart is the shared .hbtn.fav pill in its compact form, parked in the
   corner. It appears on hover, on focus, on the current card — and always, once it is
   pressed, because a favourite that hides is a favourite you cannot see you have.
   The size is spelled at .hbtn.fav.fav-compact specificity, or the button system's
   own 40px circle wins the width alone and the heart renders as an oval.

   It is measured against the card, not the screen. A disc that reads as a corner mark
   on a 280px card is a badge across a 90px one — same pixels, five times the share of
   the picture — and the wall's whole job is the pictures. `cqi` is the card's own
   inline size (the container-type above), so the heart holds roughly a fifth of the
   card at every rung of the density slider, between a 24px floor that keeps it
   tappable and the 32px ceiling it always had at the wide end. The inset follows it
   down so the mark stays tucked in the corner rather than drifting toward the middle
   of a small card. */
.pb-card {
  --pb-fav: clamp(24px, 22cqi, 32px);
  --pb-fav-inset: clamp(4px, 6cqi, 8px);
}

.pb-card .fav {
  position: absolute;
  top: var(--pb-fav-inset);
  right: var(--pb-fav-inset);
  z-index: 5;
  min-height: 0;
  padding: 0;
  opacity: 0;
  transform: scale(.86);
  transition: opacity .18s, transform .18s, background .18s, color .18s, border-color .18s;
}

.pb-card .hbtn.fav.fav-compact {
  width: var(--pb-fav);
  height: var(--pb-fav);
}


.pb-card:hover .fav,
.pb-card:focus-within .fav,
.pb-card.is-current .fav,
.pb-card .fav[aria-pressed="true"] { opacity: 1; transform: none; }

/* The shared pill lets its ♥ stay a constant pink accent because the label next to it
   ("Favorite" / "Favorited") carries the pressed state. This icon has no label, and it
   is also shown whenever a card merely has hover, focus or is the open selection —
   none of which mean favorited. A pink heart at rest read as "already on" the moment
   it appeared for any of those reasons. So here the heart is a plain ghost until it is
   actually pressed, and pressed becomes an unmistakable solid pink disc instead of a
   one-pixel border tint. */
/* The glyph rides the disc: the shared button's 14px is .44 of its 32px circle, and it
   is that ratio rather than the pixel value that is held here, with a floor that keeps
   a ♥ reading as a heart rather than a blob at the densest end. */
.pb-card .fav .heart {
  color: var(--mp-dim);
  font-size: max(8px, calc(var(--pb-fav) * .44));
}

.pb-card .fav:hover .heart { color: var(--mp-pink-soft); }

.pb-card .fav[aria-pressed="true"] {
  background: var(--mp-pink);
  border-color: transparent;
}

.pb-card .fav[aria-pressed="true"] .heart { color: var(--mp-ink-on-pink); }
.pb-card .fav[aria-pressed="true"]:hover { background: var(--mp-pink-soft); }

.pb-grid .empty {
  grid-column: 1 / -1;
  min-height: 220px;
  display: grid;
  place-content: center;
  justify-items: center;
  gap: 8px;
  border: 1px dashed var(--mp-line-soft);
  border-radius: 14px;
  color: var(--mp-mute);
  text-align: center;
}

.pb-grid .empty strong { color: var(--mp-ink); font-size: 20px; }

.pb-grid .empty span {
  font-family: var(--mp-mono);
  font-size: 9px;
  letter-spacing: .1em;
  text-transform: uppercase;
}

/* ══ The batch sentinel ══ */

.pb-more {
  min-height: 84px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  color: var(--mp-mute);
  font-family: var(--mp-mono);
  font-size: 9px;
  letter-spacing: .14em;
  text-transform: uppercase;
}

.pb-more[hidden] { display: none; }

.pb-spinner {
  width: 16px;
  height: 16px;
  border: 2px solid var(--mp-line-soft);
  border-top-color: var(--mp-pink);
  border-radius: 50%;
  animation: browser-spin .8s linear infinite;
}

.pb-more.is-resting .pb-spinner { animation: none; border-color: var(--mp-line-soft); }

@keyframes browser-spin { to { transform: rotate(360deg); } }

/* ══ The details surface — desktop sidecar ══ */

.panel {
  grid-column: 2;
  position: sticky;
  top: calc(var(--admin-bar-h, 66px) + 10px);
  z-index: 20;
  width: calc(100% - 12px);
  min-width: 0;
  height: calc(100dvh - var(--admin-bar-h, 66px) - 22px);
  margin: 10px 12px 0 0;
  overflow: hidden;
  visibility: hidden;
  opacity: 0;
  transform: translate3d(40px, 0, 0) scale(.985);
  border: 1px solid var(--mp-line-soft);
  border-radius: 16px;
  background: var(--mp-surface);
  box-shadow: 0 30px 90px rgba(0, 0, 0, .72);
  transition: transform .42s cubic-bezier(.22, .9, .26, 1), opacity .22s, visibility 0s linear .42s;
}

.browser[data-panel="open"] .panel,
.browser[data-panel="compact"] .panel,
.browser[data-panel="expanded"] .panel {
  visibility: visible;
  opacity: 1;
  transform: none;
  transition-delay: 0s;
}

.panel-grip {
  height: 46px;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 0 10px 0 16px;
  border-bottom: 1px solid var(--mp-line);
  color: var(--mp-mute);
  font-family: var(--mp-mono);
  font-size: 9px;
  letter-spacing: .14em;
  text-transform: uppercase;
}

.pb-grip-handle {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 10px;
  height: 100%;
  padding: 0;
  border: 0;
  background: transparent;
  color: inherit;
  font: inherit;
  letter-spacing: inherit;
  text-align: left;
}

.pb-grabber { display: none; width: 38px; height: 4px; border-radius: 999px; background: #5c5c63; }

.panel-controls { display: flex; gap: 7px; margin-left: auto; }

.panel-controls button {
  width: 34px;
  height: 34px;
  display: grid;
  place-items: center;
  padding: 0;
  border: 1px solid var(--mp-line-soft);
  border-radius: 50%;
  background: var(--mp-raise);
  color: var(--mp-dim);
  cursor: pointer;
  font-size: 17px;
  line-height: 1;
}

.panel-controls button:hover { border-color: var(--mp-pink); color: var(--mp-ink); }

/* Compact/expanded is a phone distinction: on a desktop the rail is open or it is not. */
.panel-expand { display: none !important; }

.panel-scroll {
  position: relative;
  height: calc(100% - 46px);
  overflow: auto;
  overflow-x: hidden;
  overscroll-behavior: contain;
  scrollbar-color: #55555c transparent;
  scrollbar-gutter: stable;
}

.panel-scroll[aria-busy="true"] > * { opacity: .38; }

.panel-scroll[aria-busy="true"]::after {
  content: "Loading pattern details";
  position: absolute;
  top: 18px;
  right: 18px;
  z-index: 5;
  padding: 7px 10px;
  border: 1px solid var(--mp-line-soft);
  border-radius: var(--mp-r-pill);
  background: rgba(20, 20, 22, .94);
  color: var(--mp-dim);
  font-family: var(--mp-mono);
  font-size: 9px;
  letter-spacing: .12em;
  text-transform: uppercase;
}

.panel-error {
  min-height: 220px;
  display: grid;
  place-content: center;
  gap: 9px;
  padding: 24px;
  color: var(--mp-mute);
  text-align: center;
}

.panel-error strong { color: var(--mp-ink); font-size: 18px; }
.panel-error button { justify-self: center; }

/* ══ Panel contents ══ */

.panel-hero {
  display: grid;
  grid-template-columns: 118px minmax(0, 1fr);
  gap: 16px;
  align-items: start;
  padding: 18px;
}

.panel-art {
  position: relative;
  isolation: isolate;
  aspect-ratio: 1;
  overflow: hidden;
  border: 1px solid var(--mp-line-soft);
  border-radius: 12px;
  background: #0c0c0f;
  cursor: crosshair;
}

.panel-art:focus-visible { outline: 2px solid var(--mp-peach); outline-offset: 3px; }
.panel-art img { width: 100%; height: 100%; display: block; object-fit: cover; }

/* The lens: the same artwork at 200%, panned by the pointer. Opacity rather than a
   second element swapped in, so there is nothing to load and nothing to flash. Kept
   below 300%: the source is a 1024px rung with no higher-res asset behind it, and
   heavier magnification just enlarged its softness rather than revealing detail. */
.pb-weave {
  position: absolute;
  inset: 0;
  z-index: 1;
  background-position: 50% 50%;
  background-repeat: no-repeat;
  background-size: 200%;
  opacity: 0;
  pointer-events: none;
  transition: opacity .2s;
}

.panel-art.is-inspecting .pb-weave { opacity: 1; }

.pb-weave-hint {
  position: absolute;
  right: 6px;
  bottom: 6px;
  left: 6px;
  z-index: 2;
  min-height: 26px;
  display: grid;
  place-items: center;
  padding: 0 6px;
  border: 1px solid var(--mp-line-soft);
  border-radius: var(--mp-r-pill);
  background: rgba(16, 16, 19, .76);
  color: var(--mp-dim);
  font-family: var(--mp-mono);
  font-size: 8px;
  letter-spacing: .1em;
  text-align: center;
  text-transform: uppercase;
  pointer-events: none;
  transition: opacity .16s;
}

.panel-art.is-inspecting .pb-weave-hint { opacity: 0; }
.pb-weave-hint .coarse { display: none; }

/* Both signals, because neither is universal: hover:none catches touch-first devices at
   any width, and the sheet breakpoint catches emulators and hybrids that claim hover
   while showing the phone layout — where "hover to inspect" is an instruction for a
   pointer the visitor is not holding. */
@media (hover: none), (max-width: 900px) {
  .pb-weave-hint .fine { display: none; }
  .pb-weave-hint .coarse { display: inline; }
}

.panel-summary { min-width: 0; }

.panel-summary h2 {
  margin: 0;
  overflow-wrap: break-word;
  font-size: clamp(22px, 2.2vw, 32px);
  line-height: 1;
  letter-spacing: -.035em;
}

.panel-summary h2 a { color: inherit; text-decoration: none; transition: color .2s; }
.panel-summary h2 a:hover { color: var(--mp-pink-soft); }
.panel-summary h2 a:focus-visible { outline: 2px solid var(--mp-pink); outline-offset: 3px; border-radius: 4px; }

.panel-phrase {
  margin: 9px 0 0;
  color: var(--mp-dim);
  font-family: var(--mp-mono);
  font-size: 10px;
  line-height: 1.55;
}

/* One row, always — it scrolls sideways rather than wrapping, so the compact
   sheet's height does not depend on how chatty the enrichment pass felt. Each tag
   is a real control now: clicking one filters the wall to that style. */
.panel-tags {
  display: flex;
  flex-wrap: nowrap;
  gap: 6px;
  margin-top: 11px;
  overflow-x: auto;
  scrollbar-width: none;
}

.panel-tags::-webkit-scrollbar { display: none; }

.panel-tags button {
  flex: none;
  border: 0;
  cursor: pointer;
  transition: color .15s, background .15s;
}

.panel-tags button:hover { background: #29292f; color: var(--mp-ink); }

.panel-tags button.on {
  background: var(--mp-pink);
  color: var(--mp-ink-on-pink);
  font-weight: 700;
}

.panel-tags button.on:hover { background: var(--mp-pink-soft); color: var(--mp-ink-on-pink); }

.panel-tags button:focus-visible { outline: 2px solid var(--mp-peach); outline-offset: 2px; }

.panel-tags button {
  padding: 5px 8px;
  border-radius: var(--mp-r-pill);
  background: var(--mp-raise);
  color: var(--mp-mute);
  font-family: var(--mp-mono);
  font-size: 8.5px;
  letter-spacing: .12em;
  text-transform: uppercase;
}

.panel-actions { display: flex; align-items: center; gap: 8px; margin-top: 13px; }
.panel-actions .fav-hint { display: none; }

.panel-section { padding: 18px; border-top: 1px solid var(--mp-line); }
.panel-heading { margin-bottom: 11px; }

.panel-heading p,
.panel-label {
  margin: 0 0 4px;
  color: var(--mp-peach);
  font-family: var(--mp-mono);
  font-size: 9px;
  letter-spacing: .16em;
  text-transform: uppercase;
}

.panel-heading h3 { margin: 0; font-size: 16px; letter-spacing: -.01em; }

.panel-about p:not(.panel-label) {
  margin: 0;
  color: var(--mp-dim);
  font-family: var(--mp-mono);
  font-size: 10px;
  line-height: 1.65;
}

.pb-garments,
.pb-similars {
  display: flex;
  gap: 8px;
  padding: 1px 1px 7px;
  overflow-x: auto;
  scrollbar-width: thin;
  scrollbar-color: #45454b transparent;
}

.pb-garment,
.pb-similar {
  flex: 0 0 88px;
  min-width: 0;
  display: block;
  color: var(--mp-dim);
  text-decoration: none;
}

.pb-garment .well,
.pb-similar img {
  position: relative;
  height: 88px;
  width: 88px;
  display: block;
  overflow: hidden;
  border: 1px solid var(--mp-line-soft);
  border-radius: 10px;
  background: radial-gradient(circle at 50% 12%, rgb(var(--accent-rgb, 251 94 190) / .22), var(--mp-raise) 72%);
}

.pb-garment .well img { width: 100%; height: 100%; display: block; object-fit: contain; }
.pb-similar img { object-fit: cover; }

.pb-garment .well.ph {
  display: grid;
  place-items: center;
  padding: 6px;
  color: var(--mp-mute);
  font-family: var(--mp-mono);
  font-size: 8px;
  letter-spacing: .08em;
  text-align: center;
  text-transform: uppercase;
}

.pb-garment.on .well { border-color: var(--mp-pink); box-shadow: 0 0 0 1px var(--mp-pink); }
.pb-garment:hover .well,
.pb-similar:hover img { border-color: var(--mp-pink); }
.pb-garment:focus-visible,
.pb-similar:focus-visible { outline: 2px solid var(--mp-peach); outline-offset: 3px; border-radius: 12px; }

.pb-garment small,
.pb-similar small {
  display: block;
  margin-top: 6px;
  overflow: hidden;
  font-family: var(--mp-mono);
  font-size: 8.5px;
  letter-spacing: .08em;
  text-overflow: ellipsis;
  text-transform: uppercase;
  white-space: nowrap;
}

/* A style nobody has rendered on this pattern yet is muted until it is asked for; the
   D59 badge inside the pane already says why. */
.pb-garment.is-blank .well img { filter: grayscale(1); opacity: .5; }
.pb-garment.is-blank .well { border-style: dashed; }

/* The ring a requested render fills (D83). Drawn over the pane rather than beside it,
   so the thing that is arriving is the thing you are looking at. */
.pb-garment .render {
  position: absolute;
  inset: 50% auto auto 50%;
  z-index: 3;
  width: 52px;
  height: 52px;
  display: grid;
  place-items: center;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  background: conic-gradient(var(--mp-pink) calc(var(--progress, 0) * 1%), rgba(0, 0, 0, .66) 0);
  color: var(--mp-ink);
  font-family: var(--mp-mono);
  font-size: 8px;
}

.pb-garment .render::before {
  content: "";
  position: absolute;
  inset: 5px;
  border-radius: inherit;
  background: #18181b;
}

.pb-garment .render span { position: relative; }

.pb-own {
  display: grid;
  gap: 5px;
  padding: 14px;
  border: 1px solid var(--mp-line-soft);
  border-radius: 12px;
  background: var(--mp-raise);
}

.pb-own.unowned {
  border-color: rgb(251 94 190 / .36);
  background: linear-gradient(135deg, rgb(251 94 190 / .09), transparent);
}

.pb-own span {
  color: var(--mp-pink);
  font-family: var(--mp-mono);
  font-size: 9px;
  letter-spacing: .16em;
  text-transform: uppercase;
}

.pb-own strong { font-size: 14px; }
.pb-own p { margin: 0; color: var(--mp-mute); font-size: 11px; line-height: 1.45; }

/* ══ The desktop rail's hero ══
   On a desktop the panel is a rail, not a sheet, and its artwork is the headline:
   full-bleed under the grip, with the name and details reading below it. (The phone
   sheet keeps the side-by-side compact hero — its fold is too dear for a full-width
   image.) */

@media (min-width: 901px) {
  .panel-hero { display: block; padding: 0; }

  /* The grip floats over the hero as a scrim instead of sitting above it, so the
     artwork owns the rail's full height from the very top edge. */
  .panel-grip {
    position: absolute;
    top: 0;
    right: 0;
    left: 0;
    z-index: 4;
    border-bottom: 0;
    background: linear-gradient(rgba(8, 8, 10, .62), rgba(8, 8, 10, 0));
    color: var(--mp-dim);
  }

  .panel-scroll {
    height: 100%;
    /* No scrollbar lane at all: even a thin one puts a stripe down the hero's right
       edge, and the rail scrolls by wheel and keyboard. The cut-off section below
       the fold is its own affordance. */
    scrollbar-gutter: auto;
    scrollbar-width: none;
  }

  .panel-scroll::-webkit-scrollbar { display: none; }

  .panel-art {
    width: 100%;
    aspect-ratio: 4 / 3;
    border: 0;
    border-radius: 0;
    border-bottom: 1px solid var(--mp-line);
  }

  .panel-art:focus-visible { outline-offset: -3px; }

  .panel-summary { padding: 16px 18px 14px; }
}

/* ══ The prompt dock yields to the panel ══
   Whenever the details surface is visible at all — not only when it is expanded. Two
   floating surfaces competing for the bottom of the screen is the busyness this
   redesign was told to remove. */

.browser:not([data-panel="closed"]) .dock {
  visibility: hidden;
  pointer-events: none;
  opacity: 0;
  transform: translateY(calc(100% + 20px));
  transition: opacity .2s, transform .32s cubic-bezier(.22, .9, .26, 1), visibility 0s linear .32s;
}

.dock { transition: opacity .2s, transform .32s cubic-bezier(.22, .9, .26, 1); }

/* ══ Shortcuts dialog and toast ══ */

.pb-shortcuts {
  width: min(650px, calc(100vw - 28px));
  padding: 0;
  border: 1px solid var(--mp-line-soft);
  border-radius: 17px;
  background: var(--mp-surface);
  color: var(--mp-ink);
  box-shadow: 0 38px 110px rgba(0, 0, 0, .85);
}

.pb-shortcuts::backdrop { background: rgba(0, 0, 0, .76); }
.pb-shortcuts form { padding: 22px; }
.pb-shortcuts header { display: flex; align-items: flex-start; justify-content: space-between; gap: 18px; }

.pb-shortcuts header p {
  margin: 0 0 4px;
  color: var(--mp-peach);
  font-family: var(--mp-mono);
  font-size: 9px;
  letter-spacing: .16em;
  text-transform: uppercase;
}

.pb-shortcuts header h2 { margin: 0; font-size: 26px; letter-spacing: -.03em; }
.pb-shortcut-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 7px; margin: 18px 0; }

.pb-shortcut-grid > div {
  min-height: 44px;
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 8px 10px;
  border: 1px solid var(--mp-line-soft);
  border-radius: 9px;
  background: var(--mp-raise);
}

.pb-shortcut-grid kbd { min-width: 76px; }
.pb-shortcut-grid span { color: var(--mp-dim); font-size: 12px; }
.pb-note { color: var(--mp-mute); font-size: 12px; line-height: 1.55; }

.pb-toast {
  position: fixed;
  left: 50%;
  bottom: 104px;
  z-index: 120;
  max-width: min(540px, calc(100vw - 28px));
  padding: 11px 15px;
  border: 1px solid var(--mp-line-soft);
  border-radius: var(--mp-r-pill);
  background: #222227;
  color: var(--mp-dim);
  font-family: var(--mp-mono);
  font-size: 9px;
  letter-spacing: .08em;
  text-align: center;
  opacity: 0;
  pointer-events: none;
  transform: translate(-50%, 12px);
  transition: opacity .2s, transform .24s;
}

.pb-toast.is-on { opacity: 1; transform: translate(-50%, 0); }

/* A collection may eventually contain many drops. Keep the shared picker grammar, but let its
   option list scroll inside the viewport instead of turning the hero into a second page. */
.browser #collectionMenu { max-height: min(68dvh, 520px); overflow-y: auto; overscroll-behavior: contain; }

/* ══ The lane, not the viewport ══
   With the rail open at 1280px the wall's lane is under 900px, and a hero laid out
   for the window overflows it. Keyed to the pane's own width so it reflows when it
   actually needs to. */

/* Labels leave only when they truly cannot fit. The picker names never hide at all —
   flex shrink ellipsizes them exactly when the row runs out of room and not before —
   and the density's word, the lowest-priority label, goes only on a genuinely tight
   phone lane. The shortcuts link is desktop furniture and steps aside first. */
@container pane (max-width: 880px) {
  .hero { align-items: flex-start; gap: 14px; }
  .pb-toolbar { gap: 12px; }
  .pb-toolbar .pb-density input { width: 72px; }
  .pb-toolbar .pb-shortcut-link { display: none; }
}

@container pane (max-width: 520px) {
  .pb-toolbar .pb-density input { width: 64px; }
}

/* Set by JS exactly when a picker name would otherwise clip — the density word is
   the first label to go when the row truly runs out, and never before. */
.pb-toolbar.pb-tight .pb-density > span { display: none; }

/* ══ Phone and small tablet: the panel becomes a sheet ══ */

@media (max-width: 900px) {
  .browser,
  .browser[data-panel="open"] { display: block; }

  .pb-pane { padding: 0 12px; }

  /* The sticky toolbar yields on a deliberate downward scroll — but only once the
     page has scrolled past where it would naturally have left (the JS gates on that),
     so it never vanishes out from under the hero. Reverse scroll restores it. Its
     z-index leaves the translated controls behind the global bar. */
  .pb-toolbar {
    transition: transform .24s cubic-bezier(.22, .9, .26, 1);
    will-change: transform;
  }

  .pb-toolbar.is-tucked {
    pointer-events: none;
    transform: translate3d(0, calc(-100% - 1px), 0);
  }

  /* Menus hang from the toolbar itself rather than the button: the bar sits against
     the left margin, and a 230px menu hanging off a left-edge button stays on screen
     only by luck. The bar is the containing block, so the panel spans it. */
  .pb-toolbar .pat-wrap { position: static; }
  .pb-toolbar .pat-menu { top: 100%; right: 0; left: 0; max-width: none; }
  .pb-toolbar .pat-opt { white-space: normal; }

  .pb-card { border-radius: 10px; }
  .pb-pos { display: none; }
  .pb-meta { padding: 9px; }
  .pb-meta strong { font-size: 12px; }
  .pb-meta span { display: none; }

  /* On a phone the corner heart stops being a control and becomes a mark. A finger
     cannot aim at a disc this size — four across a 375px phone is an 83px card — and
     the honest sizes for "big enough to tap" and "small enough not to sit on the
     picture" have no overlap here. So favouriting on a touch screen is the card's
     double tap or the sheet's own Favorite pill, and what is left in the corner only
     reports the answer. `pointer-events: none` is the whole of it: taps fall through
     to the card, where the wall's own single/double tap already lives.

     Freed of the 24px tap floor it can shrink to the mark it is — 18px on the densest
     wall, where the old flat 34px disc covered a sixth of the card's area — and the
     ghost goes with the control: an unfavourited heart said "press me" to a finger
     that now presses the card instead, so only a given heart shows. What that ghost
     used to explain, the open sheet says outright, in a pill you can actually hit. */
  .pb-card { --pb-fav: clamp(18px, 20cqi, 30px); }

  .pb-card .hbtn.fav.fav-compact {
    opacity: 1;
    transform: none;
    pointer-events: none;
  }

  .pb-card .fav[aria-pressed="false"] { display: none; }

  .panel {
    --peek: min(360px, 53dvh);
    position: fixed;
    top: auto;
    right: 0;
    bottom: 0;
    left: 0;
    z-index: 60;
    width: auto;
    height: min(86dvh, 760px);
    margin: 0;
    visibility: visible;
    opacity: 1;
    transform: translate3d(0, calc(100% + 12px), 0);
    border: 0;
    border-top: 1px solid rgba(255, 255, 255, .16);
    border-radius: 20px 20px 0 0;
    background:
      linear-gradient(180deg, rgba(14, 14, 17, .8) 0%, rgba(9, 9, 11, .965) 22%, #08080a 60%);
    backdrop-filter: blur(22px) saturate(1.15);
    -webkit-backdrop-filter: blur(22px) saturate(1.15);
    box-shadow: 0 -16px 46px rgba(0, 0, 0, .62);
    transition: transform .36s cubic-bezier(.22, .9, .26, 1);
  }

  .browser[data-panel="compact"] .panel { transform: translate3d(0, calc(100% - var(--peek)), 0); }
  .browser[data-panel="expanded"] .panel { transform: translate3d(0, 0, 0); }
  .browser .panel.is-dragging { transform: translate3d(0, var(--drag-y), 0); transition: none; }

  /* The fitting room's cap: one quiet centred grabber over a continuous glass sheet.
     The full header remains the generous hit target; its label and redundant expand
     circle disappear, while a restrained close action remains at the right edge. */
  .panel-grip {
    position: relative;
    height: 34px;
    justify-content: center;
    padding: 0;
    border-bottom: 0;
    touch-action: none;
    cursor: grab;
  }

  .pb-grip-handle {
    position: absolute;
    inset: 0;
    justify-content: center;
    gap: 0;
    cursor: grab;
    touch-action: none;
    user-select: none;
  }

  .pb-grip-handle > span:not(.pb-grabber) { display: none; }
  .panel.is-dragging { user-select: none; }
  .pb-grabber {
    display: block;
    flex: none;
    width: 42px;
    height: 4px;
    background: rgba(255, 255, 255, .28);
  }

  .panel-controls {
    position: absolute;
    top: 0;
    right: 8px;
    z-index: 2;
    height: 34px;
    gap: 0;
    margin: 0;
  }

  .panel-expand { display: none !important; }

  .panel-controls button {
    width: 34px;
    height: 34px;
    border: 0;
    border-radius: 0;
    background: transparent;
    color: rgba(255, 255, 255, .5);
    font-size: 20px;
  }

  .panel-controls button:hover { border-color: transparent; background: rgba(255, 255, 255, .06); }

  .panel-scroll { height: calc(100% - 34px); padding-bottom: env(safe-area-inset-bottom); }

  /* The sheet's gesture drives the body's scrolling itself, at both detents — the
     fitting room's rule, and the only arrangement that lets one stroke scroll the
     copy and then move the sheet without the UA cancelling the pointer mid-hand-off.
     Horizontal strips keep their own native panning below. */
  .browser[data-panel="compact"] .panel-scroll,
  .browser[data-panel="expanded"] .panel-scroll {
    overflow: hidden;
    touch-action: none;
  }

  .browser[data-panel="compact"] .pb-garments,
  .browser[data-panel="expanded"] .pb-garments,
  .browser[data-panel="expanded"] .pb-similars {
    touch-action: pan-x;
  }

  .panel-art { touch-action: none; }
  /* The column is sized by what has to sit under the artwork, not by the artwork: the heart
     and the thumbs need 110px on one line, and a narrower column left them hanging over the
     summary. The picture simply gets the same width. */
  .panel-hero { grid-template-columns: 118px minmax(0, 1fr); gap: 11px; padding: 10px 12px 8px; }
  .panel-summary h2 { font-size: clamp(21px, 6.6vw, 28px); }
  .panel-phrase { margin-top: 6px; font-size: 9px; line-height: 1.45; }
  .panel-tags { gap: 4px; margin-top: 7px; }
  .panel-actions { gap: 6px; margin-top: 9px; }

  /* Everything a visitor came for sits above the compact fold: name, heart and
     thumbs. The garment strip is one scroll away and is the only door into the
     fitting room; the description and the neighbours are what expanding reveals —
     the same tree, more of it. */
  .panel-section { padding: 12px; }

  .pb-toast { bottom: 84px; }
}

@media (max-width: 560px) {
  .pb-shortcut-grid { grid-template-columns: 1fr; }
  /* Deliberately not narrowed further: the verdict row under the artwork sets this floor. */
}

/* The narrowest phones. The action row wraps rather than pushing the thumbs past
   the sheet's own edge. */
@media (max-width: 380px) {
  .panel-actions { flex-wrap: wrap; }
}

@media (prefers-reduced-motion: reduce) {
  .browser,
  .filters,
  .panel,
  .dock,
  .pb-card,
  .pb-art img,
  .pb-card .well,
  .pb-card .well img,
  .pb-meta,
  .pb-card .fav,
  .pb-weave,
  .pb-weave-hint,
  .pb-toast {
    transition-duration: .01ms !important;
    animation-duration: .01ms !important;
  }

  .pb-spinner { animation: none !important; }
}

/* ══ The generation shelves (D91) ═══════════════════════════════════════════════
   Three pieces of furniture: the collection menu's expandable "Generated by you"
   group, the run context bar under the refinement row, and the panel's editable
   "your prompt" form. Peach marks the generative moments, per the token sheet;
   the prompts themselves are prose — the visitor's own words — so they render in
   the sans face, never the uppercase mono chrome wears. */

/* The expandable row is one full-width option with a twisty riding its leading gutter.
   Every row of the Yours group takes the same gutter (.pat-gut) whenever the twisty
   renders, so labels — and the counts at the far edge — hold one straight column. */
.pat-row { position: relative; display: flex; }
.pat-row .pat-opt { flex: 1; min-width: 0; }

.pat-opt.pat-gut { padding-left: 34px; }

.pat-expand {
  position: absolute;
  left: 4px;
  top: 50%;
  transform: translateY(-50%);
  width: 26px;
  height: 26px;
  border: 0;
  border-radius: 7px;
  background: none;
  cursor: pointer;
  color: var(--mp-mute);
  display: grid;
  place-items: center;
  z-index: 1;
}

.pat-expand svg {
  width: 12px;
  height: 12px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
  transition: transform .18s;
  transform: rotate(-90deg);
}

.pat-expand:hover { color: var(--mp-ink); background: var(--mp-raise); }
.pat-expand[aria-expanded="true"] svg { transform: rotate(0deg); }

/* The runs nest under the shelf row: full-width rows held by a hairline dropped from
   the twisty's own column, so their counts right-align with every other row's. The
   prompt is the label — sans, sentence case, one ellipsized line — because a run has
   no honest name except what was asked for. */
.pat-sub {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin-left: 16px;
  padding-left: 6px;
  border-left: 1px solid var(--mp-line);
}

.pat-sub[hidden] { display: none; }

.pat-opt.pat-run { text-align: left; }

.pat-run-prompt {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  text-align: left;
  font-family: var(--mp-sans);
  font-size: 12px;
  letter-spacing: 0;
  text-transform: none;
}

/* The run's count is data, not a control state: keep it right-aligned in the same
   column as every other row's count, with the live pip just inside it. */
.pat-run b { flex: none; }

.pat-run-live {
  flex: none;
  width: 7px;
  height: 7px;
  border-radius: var(--mp-r-pill);
  background: var(--mp-peach);
  animation: pb-run-pulse 1.6s ease-in-out infinite;
}

@keyframes pb-run-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: .35; }
}

.pat-sub .pat-all { border-top: 0; padding-top: 5px; }

/* ── the run context bar ── */

.pb-gen {
  margin: 0 0 16px;
  padding: 13px 16px;
  border: 1px solid var(--mp-line-soft);
  border-left: 2px solid var(--mp-peach);
  border-radius: var(--mp-r-card);
  background: var(--mp-surface);
}

.pb-gen-prompt {
  margin: 0;
  color: var(--mp-ink);
  font-family: var(--mp-sans);
  font-size: 14px;
  line-height: 1.5;
}

.pb-gen-row {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  margin-top: 8px;
}

.pb-gen-status { margin: 0; flex: 1; min-width: 180px; }
.pb-gen[data-live="1"] .pb-gen-status { color: var(--mp-peach); }

.pb-gen-actions { display: flex; align-items: center; gap: 12px; }

.pb-gen-more { height: 34px; padding: 0 16px; font-size: 11px; }
.pb-gen-more[disabled] { opacity: .45; cursor: not-allowed; }
.pb-gen-more[disabled]:hover { background: var(--mp-pink); }

.pb-gen-studio {
  font-family: var(--mp-mono);
  font-size: 10px;
  letter-spacing: .14em;
  text-transform: uppercase;
}

.pb-gen-note { margin: 0; }

/* ── warming up ── */

.pb-warm {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: 7px 12px;
  margin-top: 11px;
}

.pb-warm[hidden] { display: none; }

/* The line that changes its mind. Peach, because this is the generative moment; it fades
   between thoughts rather than snapping, so the eye follows it instead of being startled. */
.pb-warm-say {
  grid-column: 1 / -1;
  margin: 0;
  color: var(--mp-peach);
  font-family: var(--mp-sans);
  font-size: 14px;
  transition: opacity .26s ease;
}

.pb-warm-track {
  flex: 1;
  height: 3px;
  border-radius: var(--mp-r-pill);
  background: rgba(255, 255, 255, .08);
  overflow: hidden;
}

/* Paced against the whole one-to-three-minute range a fan-out actually takes, not its
   optimistic end: 40% at a minute, 70% at two, 90% at three, and then it holds. Under-promising
   is the point — a bar that reaches the top while the visitor is still waiting has told them
   something false, and they will remember that longer than the wait. */
.pb-warm-fill {
  display: block;
  height: 100%;
  width: 0;
  border-radius: inherit;
  background: linear-gradient(90deg, var(--mp-peach), var(--mp-pink));
  animation: pb-warm 180s linear forwards;
}

@keyframes pb-warm {
  0% { width: 0; }
  33% { width: 40%; }
  67% { width: 70%; }
  100% { width: 90%; }
}

.pb-warm-note {
  flex: none;
  color: var(--mp-mute);
  font-family: var(--mp-mono);
  font-size: 9px;
  letter-spacing: .14em;
  text-transform: uppercase;
}

/* ── the slots still coming ──
   Chrome, not content: a ghost never becomes a card — the real one arrives as a
   server-rendered fragment — it only holds the shape of the run so the grid does not
   grow into the page a tile at a time. */
/* A waiting slot does not move. It used to: every ghost ran a gradient across its own
   background and a scale-and-border breath, forever, and both of those repaint — sixty tiles
   of it for the length of a run was enough to bog a browser down watching its own generation.
   What is left is a still square, and the waiting is carried by two things that cost nothing:
   the slot's one-time arrival, and a single band of light crossing the whole wall. */
.pb-ghost {
  position: relative;
  aspect-ratio: 1;
  overflow: hidden;
  border: 1px solid var(--mp-line);
  border-radius: 12px;
  background: var(--mp-surface);
}

/* The arrival is news — a slot has been claimed — so it plays once, when the run is claimed,
   and never again: not on reload, not on coming back to a run still going. The script decides
   who is owed it (a first-sight marker per run) and paces the beat so the grid fills in ten
   seconds whatever the slot count. */
.pb-ghost.is-arriving {
  animation: pb-ghost-arrive 1.1s cubic-bezier(.2, .9, .24, 1) both;
  animation-delay: calc(var(--i, 0) * var(--pb-step, 0ms));
}

@keyframes pb-ghost-arrive {
  from { opacity: 0; transform: scale(.46); border-color: rgba(255, 201, 160, .5); }
  60% { opacity: 1; }
  to { opacity: 1; transform: scale(1); border-color: var(--mp-line); }
}

/* The slot is waiting on something and says so quietly. It fades in behind its own square, so
   the ring never announces itself before the thing it belongs to. */
.pb-ghost .ring {
  position: absolute;
  inset: 50% auto auto 50%;
  width: 22px;
  height: 22px;
  margin: -11px 0 0 -11px;
  border: 1.5px solid rgba(255, 255, 255, .09);
  border-top-color: rgba(255, 201, 160, .42);
  border-radius: 50%;
  animation: pb-ring-turn 2.6s linear infinite, pb-ring-in .9s ease both;
  animation-delay: calc(var(--i, 0) * var(--pb-step, 0ms)),
                   calc(var(--i, 0) * var(--pb-step, 0ms) + .5s);
  will-change: transform;
}

@keyframes pb-ring-turn { to { transform: rotate(1turn); } }
@keyframes pb-ring-in { from { opacity: 0; } to { opacity: 1; } }

/* One band for the whole wall, not one per tile. Sixty offset copies of the same sweep only
   ever approximated a single light crossing the grid, and the approximation had to be
   re-phased in script at every column count. Drawn once it is exactly right at any width and
   any density, and it is one animated element instead of sixty.

   It is masked to the slots still waiting — the script rebuilds an SVG of their boxes — so it
   passes behind the patterns that have already landed without touching them: a finished
   pattern is not waiting for anything. */
.pb-glide {
  position: absolute;
  inset: 0;
  z-index: 1;
  overflow: hidden;
  pointer-events: none;
  mask-repeat: no-repeat;
  mask-size: 100% 100%;
}

.pb-glide::before {
  content: "";
  position: absolute;
  inset: -20% -60%;
  background: linear-gradient(100deg, transparent 44%, rgba(255, 201, 160, .13) 49%,
                              rgba(251, 94, 190, .10) 54%, transparent 60%);
  /* `backwards` is load-bearing: without it the band sits parked over the grid through its
     delay, and every slot would arrive already wearing a gradient. */
  animation: pb-glide-cross 9s cubic-bezier(.45, 0, .55, 1) infinite backwards;
  /* Nothing sweeps while the wall is still filling — two things moving at once is the
     busyness this replaced. A run being rejoined has no fill to wait for, so its wait is 0. */
  animation-delay: var(--pb-glide-wait, 0ms);
  will-change: transform;
}

@keyframes pb-glide-cross {
  0% { transform: translateX(-42%); }
  55%, 100% { transform: translateX(42%); }
}

/* ── the panel's own-prompt form and unnamed state ── */

/* The artwork and the verdicts share the narrow column. This is the phone's layout and the
   markup's own shape; the desktop rule below lifts the row out of the column without the
   fragment being rendered twice. */
.panel-side { display: flex; flex-direction: column; gap: 9px; min-width: 0; }

/* One line, always: the sheet narrows this column to ~92px and the pair needs ~110px, so the
   row is sized to its content and allowed to reach past the column's edge into the gutter
   rather than folding the thumbs under the heart. */
.browser .panel-side .panel-actions {
  margin: 0;
  gap: 6px;
  flex-wrap: nowrap;
  width: max-content;
}

/* In the column there is no room for words: the heart is its glyph, the thumbs their pair.
   Both controls are rendered in their labelled form and reduced here, so the desktop rule
   has a label to reveal rather than a second partial to keep in step. */
.panel-side .fav-label { display: none; }

.panel-side .hbtn.fav {
  width: 34px;
  height: 34px;
  min-height: 0;
  padding: 0;
  justify-content: center;
}

/* The shared pill keeps its ♥ pink at all times because its label carries the pressed
   state. Here the glyph IS the state — an always-pink heart on an unfavorited pattern
   reads as already-on — so it is a ghost until it is actually pressed, matching the
   thumbs beside it. */
.panel-side .fav .heart { color: var(--mp-mute); font-size: 13px; }
.panel-side .fav:hover .heart { color: var(--mp-pink-soft); }
.panel-side .fav[aria-pressed="true"] .heart { color: var(--mp-pink); }

.panel-side .rate { gap: 2px; }
.panel-side .rate .rate-btn { width: 30px; height: 30px; }

/* Past the sheet's breakpoint the hero stops being a two-column grid — the sidecar's artwork
   goes full-bleed — so this row is already the panel's full width and only has to spread: the
   heart takes the left edge and wears its label again, the thumbs take the right. */
@media (min-width: 900.02px) {
  .browser .panel-side .panel-actions {
    /* Full width, not the phone's content-sized row — otherwise space-between has nothing
       to spread — and inset to the summary's own 18px so the heart starts on the title's
       line and the thumbs finish on the copy's right edge. */
    width: auto;
    padding: 0 18px;
    justify-content: space-between;
    gap: 12px;
    margin-top: 3px;
  }

  .panel-side .fav-label { display: inline; }

  .panel-side .hbtn.fav {
    width: auto;
    height: 38px;
    padding: 0 16px;
    justify-content: center;
  }

  .panel-side .fav .heart { font-size: 14px; }
  .panel-side .rate .rate-btn { width: 34px; height: 34px; }
}

/* The privacy claim is a chip beside the name, not a line of its own. Peach — it is
   a statement about the generative moment, not an interactive word. */
.panel-private {
  display: inline-block;
  vertical-align: 3px;
  margin-left: 7px;
  padding: 3px 8px;
  border: 1px solid rgba(255, 201, 160, .4);
  border-radius: var(--mp-r-pill);
  color: var(--mp-peach);
  font-family: var(--mp-mono);
  font-size: 8px;
  font-weight: 400;
  letter-spacing: .14em;
  text-transform: uppercase;
  white-space: nowrap;
}

/* No heading yet: the chip carries the line on its own, with the quiet note that a name
   is on its way. Same height as the h2 it stands in for, so the panel does not jump when
   the real title arrives. */
.panel-unnamed { display: flex; align-items: center; gap: 10px; margin: 0 0 2px; }
.panel-unnamed .panel-private { margin-left: 0; vertical-align: baseline; }

.panel-naming {
  color: var(--mp-mute);
  font-family: var(--mp-mono);
  font-size: 10px;
  letter-spacing: .12em;
  text-transform: uppercase;
}

/* The prompt is a miniature of the dock it really is: one surface, no label spending a
   line. The field grows downward as the prompt does (js/prompt-dock.js measures it), and
   the button sits in a band the text is padded out of — so nothing ever slides behind it,
   and the reserved space is one button tall rather than a whole blocked-off edge. */
.panel-prompt { position: relative; margin-top: 9px; }

.panel-prompt textarea {
  display: block;
  width: 100%;
  resize: none;
  /* The field grows to its content, so a scrollbar is furniture for a state that only
     exists past 40vh of prompt — and a permanent one (which is what a trackpad-less
     platform renders) sat there looking like the text was cut off. Scrolling still works
     at the cap; the bar is simply not drawn. */
  overflow: auto;
  scrollbar-width: none;
  min-height: 72px;
  max-height: 40vh;
  padding: 10px 12px 42px;
  background: var(--mp-raise);
  border: 1px solid var(--mp-line-soft);
  border-radius: 12px;
  color: var(--mp-ink);
  font-family: var(--mp-sans);
  font-size: 13px;
  line-height: 1.5;
}

.panel-prompt textarea::-webkit-scrollbar { display: none; }

.panel-prompt textarea:focus-visible {
  outline: none;
  border-color: rgba(255, 201, 160, .5);
  box-shadow: 0 0 0 3px rgba(255, 201, 160, .12);
}

.panel-prompt .genb {
  position: absolute;
  right: 7px;
  bottom: 7px;
  height: 30px;
  padding: 0 14px;
  font-size: 10px;
  max-width: calc(100% - 14px);
}

@media (max-width: 900px) {
  /* Under iOS's 16px auto-zoom floor the field would zoom the sheet on focus. */
  .panel-prompt textarea { font-size: 16px; padding-bottom: 46px; }
}

@media (prefers-reduced-motion: reduce) {
  .pat-run-live,
  .pb-ghost,
  .pb-ghost .ring,
  .pb-glide::before { animation: none !important; }

  /* Motion off keeps the waiting room and drops the waiting: slots are simply present, the
     band does not run at all, and the ring holds still but stays — it is the only thing left
     saying the slot is working. */
  .pb-ghost.is-arriving { opacity: 1; transform: none; }
  .pb-glide { display: none; }

  /* The warm-up bar still has a job when motion is off — it is the only thing saying the
     wait is bounded — so it holds a steady share rather than animating to it. */
  .pb-warm-fill { animation: none !important; width: 45%; }
}
