/* Sttchr web storefront - editorial "Apple" layer.
 *
 * Loaded AFTER app.css. Every rule is scoped so it can only reach a page that
 * opts in, and nothing here leaks to the feed or to a page that did not ask:
 *   - Buying surfaces (/shop, /creator, /pattern) AND /account: `body.ui-apple`.
 *     The account library is a grid of the same cards, so it opted in too.
 *   - Sell dashboard (/sell): `#dashSection`, which exists on that page only,
 *     plus `body.dash-tint` for the page background. See the dashboard section
 *     at the bottom for why sell.html does not carry `ui-apple`.
 *   - Review queue (/review): `body.ui-review`.
 *   - The listing sheet (.pm-sheet): keyed off the component rather than a page
 *     class. It used to be the listing UI on five pages; now /pattern is the
 *     listing UI and the sheet survives only as the seller's Preview on /sell.
 *     feed.html and search.html still load this file for the card rules and the
 *     unscoped owned badge.
 *
 * Two rules from app.css are deliberately NOT overridden:
 *   - The left sidebar (.feedside) and its 220px body padding. That chrome is
 *     shared with every other page and stays exactly as it is.
 *   - Light mode. app.css pins `color-scheme: light` on purpose and dropped its
 *     dark tokens, so this layer is light-only too. A page that followed
 *     prefers-color-scheme would sit next to a permanently white sidebar.
 *
 * Type floor: nothing a buyer reads goes below 14px. 12px is reserved for chrome
 * and legal. Every interactive control is at least 44px tall (HIG minimum).
 */

/* Apple's section grey. The accent stays the product's brand blue (#007AFF from
   app.css) rather than apple.com's #0071e3 - the brand colour is pinned across
   app and web, and one page inventing its own blue would break that.
   Declaring the values here is inert: only the scoped rules below consume them. */
:root {
  --a-grey: #f5f5f7;
  --a-text2: #6e6e73;
  --a-text3: #86868b;
  --a-sep: rgba(0, 0, 0, .10);
  --a-track: rgba(120, 120, 128, .12);
  --a-ease: cubic-bezier(.25, .1, .25, 1);
  --a-shadow: 0 1px 3px rgba(0, 0, 0, .06), 0 8px 24px rgba(0, 0, 0, .04);
}

/* The `hidden` attribute has to actually hide. The UA rule is `[hidden]{display:none}`
   at specificity (0,1,0), so ANY class that sets `display` beats it - and most of
   the components below do. That silently broke every element this layer hides:
   the "Makers" heading and the featured hero stayed on screen through a filter,
   the Level menu showed for a single-difficulty catalogue, and "Load more" sat
   there permanently doing nothing. `!important` is the fix normalize.css ships for
   exactly this. Audited: every `hidden` on these pages means it. */
[hidden] { display: none !important; }

body.ui-apple {
  font-feature-settings: "tnum";   /* lining figures, so prices align */
}

body.ui-apple .page { padding-bottom: 0; }

/* ---- page title -------------------------------------------------------- */
.a-head { padding: 18px 0 0; }
.a-head h1 { font-family: var(--font-display); font-size: 40px; line-height: 1.08;
  letter-spacing: -.03em; font-weight: 600; margin: 0; }
.a-head .a-sub { font-size: 17px; color: var(--a-text2); margin: 8px 0 0; }

/* ---- search: a blur-material pill, not a bordered box ------------------- */
/* A <label> so the whole pill focuses the field. min-height goes on the input
   as well as the wrapper - setting it only on the wrapper leaves the actual
   touch target (the input) at 41px, which is under the 44pt minimum. */
.a-srch { display: flex; align-items: center; gap: 10px; margin: 22px 0 18px;
  background: var(--a-track); border-radius: var(--radius-pill); padding: 0 16px;
  min-height: 44px; max-width: 420px; }
.a-srch svg { width: 17px; height: 17px; color: var(--a-text3); flex: 0 0 auto; }
.a-srch input { border: 0; outline: 0; background: none; font: inherit; font-size: 16px;
  color: var(--text); width: 100%; padding: 0; min-height: 44px; }
.a-srch input::placeholder { color: var(--a-text3); }
.a-srch input::-webkit-search-cancel-button { -webkit-appearance: none; }

/* ---- filters: visible controls, not a popover holding hidden state ------ */
/* Two stacked rows, both left-aligned: narrow the set, then order it. */
.a-ctrls { display: flex; flex-direction: column; align-items: flex-start; gap: 14px;
  margin-bottom: 30px; max-width: 100%; }
/* The controls sit directly under the section heading they act on, so that
   heading gives up its own bottom margin. */
.a-sec + .a-ctrls { margin-top: -6px; }
.a-filters { display: flex; align-items: center; gap: 10px; max-width: 100%; min-width: 0; }
/* The chip strip is the only part that scrolls, so Free stays reachable no matter
   how far the categories run. */
.a-filters .a-chips { min-width: 0; flex: 0 1 auto; }
.a-filters .a-pill { flex: 0 0 auto; }
.a-vr { flex: 0 0 auto; width: 1px; height: 24px; background: var(--a-sep); }
.a-seg { display: inline-flex; background: var(--a-track); border-radius: 11px; padding: 3px;
  gap: 2px; overflow-x: auto; max-width: 100%; scrollbar-width: none; }
.a-seg::-webkit-scrollbar { display: none; }
.a-seg button { border: 0; background: none; font: inherit; font-size: 15px; font-weight: 590;
  color: var(--text); padding: 9px 15px; border-radius: 9px; cursor: pointer;
  white-space: nowrap; min-height: 44px; }
.a-seg button.on { background: #fff; box-shadow: 0 1px 3px rgba(0, 0, 0, .12); }
/* A count inside the tab, so "Purchased 12" is the label. The dashboard has the
   same idea at #dashSection .dsh-seg button .n, but that copy is page-scoped and
   unreachable from here. */
.a-seg button .n { color: var(--a-text3); font-weight: 510; margin-left: 6px; }
.a-seg button.on .n { color: var(--a-text2); }

/* Categories are chips, not one long segmented track. A segmented control is for
   a handful of peers; at nine categories the track ran the full width and the
   Free toggle parked on its end looked like a tenth category. Chips scroll, and
   the selection is a solid fill rather than a slot in a rail. */
.a-chips { display: flex; align-items: center; gap: 8px; overflow-x: auto; max-width: 100%;
  padding: 2px 0; scrollbar-width: none; }
.a-chips::-webkit-scrollbar { display: none; }
.a-chips button { flex: 0 0 auto; border: 0; background: var(--a-track); font: inherit;
  font-size: 15px; font-weight: 590; color: var(--text); padding: 0 16px; min-height: 44px;
  border-radius: var(--radius-pill); cursor: pointer; white-space: nowrap; }
.a-chips button:hover { background: rgba(120, 120, 128, .2); }
.a-chips button.on { background: var(--text); color: #fff; }

/* Free is an on/off refinement, not a category, so it lives with the other
   refinements and reads as a switch: outlined off, accent-filled on. The label
   plus `aria-pressed` carry the state, never the colour alone. */
.a-pill { border: 1px solid var(--a-sep); background: none; font: inherit; font-size: 15px;
  font-weight: 590; color: var(--text); padding: 0 18px; min-height: 44px;
  border-radius: var(--radius-pill); cursor: pointer; }
.a-pill:hover { background: var(--a-track); }
.a-pill[aria-pressed="true"] { background: var(--accent); border-color: var(--accent); color: #fff; }
.a-pill[aria-pressed="true"]:hover { background: var(--accent-strong); }

.a-menus { display: flex; align-items: center; gap: 18px; flex-wrap: wrap; }
.a-menu { display: inline-flex; align-items: center; gap: 7px; font-size: 15px; color: var(--a-text2); }
.a-menu select { border: 0; background: none; font: inherit; font-size: 15px; font-weight: 590;
  color: var(--accent); cursor: pointer; min-height: 44px; padding: 0; }

/* ---- editorial hero: one listing gets the stage ------------------------- */
.a-feat { display: grid; grid-template-columns: 1.05fr .95fr; align-items: center;
  background: var(--a-grey); border-radius: 24px; overflow: hidden; margin-bottom: 64px; }
.a-feat .a-feat-img { width: 100%; height: 100%; min-height: 340px; object-fit: cover;
  display: block; background: var(--fill-strong); }
.a-feat .a-feat-copy { padding: 44px 42px; }
.a-feat .a-kick { font-size: 14px; font-weight: 600; color: var(--accent); }
.a-feat h2 { font-family: var(--font-display); font-size: 34px; line-height: 1.1;
  letter-spacing: -.03em; font-weight: 600; margin: 9px 0 0; }
.a-feat .a-by { font-size: 15px; color: var(--a-text2); margin-top: 7px; }
.a-feat p { font-size: 17px; line-height: 1.5; color: var(--a-text2); margin: 14px 0 0; max-width: 380px; }
.a-feat .a-pr { font-size: 17px; margin-top: 20px; }
.a-feat .a-pr .sm { display: block; font-size: 14px; color: var(--a-text3); margin-top: 4px; }

/* The hero rotates through every paid listing, so it needs controls - and they
   have to be SIBLINGS of the hero, because the hero is one big <a> and a button
   inside a link is invalid (a dot click would navigate instead of paging). */
.a-featwrap { position: relative; margin-bottom: 64px; }
.a-featwrap .a-feat { margin-bottom: 0; }
.a-featnav { display: flex; align-items: center; justify-content: center; gap: 6px; margin-top: 18px; }
.a-featdots { display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  justify-content: center; max-width: 100%; }
/* The dot is 8px of paint inside a 44px target: the size Apple draws is not the
   size a finger needs, and there can be a dozen of these in a row. */
.a-featdot { width: 20px; height: 44px; padding: 0; border: 0; background: none; cursor: pointer;
  position: relative; }
.a-featdot::after { content: ""; position: absolute; left: 50%; top: 50%;
  width: 8px; height: 8px; margin: -4px 0 0 -4px; border-radius: 50%;
  background: var(--fill-strong); transition: background .2s var(--a-ease); }
.a-featdot:hover::after { background: var(--a-text3); }
.a-featdot.on::after { background: var(--text); }
.a-featarrow { flex: 0 0 auto; width: 44px; height: 44px; border: 0; border-radius: 50%;
  background: none; color: var(--a-text3); display: grid; place-items: center; cursor: pointer; }
.a-featarrow:hover { background: var(--a-track); color: var(--text); }
.a-featarrow svg { width: 16px; height: 16px; }
/* The ring belongs on the 8px of paint, not on the 20x44 hit area — as an
   outline it drew a tall pill around a dot and read as a rendering fault. */
.a-featdot:focus-visible { outline: none; }
.a-featdot:focus-visible::after { box-shadow: 0 0 0 2px #fff, 0 0 0 4px var(--accent); }
/* Phone: one count instead of a wrapped field of specks (see shop.html). */
.a-featcount { display: none; min-width: 62px; text-align: center; font-size: 15px;
  font-weight: 590; color: var(--a-text2); font-feature-settings: "tnum"; }
@media (max-width: 700px) {
  .a-featdots { display: none; }
  .a-featcount { display: block; }
  .a-featnav { gap: 2px; }
}
@media (prefers-reduced-motion: reduce) { .a-featdot::after { transition: none; } }

/* Phone: the four filter controls collapse behind one button. Above 700px they
   are just visible, which is the whole point of having room. */
.a-filtbtn { display: none; align-items: center; gap: 8px; min-height: 44px; padding: 0 18px;
  border: 1px solid var(--a-sep); border-radius: var(--radius-pill); background: none;
  font: inherit; font-size: 15px; font-weight: 590; color: var(--text); cursor: pointer; }
.a-filtbtn svg { width: 17px; height: 17px; color: var(--a-text2); }
/* Active count, so a collapsed panel can never hide a filter that is ON. */
.a-filtbtn .n:not(:empty) { display: inline-flex; align-items: center; justify-content: center;
  min-width: 20px; height: 20px; padding: 0 6px; border-radius: 999px;
  background: var(--accent); color: #fff; font-size: 13px; font-weight: 590; }
.a-filtbtn.has { border-color: var(--accent); color: var(--accent); }
.a-filtbtn.has svg { color: var(--accent); }
@media (max-width: 700px) {
  .a-filtbtn { display: inline-flex; }
  .a-ctrls > .a-filters, .a-ctrls > .a-menus { display: none; }
  .a-ctrls.open > .a-filters { display: flex; }
  .a-ctrls.open > .a-menus { display: flex; }
}

.a-cta { display: inline-flex; align-items: center; justify-content: center; background: var(--accent);
  color: #fff; font-size: 15px; font-weight: 500; padding: 12px 22px; border: 0;
  border-radius: var(--radius-pill); min-height: 44px; margin-top: 22px; cursor: pointer; }
.a-cta:hover { background: var(--accent-strong); }

/* ---- section heads ------------------------------------------------------ */
.a-sec { display: flex; align-items: baseline; gap: 12px; margin: 0 0 22px; }
.a-sec h2 { font-family: var(--font-display); font-size: 26px; letter-spacing: -.025em;
  font-weight: 600; margin: 0; }
.a-sec .n { font-size: 15px; color: var(--a-text3); }

/* ---- loading skeleton --------------------------------------------------- */
/* Mirrors the real card's shape, not just its cover. A bare square skeleton
   under a card with four text lines means the grid grows the moment data lands,
   and the hero and makers shelf had no placeholder at all - so everything below
   them jumped down ~600px. The point of a skeleton is that nothing moves. */
.sk-line { background: var(--fill); border-radius: 6px; height: 12px; margin: 0 auto; }
.sk-dark { background: var(--fill-strong); }
/* The min-heights are MEASURED against the loaded page (hero 441, maker 152, card
   452 at 1440px), not guessed. Stacked placeholder lines alone came up 137px
   short, because real titles and maker names wrap to a second line often enough
   that the minimum case is the wrong thing to reserve. These will drift if the
   card design changes; they are an approximation, which is what a skeleton is. */
body.ui-apple .card.is-sk { pointer-events: none; min-height: 452px; }
body.ui-apple .card.is-sk .cover-wrap { background: var(--fill); }
.a-feat.is-sk { pointer-events: none; min-height: 440px; }
.a-feat.is-sk .a-feat-img { background: var(--fill); }
.crcard.is-sk { pointer-events: none; min-height: 152px; }
/* Creator shop first paint. Measured against the loaded page: hero 495, tab row 51. */
.a-crhero.is-sk { min-height: 495px; }
.a-crhero.is-sk .av { background: var(--fill); }
.a-crhero.is-sk .sk-line { margin-left: auto; margin-right: auto; }
.cr-tabrow.is-sk { min-height: 51px; }
.crcard.is-sk .av { background: var(--fill); }
@media (max-width: 860px) {
  .a-feat.is-sk { min-height: 520px; }   /* stacks: image over copy */
}
@media (max-width: 560px) {
  body.ui-apple .card.is-sk { min-height: 420px; }
}
.a-chips .sk-chip { flex: 0 0 auto; height: 44px; border-radius: var(--radius-pill);
  background: var(--fill); }

/* ---- makers rail: circular avatars, App Store shelf --------------------- */
body.ui-apple .rail { gap: 30px; padding-bottom: 6px; margin-bottom: 60px; }
body.ui-apple .crcard { width: 104px; }
/* The avatar is a <div> here (it has to hold the initial fallback), so the
   object-fit on app.css's `.crcard .av` no longer applies - that rule only works
   on a replaced element. Without sizing the child <img>, the photo rendered at its
   natural size and `overflow:hidden` showed a tiny centre crop: a white logo came
   out blank and a dark one came out solid black. */
body.ui-apple .crcard .av { position: relative; width: 84px; height: 84px; background: var(--a-track);
  display: grid; place-items: center; margin: 0 auto; font-size: 30px; font-weight: 600;
  color: var(--a-text2); overflow: hidden; }
/* Absolute, not `width/height:100%` on a centred grid item: the row is auto-sized,
   so a percentage height resolved against the image's own aspect and pushed tall
   avatars out of the circle (measured 126px inside an 84px well). */
body.ui-apple .crcard .av img { position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; display: block; }
body.ui-apple .crcard .nm { font-size: 14px; font-weight: 590; color: var(--text);
  margin-top: 11px; line-height: 1.3; white-space: normal; }
body.ui-apple .crcard .cf { font-size: 14px; color: var(--a-text3); margin-top: 2px; white-space: normal; }

/* ---- product grid: borderless. Image, name, maker, meta, price ---------- */
/* The card markup is still Sttchr.patternCard()'s, so the reel-hover wiring,
   the owned badge and the pattern modal all keep working untouched. */
body.ui-apple .grid { grid-template-columns: repeat(3, 1fr); gap: 48px 30px; margin-bottom: 26px; }
body.ui-apple .card { text-align: center; }
body.ui-apple .card .cover-wrap { border-radius: 18px; background: var(--a-grey); }
body.ui-apple .card .cover { transition: transform .5s var(--a-ease); }
body.ui-apple .card:hover .cover { transform: scale(1.035); }
body.ui-apple .card .title { font-size: 18px; font-weight: 600; letter-spacing: -.015em;
  margin-top: 16px; white-space: normal; overflow: visible; }
body.ui-apple .card .cby { font-size: 15px; color: var(--a-text2); margin-top: 5px; }
body.ui-apple .card .cmeta { font-size: 14px; color: var(--a-text3); margin-top: 8px; }
body.ui-apple .card .crate { justify-content: center; margin-top: 8px; }
body.ui-apple .card .cprice { font-size: 17px; font-weight: 400; margin-top: 10px; }
body.ui-apple .card .cprice.free { color: var(--text); }
/* The hover-preview reel and the play badge follow the cover's new radius. */
body.ui-apple .card .cover-reel { transition: opacity .35s ease; }

/* ---- cover badges: material, not flags ---------------------------------- */
/* A saturated green rectangle stamped on the artwork is a sticker. Apple marks
   state on media with a translucent capsule that picks up whatever is behind it
   (Photos' Live badge, the TV app's Purchased) - so it sits ON the cover instead
   of on top of it, and it reads on a light or dark image without changing colour.
   Dropping the green costs nothing: the word and the checkmark carry the meaning,
   which is the rule anyway. Unscoped on purpose - every page with cards loads this
   file, and a badge that changed shape between pages would be worse than either. */
.card .owned-badge, .card .card-badge {
  top: 10px; left: 10px; display: inline-flex; align-items: center; gap: 5px;
  min-height: 24px; padding: 0 10px; border-radius: var(--radius-pill);
  background: rgba(0, 0, 0, .52); color: #fff; font-size: 12px; font-weight: 590;
  letter-spacing: 0; box-shadow: 0 1px 3px rgba(0, 0, 0, .18);
  -webkit-backdrop-filter: saturate(180%) blur(20px); backdrop-filter: saturate(180%) blur(20px); }
.card .owned-badge svg { width: 11px; height: 11px; flex: 0 0 auto; }

.a-loadwrap { text-align: center; margin-bottom: 80px; }
.a-load { display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--a-sep); background: none; color: var(--accent); font: inherit;
  font-size: 15px; font-weight: 590; padding: 12px 26px; border-radius: var(--radius-pill);
  min-height: 44px; cursor: pointer; }
.a-load:hover { background: var(--a-grey); }

body.ui-apple .empty { font-size: 17px; color: var(--a-text2); padding: 60px 0; }

/* ---- closing band ------------------------------------------------------- */
/* Sits outside .page so it spans the content column edge to edge, but still
   starts after the sidebar because that offset is body padding. */
.a-band { background: var(--a-grey); text-align: center; padding: 76px 26px 84px; margin-top: 20px; }
.a-band h2 { font-family: var(--font-display); font-size: 38px; line-height: 1.1;
  letter-spacing: -.03em; font-weight: 600; margin: 0; }
.a-band p { font-size: 19px; line-height: 1.5; color: var(--a-text2); max-width: 520px; margin: 15px auto 0; }
.a-band .a-cta { margin-top: 26px; }

/* ---- creator storefront ------------------------------------------------- */
/* No banner: the name carries the page, so a white-wordmark banner can no
   longer collide with the avatar sitting on top of it. */
.a-crhero { text-align: center; padding: 56px 0 44px; }
/* One class serves two elements: an <img> when the maker has a photo, a <div>
   holding their initial when they don't. They cannot share a `display`.
   `display:grid` on a replaced element makes the image its own grid item, and
   `place-items:center` then centres the intrinsic bitmap instead of letting
   object-fit cover the circle - a 1365x2048 portrait came out letterboxed with a
   grey band above it. Grid centring is for the fallback only. */
.a-crhero .av { width: 96px; height: 96px; border-radius: 50%; background: var(--fill-strong);
  margin: 0 auto 24px; overflow: hidden; }
.a-crhero img.av { display: block; object-fit: cover; }
.a-crhero div.av { display: grid; place-items: center; font-size: 36px; font-weight: 600;
  color: var(--a-text2); }
.a-crhero h1 { font-family: var(--font-display); font-size: 56px; line-height: 1.04;
  letter-spacing: -.035em; font-weight: 600; margin: 0; }
.a-crhero .a-tag { font-size: 24px; line-height: 1.3; letter-spacing: -.015em;
  color: var(--a-text2); max-width: 620px; margin: 16px auto 0; }
.a-crhero .a-meta { font-size: 15px; color: var(--a-text3); margin-top: 20px; }
.a-crhero .a-meta a { color: var(--accent); }
.a-crhero .a-crbtns { display: flex; align-items: center; justify-content: center;
  gap: 12px; margin-top: 26px; }
body.ui-apple .cr-follow { min-width: 0; padding: 12px 30px; border-radius: var(--radius-pill);
  font-size: 15px; font-weight: 500; min-height: 44px; }
body.ui-apple .cr-follow.on { background: var(--a-track); color: var(--text); }
body.ui-apple .cr-share { width: 44px; min-height: 44px; border-radius: var(--radius-pill);
  border-color: var(--a-sep); }

/* Tabs + sort keep their ids and handlers; only the paint changes. */
body.ui-apple .cr-tabrow { border-bottom: 1px solid var(--a-sep); margin: 0 0 26px; }
body.ui-apple .cr-tabs { gap: 30px; }
body.ui-apple .cr-tabs button { font-size: 16px; font-weight: 590; padding: 14px 2px; min-height: 44px; }
body.ui-apple .cr-sort { background: var(--a-track); border-radius: 11px; padding: 3px; }
body.ui-apple .cr-sort button { font-size: 14px; font-weight: 590; padding: 0 14px;
  min-height: 44px; border-radius: 9px; }
body.ui-apple .cr-sort button.on { background: #fff; color: var(--text); box-shadow: 0 1px 3px rgba(0, 0, 0, .12); }
body.ui-apple .cr-reelgrid { gap: 8px; margin-bottom: 60px; }
body.ui-apple .cr-reel { border-radius: 12px; }

/* ---- responsive --------------------------------------------------------- */
@media (max-width: 860px) {
  body.ui-apple .grid { grid-template-columns: repeat(2, 1fr); gap: 36px 20px; }
  .a-feat { grid-template-columns: 1fr; }
  .a-feat .a-feat-img { min-height: 260px; }
  .a-feat .a-feat-copy { padding: 30px 24px 36px; }
  .a-feat h2 { font-size: 27px; }
  .a-crhero h1 { font-size: 40px; }
  .a-crhero .a-tag { font-size: 19px; }
}
@media (max-width: 560px) {
  body.ui-apple .grid { grid-template-columns: 1fr; gap: 32px; }
  .a-head h1 { font-size: 32px; }
  .a-band h2 { font-size: 27px; }
  .a-crhero { padding: 34px 0 32px; }
  .a-crhero h1 { font-size: 34px; }
  /* Free wraps under the chip strip rather than squeezing it. */
  .a-filters { flex-wrap: wrap; }
  .a-vr { display: none; }
}
@media (prefers-reduced-motion: reduce) {
  body.ui-apple .card .cover, body.ui-apple .card:hover .cover { transition: none; transform: none; }
}

/* ===========================================================================
   SELL DASHBOARD (/sell)
   Scoped to `#dashSection`, which exists on sell.html only, plus one body class
   for the page tint. sell.html deliberately does NOT carry `ui-apple`: its
   profile-preview popup reuses .grid/.card/.cr-head, and the shop rules above
   would repaint that popup into a three-column storefront.
   =========================================================================== */

/* Grey backdrop with white content groups is the inverse of app.css, so it's
   toggled by show() and only while the dashboard is the visible section. The
   listing editor, the profile form and the apply flow stay white. */
body.dash-tint { background: var(--a-grey); }
body.dash-tint #dashSection { font-feature-settings: "tnum"; }

#dashSection .dsh-bar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 18px; }
#dashSection .dsh-who { font-size: 14px; font-weight: 600; color: var(--a-text2); }
#dashSection .dsh-who a { color: var(--accent); }
#dashSection .dsh-spacer { flex: 1; }
/* .a-btn is shared by the dashboard toolbar and the review queue. The class name
   is new, and apple.css only loads on pages that opt in, so it can't collide. */
.a-btn { display: inline-flex; align-items: center; justify-content: center;
  min-height: 44px; padding: 0 16px; border: 0; border-radius: var(--radius-pill);
  background: var(--a-track); color: var(--text); font: inherit; font-size: 14px;
  font-weight: 590; cursor: pointer; text-decoration: none; }
.a-btn:hover { background: rgba(120, 120, 128, .2); }
.a-btn.pri { background: var(--accent); color: #fff; }
.a-btn.pri:hover { background: var(--accent-strong); }
.a-btn.on { background: var(--text); color: #fff; }
.a-btn.danger { color: var(--red); }
.a-btn.danger:hover { background: rgba(207, 34, 46, .10); }
.a-btn:disabled { opacity: .5; cursor: default; }

/* A grouped inset list (.a-group/.a-row) lived here briefly, holding Creator
   tools, Review submissions and Sign out. All three left: the first duplicated
   the sidebar's Sell item, the second moved to /sell's admin toolbar, and the
   last is one rare action that reads better as a quiet line than as a card with
   a single thing in it. The component went with them rather than sitting here
   unused - a grouped list is easy to bring back when something needs one. */

#dashSection .dsh-h1 { font-family: var(--font-display); font-size: 32px; line-height: 1.1;
  letter-spacing: -.02em; font-weight: 700; margin: 0; }
#dashSection .dsh-sub { font-size: 15px; color: var(--a-text2); margin: 5px 0 20px; }

#dashSection .dsh-ctrls { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-bottom: 22px; }
#dashSection .dsh-seg { display: inline-flex; background: var(--a-track); border-radius: 11px;
  padding: 3px; gap: 2px; max-width: 100%; overflow-x: auto; scrollbar-width: none; }
#dashSection .dsh-seg::-webkit-scrollbar { display: none; }
#dashSection .dsh-seg button { border: 0; background: none; font: inherit; font-size: 14px;
  font-weight: 590; color: var(--text); padding: 0 14px; min-height: 44px; border-radius: 9px;
  cursor: pointer; white-space: nowrap; }
#dashSection .dsh-seg button.on { background: #fff; box-shadow: 0 1px 3px rgba(0, 0, 0, .12); }
#dashSection .dsh-seg button .n { color: var(--a-text3); font-weight: 510; margin-left: 5px; }
#dashSection .dsh-seg button.on .n { color: var(--a-text2); }
#dashSection .lc-viewseg { margin-left: auto; background: var(--a-track); border-radius: 11px; padding: 3px; }
/* app.css draws these at 32px. They sit in the same row as the 44px status tabs
   here, so they get the full target rather than staying under the minimum. */
#dashSection .lc-viewseg button { width: 44px; height: 44px; border-radius: 9px; }
#dashSection .lc-viewseg button.on { background: #fff; box-shadow: 0 1px 3px rgba(0, 0, 0, .12); }

/* Stats: one group, hairline dividers, big quiet numbers. */
#dashSection .dsh-stats { background: #fff; border-radius: 18px; box-shadow: var(--a-shadow);
  display: grid; grid-template-columns: repeat(4, 1fr); overflow: hidden; margin-bottom: 26px; }
#dashSection .dsh-stat { padding: 16px 18px; border-left: 1px solid var(--a-sep); }
#dashSection .dsh-stat:first-child { border-left: 0; }
#dashSection .dsh-stat .l { font-size: 14px; font-weight: 600; color: var(--a-text2); }
#dashSection .dsh-stat .v { font-size: 26px; font-weight: 600; letter-spacing: -.02em;
  margin-top: 6px; line-height: 1; }
/* "Not tracked" is a sentence, not a number, so it drops out of the number scale. */
#dashSection .dsh-stat .v.dim { font-size: 16px; font-weight: 590; color: var(--a-text3); letter-spacing: 0; }
#dashSection .dsh-stat .d { font-size: 14px; color: var(--a-text3); margin-top: 5px; }
@media (max-width: 640px) {
  #dashSection .dsh-stats { grid-template-columns: repeat(2, 1fr); }
  #dashSection .dsh-stat:nth-child(3) { border-left: 0; }
  #dashSection .dsh-stat:nth-child(n+3) { border-top: 1px solid var(--a-sep); }
}

/* Contextual action bar, appears with a selection. */
#dashSection .dsh-selbar { display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  background: #fff; border-radius: 14px; box-shadow: var(--a-shadow); padding: 6px 14px;
  margin-bottom: 12px; font-size: 14px; }
#dashSection .dsh-selbar .cnt { font-weight: 590; }
#dashSection .dsh-selbar button { border: 0; background: none; font: inherit; font-size: 14px;
  font-weight: 510; color: var(--accent); cursor: pointer; padding: 0 8px; min-height: 44px; }
#dashSection .dsh-selbar button.del { color: var(--red); }
#dashSection .dsh-selbar button:disabled { color: var(--a-text3); cursor: default; }

/* Grouped inset list. The separator inset to the title is the detail that makes
   this read as Apple rather than as a generic table. */
#dashSection .dsh-group { background: #fff; border-radius: 18px; box-shadow: var(--a-shadow); overflow: hidden; }
#dashSection .dsh-row { display: flex; align-items: center; gap: 13px; padding: 8px 16px 8px 6px;
  position: relative; cursor: pointer; min-height: 60px; }
#dashSection .dsh-row:hover { background: rgba(120, 120, 128, .06); }
/* 120px = padding-left 6 + checkbox target 44 + gap 13 + art 44 + gap 13, so the
   hairline starts exactly under the title. That inset is what makes this read as
   an Apple grouped list rather than a plain table. */
#dashSection .dsh-row + .dsh-row::before { content: ""; position: absolute; left: 120px; right: 0;
  top: 0; height: 1px; background: var(--a-sep); }
/* 44x44 hit area around a 21px control: the visual size Apple draws is not the
   size a finger needs. */
#dashSection .dsh-ckwrap { width: 44px; height: 44px; flex: 0 0 auto; display: grid; place-items: center; cursor: pointer; }
#dashSection .dsh-ck { appearance: none; -webkit-appearance: none; width: 21px; height: 21px; margin: 0;
  border-radius: 50%; border: 1.5px solid var(--a-text3); background: #fff; cursor: pointer; position: relative; }
#dashSection .dsh-ck:checked { background: var(--accent); border-color: var(--accent); }
#dashSection .dsh-ck:checked::after { content: ""; position: absolute; left: 6px; top: 4px;
  width: 5px; height: 9px; border: solid #fff; border-width: 0 2px 2px 0; transform: rotate(45deg); }
#dashSection .dsh-art { width: 44px; height: 44px; border-radius: 10px; object-fit: cover;
  flex: 0 0 auto; background: var(--a-track); display: block; overflow: hidden; }
#dashSection .dsh-txt { flex: 1; min-width: 0; }
#dashSection .dsh-nm { font-size: 15px; font-weight: 590; letter-spacing: -.01em;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#dashSection .dsh-mt { font-size: 14px; color: var(--a-text2); margin-top: 2px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#dashSection .dsh-right { display: flex; align-items: center; gap: 10px; flex: 0 0 auto; }
#dashSection .dsh-amt { font-size: 15px; font-weight: 590; }
#dashSection .dsh-amt.free { color: var(--a-text2); font-weight: 510; }
/* The dot only ever repeats what the meta line already says in words. */
#dashSection .dsh-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--sew); flex: 0 0 auto; }
#dashSection .dsh-dot.draft { background: var(--fill-strong); }
#dashSection .dsh-dot.pending_review { background: var(--orange); }
#dashSection .dsh-dot.rejected { background: var(--red); }
#dashSection .dsh-chev { color: var(--a-text3); display: block; }
#dashSection .dsh-chev svg { width: 9px; height: 15px; display: block; }
#dashSection .dsh-group .empty { padding: 44px 16px; }
@media (max-width: 520px) {
  #dashSection .dsh-mt .hidesm { display: none; }
  #dashSection .dsh-row + .dsh-row::before { left: 16px; }
}

/* Card view keeps its own per-row Edit / Publish / Delete buttons, repainted to
   match. Nothing is lost by preferring the grouped list. */
body.dash-tint #listings.lc-grid .lc { border: 0; border-radius: 18px; box-shadow: var(--a-shadow); }
body.dash-tint #listings.lc-grid .lc:hover { box-shadow: 0 2px 6px rgba(0, 0, 0, .08), 0 12px 30px rgba(0, 0, 0, .07); }
body.dash-tint .stat-card { background: #fff; border-radius: 18px; box-shadow: var(--a-shadow); }
body.dash-tint #dashSection .lst-card, body.dash-tint #dashSection .notice { border-radius: 14px; }

/* ===========================================================================
   REVIEW QUEUE (/review, admin only)
   Same grouped-on-tint language as the dashboard, scoped to `body.ui-review`.
   =========================================================================== */
body.ui-review { background: var(--a-grey); font-feature-settings: "tnum"; }
/* Full width: this is an internal queue, not a reading surface, and the 1040px
   page cap left most of the screen empty on a desktop monitor. */
body.ui-review .page { max-width: none; }
body.ui-review .backlink { font-size: 15px; min-height: 44px; }

.rv-head { display: flex; align-items: flex-start; gap: 12px; flex-wrap: wrap; margin: 6px 0 28px; }
.rv-head h1 { font-family: var(--font-display); font-size: 32px; line-height: 1.1;
  letter-spacing: -.02em; font-weight: 700; margin: 0; }
.rv-head .rv-sub { font-size: 15px; color: var(--a-text2); margin: 5px 0 0; }
.rv-head .a-btn { margin-left: auto; }

/* Sentence case, quiet. Never an ALL CAPS shout. */
.rv-seclab { font-size: 15px; font-weight: 600; color: var(--a-text2); margin: 0 4px 10px; }
.rv-seclab + * { margin-bottom: 30px; }

.rv-card { display: flex; align-items: flex-start; gap: 14px; background: #fff;
  border-radius: 18px; box-shadow: var(--a-shadow); padding: 16px; margin-bottom: 12px; }
.rv-art { width: 64px; height: 80px; border-radius: 10px; object-fit: cover;
  background: var(--a-track); flex: 0 0 auto; display: block; }
.rv-body { flex: 1; min-width: 0; }
.rv-title { font-size: 17px; font-weight: 600; letter-spacing: -.01em; display: flex;
  align-items: center; gap: 8px; flex-wrap: wrap; }
.rv-meta { font-size: 14px; color: var(--a-text2); margin-top: 3px; }
.rv-desc { font-size: 14px; color: var(--a-text2); line-height: 1.45; margin-top: 8px; }
/* Paired with the word, never colour alone. */
.rv-flag { font-size: 12px; font-weight: 600; padding: 3px 9px; border-radius: var(--radius-pill);
  background: rgba(207, 34, 46, .12); color: var(--red); }
.rv-acts { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 12px; }
body.ui-review .rej-note { border-radius: 12px; border-color: var(--a-sep); font-size: 15px;
  padding: 10px 12px; margin-top: 12px; }
body.ui-review .empty { background: #fff; border-radius: 18px; box-shadow: var(--a-shadow);
  padding: 44px 16px; font-size: 17px; color: var(--a-text2); }
@media (max-width: 520px) {
  .rv-card { flex-direction: column; }
  .rv-head h1 { font-size: 27px; }
}

/* ===========================================================================
   PATTERN PRODUCT PAGE (/pattern)
   The listing itself, which used to be a pop-up. Two columns: the gallery
   decides nothing on its own, so the whole decision - price, maker, what you
   get - sits beside it rather than under it. Everything below the hero is a
   plain vertical stack of sections, and a section with no data is never
   rendered at all (most listings have no reviews, tutorial or Makes yet).
   =========================================================================== */

/* Sub-nav: hidden until the hero's buy button leaves the top of the viewport,
   then it carries the title and the same action. Offset by the sidebar's 220px
   at the width where that sidebar appears (app.css:301). */
.pd-nav { position: fixed; top: 0; left: 0; right: 0; z-index: 60;
  background: rgba(255, 255, 255, .82); border-bottom: 1px solid var(--a-sep);
  -webkit-backdrop-filter: saturate(180%) blur(20px); backdrop-filter: saturate(180%) blur(20px);
  transform: translateY(-100%); opacity: 0; transition: transform .28s var(--a-ease), opacity .28s var(--a-ease);
  pointer-events: none; }
.pd-nav.show { transform: none; opacity: 1; pointer-events: auto; }
.pd-navinner { display: flex; align-items: center; gap: 16px; max-width: var(--maxw);
  margin: 0 auto; padding: 8px 20px; min-height: 56px; }
.pd-navtitle { flex: 1; min-width: 0; font-size: 17px; font-weight: 600; letter-spacing: -.015em;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pd-navcta { flex: 0 0 auto; border: 0; background: var(--accent); color: #fff; font: inherit;
  font-size: 15px; font-weight: 500; padding: 0 22px; min-height: 44px;
  border-radius: var(--radius-pill); cursor: pointer; }
.pd-navcta:hover { background: var(--accent-strong); }
.pd-navcta.is-owned { background: var(--a-track); color: var(--text); }
.pd-navcta:disabled { background: var(--a-track); color: var(--a-text3); cursor: default; }
@media (min-width: 760px) { .pd-nav { left: 220px; } }
@media (prefers-reduced-motion: reduce) { .pd-nav { transition: none; } }

/* Back to where you were browsing. Present in the skeleton too, so the hero does
   not drop when the listing lands, and present on the error states, which is
   where a dead end would otherwise leave you with only the browser chrome. */
.pd-back { display: inline-flex; align-items: center; gap: 7px; min-height: 44px;
  margin-top: 4px; font-size: 15px; font-weight: 590; color: var(--accent); text-decoration: none; }
.pd-back svg { width: 9px; height: 15px; }
.pd-back:hover { opacity: .7; }

/* ---- hero --------------------------------------------------------------- */
.pd-hero { display: grid; grid-template-columns: 1.02fr .98fr; gap: 52px;
  align-items: start; padding: 12px 0 12px; }

.pd-gal { position: relative; aspect-ratio: 1; border-radius: 22px; overflow: hidden;
  background: var(--a-grey); }
.pd-track { display: flex; height: 100%; overflow-x: auto; scroll-snap-type: x mandatory;
  scrollbar-width: none; -ms-overflow-style: none; }
.pd-track::-webkit-scrollbar { display: none; }
.pd-track.pd-swipeable { cursor: grab; }
.pd-track.pd-dragging { cursor: grabbing; scroll-snap-type: none; scroll-behavior: auto; }
.pd-slide { position: relative; flex: 0 0 100%; width: 100%; height: 100%; scroll-snap-align: center; }
/* Photos fill the square. A 9:16 reel would lose its subject to that crop, so
   video slides (and the poster held over them) letterbox instead. */
.pd-slide img.pd-slide-media { width: 100%; height: 100%; object-fit: cover; display: block; }
.pd-slide-video { background: #000; }
.pd-slide-video video.pd-slide-media, .pd-slide-poster { position: absolute; inset: 0;
  width: 100%; height: 100%; object-fit: contain; display: block; }
.pd-slide-poster { z-index: 1; transition: opacity .25s ease; }
.pd-slide-video.is-playing .pd-slide-poster { opacity: 0; }
.pd-slide-collage { background: #fff; }

.pd-dots { position: absolute; left: 0; right: 0; bottom: 14px; display: flex;
  justify-content: center; gap: 7px; }
.pd-dots span { width: 7px; height: 7px; border-radius: 50%; background: rgba(0, 0, 0, .22); cursor: pointer; }
.pd-dots span.on { background: rgba(0, 0, 0, .62); }
.pd-arrow { position: absolute; top: 50%; transform: translateY(-50%);
  width: 44px; height: 44px; border: 0; border-radius: 50%; display: grid; place-items: center;
  background: rgba(255, 255, 255, .78); color: var(--text); cursor: pointer;
  -webkit-backdrop-filter: saturate(180%) blur(20px); backdrop-filter: saturate(180%) blur(20px);
  box-shadow: 0 1px 4px rgba(0, 0, 0, .14); }
.pd-prev { left: 12px; } .pd-next { right: 12px; }
.pd-prev svg, .pd-next svg { width: 17px; height: 17px; }
/* On touch there is nothing to hover and the swipe is the affordance. */
@media (hover: none) { .pd-prev, .pd-next { display: none !important; } }

.pd-kick { font-size: 14px; font-weight: 600; color: var(--accent); }
.pd-title { font-family: var(--font-display); font-size: 40px; line-height: 1.06;
  letter-spacing: -.03em; font-weight: 600; margin: 8px 0 0; }
.pd-sub { font-size: 19px; line-height: 1.4; color: var(--a-text2); margin: 12px 0 0; }

.pd-maker { display: flex; align-items: center; gap: 12px; margin-top: 22px; padding: 12px 0;
  min-height: 60px; border-top: 1px solid var(--a-sep); border-bottom: 1px solid var(--a-sep);
  text-decoration: none; color: inherit; }
.pd-cav { width: 44px; height: 44px; border-radius: 50%; background: var(--fill-strong);
  object-fit: cover; flex: 0 0 auto; display: block; }
.pd-mtxt { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.pd-mname { font-size: 16px; font-weight: 590; }
.pd-mshop { font-size: 14px; color: var(--a-text2); }
.pd-chev { color: var(--a-text3); font-size: 20px; }

.pd-pricerow { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; margin-top: 22px; }
.pd-price { font-size: 28px; font-weight: 600; letter-spacing: -.02em; }
.pd-was { font-size: 15px; color: var(--a-text3); text-decoration: line-through; }

.pd-ctarow { display: flex; align-items: center; gap: 10px; margin-top: 16px; }
.pd-cta { flex: 1; min-height: 52px; border: 0; border-radius: var(--radius-pill);
  background: var(--accent); color: #fff; font: inherit; font-size: 17px; font-weight: 500;
  letter-spacing: -.01em; cursor: pointer; }
.pd-cta:hover { background: var(--accent-strong); }
/* Owned and unreleased are quiet fills, never a second competing blue. The label
   always says which one it is, so the colour is never carrying the meaning. */
.pd-cta.is-owned { background: var(--a-track); color: var(--text); }
.pd-cta.is-off, .pd-cta:disabled { background: var(--a-track); color: var(--a-text3); cursor: default; }
.pd-savebtn { flex: 0 0 auto; width: 52px; height: 52px; border: 1px solid var(--a-sep);
  border-radius: 50%; background: none; color: var(--text); display: grid; place-items: center; cursor: pointer; }
.pd-savebtn svg { width: 19px; height: 19px; }
.pd-savebtn.on { background: var(--a-track); color: var(--accent); }
.pd-savebtn.on svg path { fill: currentColor; }

.pd-hint { font-size: 14px; color: var(--a-text3); margin: 12px 0 0; }
body.ui-apple .pd-page .notice.err { font-size: 14px; margin-top: 12px; }

/* Facts, one per line, each read off the document. */
.pd-glance { list-style: none; margin: 22px 0 0; padding: 0; display: flex;
  flex-direction: column; gap: 9px; }
.pd-glance li { position: relative; padding-left: 20px; font-size: 15px; color: var(--a-text2); }
.pd-glance li::before { content: ""; position: absolute; left: 4px; top: 8px; width: 5px; height: 5px;
  border-radius: 50%; background: var(--a-text3); }

.pd-extra { margin-top: 20px; }
.pd-linklike { border: 0; background: none; font: inherit; font-size: 15px; font-weight: 590;
  color: var(--accent); cursor: pointer; padding: 0; min-height: 44px; text-align: left; }
.pd-form { display: flex; flex-direction: column; gap: 10px; margin-top: 8px; max-width: 340px; }
.pd-form .input { border-radius: 12px; border-color: var(--a-sep); font-size: 16px; min-height: 44px; }
.pd-check { display: flex; gap: 8px; align-items: center; font-size: 14px; color: var(--a-text2); }
.pd-formstatus { font-size: 14px; color: var(--a-text2); }

/* ---- sections ----------------------------------------------------------- */
.pd-sec { border-top: 1px solid var(--a-sep); padding: 44px 0 0; margin-top: 44px; }
.pd-sec > h2 { font-family: var(--font-display); font-size: 26px; letter-spacing: -.025em;
  font-weight: 600; margin: 0; }
.pd-secsub { font-size: 15px; color: var(--a-text2); margin: 8px 0 0; }
.pd-sec > h2 + .pd-secsub + *, .pd-sec > h2 + * { margin-top: 24px; }
.pd-sub3 { font-size: 17px; font-weight: 600; letter-spacing: -.01em; margin: 28px 0 10px; }
.pd-body { font-size: 17px; line-height: 1.55; color: var(--text); margin: 0; white-space: pre-wrap; }
.pd-list { margin: 0; padding-left: 20px; font-size: 16px; line-height: 1.6; color: var(--a-text2); }
.pd-none { font-size: 15px; color: var(--a-text2); margin: 0; }

/* What you get: an icon, a name, and one sentence saying why it matters. */
.pd-files { display: grid; grid-template-columns: repeat(2, 1fr); gap: 14px; }
.pd-file { display: flex; align-items: flex-start; gap: 13px; background: var(--a-grey);
  border-radius: 16px; padding: 17px 18px; }
.pd-fic { flex: 0 0 auto; width: 26px; height: 26px; color: var(--accent); }
.pd-fic svg { width: 26px; height: 26px; display: block; }
.pd-ftxt { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.pd-fname { font-size: 16px; font-weight: 590; letter-spacing: -.01em; }
.pd-fsub { font-size: 14px; line-height: 1.4; color: var(--a-text2); }

.pd-chips { display: flex; flex-wrap: wrap; gap: 8px; }
.pd-chip { background: var(--a-track); border-radius: var(--radius-pill); padding: 9px 16px;
  font-size: 15px; font-weight: 510; }

.pd-pieces { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; }
.pd-piece { display: flex; flex-direction: column; gap: 4px; background: var(--a-grey);
  border-radius: 14px; padding: 14px 16px; }
.pd-pname { font-size: 15px; font-weight: 590; }
.pd-pcut { font-size: 14px; color: var(--a-text2); line-height: 1.4; }

.pd-times { display: flex; gap: 12px; flex-wrap: wrap; margin-top: 24px; }
.pd-time { background: var(--a-grey); border-radius: 14px; padding: 14px 20px; min-width: 130px; }
.pd-tl { display: block; font-size: 14px; color: var(--a-text2); }
.pd-tv { display: block; font-size: 19px; font-weight: 600; letter-spacing: -.015em; margin-top: 3px; }

.pd-tags { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 26px; }
.pd-tag { background: var(--a-track); border-radius: var(--radius-pill); padding: 8px 14px;
  font-size: 14px; color: var(--a-text2); }

.pd-comments { margin-top: 26px; }

/* ---- learn -------------------------------------------------------------- */
.pd-tut { display: inline-flex; align-items: center; gap: 10px; background: var(--a-grey);
  border-radius: var(--radius-pill); padding: 0 22px; min-height: 52px; font-size: 16px;
  font-weight: 590; color: var(--text); text-decoration: none; }
.pd-tut:hover { background: var(--a-track); }
.pd-play { color: var(--accent); font-size: 14px; }
.pd-clips { display: flex; gap: 12px; overflow-x: auto; padding: 4px 0; margin-top: 20px;
  scrollbar-width: none; }
.pd-clips::-webkit-scrollbar { display: none; }
.pd-clip { position: relative; flex: 0 0 auto; width: 108px; height: 148px; border: 0;
  border-radius: 14px; background: var(--fill-strong) center/cover no-repeat; cursor: pointer; }
/* nowrap: the tile is 108px, so any two-word label broke across two lines and the
   pill stopped reading as a pill. */
.pd-cliplab { position: absolute; left: 8px; bottom: 8px; background: rgba(0, 0, 0, .52);
  color: #fff; font-size: 12px; font-weight: 590; padding: 3px 9px; border-radius: var(--radius-pill);
  white-space: nowrap; -webkit-backdrop-filter: blur(12px); backdrop-filter: blur(12px); }
/* A poster frame with no play mark is indistinguishable from a photo, and this
   tile is the only thing on the listing that opens a video. */
.pd-clipplay { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  width: 38px; height: 38px; border-radius: 50%; background: rgba(0, 0, 0, .42);
  display: grid; place-items: center; color: #fff;
  -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
  transition: background .2s var(--a-ease); }
.pd-clipplay svg { width: 20px; height: 20px; margin-left: 2px; }
.pd-clip:hover .pd-clipplay { background: rgba(0, 0, 0, .62); }
@media (prefers-reduced-motion: reduce) { .pd-clipplay { transition: none; } }

/* The listing's clip tiles are LINKS into /feed?pattern=<id>&i=<n>, which is the
   real Explore player — rail, comments, follow, mute and all. A second player
   lived here briefly; reusing the one we already have beat maintaining two. */
.pd-clip { text-decoration: none; }

/* ---- reviews ------------------------------------------------------------ */
.pd-revhead { display: flex; align-items: center; gap: 16px; }
.pd-revavg { font-family: var(--font-display); font-size: 44px; font-weight: 600; letter-spacing: -.03em; line-height: 1; }
.pd-revmeta { display: flex; flex-direction: column; gap: 3px; }
.pd-stars { color: #f5a623; letter-spacing: 2px; font-size: 15px; }
.pd-revcnt { font-size: 14px; color: var(--a-text2); }
.pd-hist { display: flex; flex-direction: column; gap: 6px; margin-top: 20px; max-width: 320px; }
.pd-histrow { display: flex; align-items: center; gap: 10px; font-size: 14px; color: var(--a-text2); }
.pd-hl { width: 10px; flex: 0 0 auto; }
.pd-hbar { flex: 1; height: 8px; border-radius: 4px; background: var(--a-track); overflow: hidden; }
.pd-hfill { display: block; height: 100%; border-radius: 4px; background: #f5a623; }
.pd-hn { width: 24px; flex: 0 0 auto; text-align: right; }
.pd-revlist { display: flex; flex-direction: column; gap: 12px; margin-top: 26px; }
.pd-rev { background: var(--a-grey); border-radius: 16px; padding: 16px 18px; }
.pd-revtop { display: flex; align-items: center; gap: 10px; }
.pd-revwho { font-size: 15px; font-weight: 590; }
.pd-revtext { font-size: 15px; line-height: 1.5; color: var(--a-text2); margin: 8px 0 0; }
.pd-rwrite { background: var(--a-grey); border-radius: 16px; padding: 18px; margin-top: 20px; }
.pd-rwlabel { font-size: 15px; font-weight: 590; }
.pd-rstars { display: flex; gap: 2px; margin: 8px 0; }
.pd-star { border: 0; background: none; color: var(--fill-strong); font-size: 26px; line-height: 1;
  cursor: pointer; padding: 0 4px; min-height: 44px; }
.pd-star.on { color: #f5a623; }
.pd-rinput { width: 100%; border: 1px solid var(--a-sep); border-radius: 12px; font: inherit;
  font-size: 15px; padding: 12px; resize: vertical; background: #fff; }
.pd-rwrite .a-btn { margin-top: 10px; }

/* ---- maker + more ------------------------------------------------------- */
.pd-mkcard { display: flex; align-items: center; gap: 16px; background: var(--a-grey);
  border-radius: 18px; padding: 20px; text-decoration: none; color: inherit; }
.pd-mkav { width: 64px; height: 64px; border-radius: 50%; background: var(--fill-strong);
  flex: 0 0 auto; object-fit: cover; }
/* One class, two elements: an <img> when the maker has a photo, a <div> holding
   their initial when they don't. They cannot share a `display` - grid centring on
   a replaced element centres the intrinsic bitmap instead of letting object-fit
   cover, which letterboxes tall portraits. */
div.pd-mkav { display: grid; place-items: center; font-size: 24px; font-weight: 600; color: var(--a-text2); }
img.pd-mkav { display: block; }
.pd-mktxt { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.pd-mkname { font-size: 19px; font-weight: 600; letter-spacing: -.015em; }
.pd-mkbio { font-size: 15px; color: var(--a-text2); }
.pd-mkgo { font-size: 15px; color: var(--accent); margin-top: 4px; }
body.ui-apple .grid.pd-more { grid-template-columns: repeat(4, 1fr); gap: 34px 22px; margin-bottom: 0; }

/* ---- first paint -------------------------------------------------------- */
/* MEASURED against the loaded page, not guessed. Two columns (1440px): the
   square gallery is the floor at 540 and the buy column is shorter, so listings
   land 529-621 depending on how long the subtitle runs. Stacked (900px): the
   gallery is 644 wide and the buy column falls below it, so 1213-1267. The
   reserve is the low end of each band: over-reserving leaves a visible gap
   under a short listing, which is the same jump in the other direction. */
.pd-hero.is-sk { min-height: 540px; pointer-events: none; }
.pd-skcover { position: absolute; inset: 0; background: var(--fill); }
.pd-hero.is-sk .sk-line { margin: 0; }

/* ---- responsive --------------------------------------------------------- */
@media (max-width: 980px) {
  .pd-hero { grid-template-columns: 1fr; gap: 28px; }
  .pd-files { grid-template-columns: 1fr; }
  .pd-pieces { grid-template-columns: repeat(2, 1fr); }
  body.ui-apple .grid.pd-more { grid-template-columns: repeat(2, 1fr); }
  .pd-hero.is-sk { min-height: 1213px; }
}
@media (max-width: 560px) {
  .pd-title { font-size: 30px; }
  .pd-sub { font-size: 17px; }
  .pd-revavg { font-size: 34px; }
  .pd-pieces { grid-template-columns: 1fr; }
  body.ui-apple .grid.pd-more { grid-template-columns: 1fr; }
  .pd-sec { padding-top: 34px; margin-top: 34px; }
  .pd-hero.is-sk { min-height: 940px; }
}

/* ===========================================================================
   LISTING SHEET (.pm-sheet)
   Now only the seller's "Preview listing" on /sell. It was the listing UI on
   five pages until /pattern took that job; the styles stay because the preview
   still renders through the same markup.

   The CTA colours are deliberately NOT touched. app.css mirrors the RN pattern
   screen on purpose (blue "Make now", green "Buy now", yellow "Save now"), and
   repainting the buy button blue here would split web from the app. Only the
   shape and weight change.
   =========================================================================== */
.modal.pm-sheet { border-radius: 22px 22px 0 0; }
.modal-scrim.pm-side .modal.pm-sheet { border-radius: 0; }

/* Material chrome over the artwork, the way Apple floats controls on an image. */
.pm-sheet .pm-x, .pm-sheet .pm-save, .pm-sheet .pm-nav {
  background: rgba(255, 255, 255, .72); color: var(--text);
  -webkit-backdrop-filter: saturate(180%) blur(20px); backdrop-filter: saturate(180%) blur(20px);
  box-shadow: 0 1px 4px rgba(0, 0, 0, .12); }
.pm-sheet .pm-x { width: 36px; height: 36px; top: 12px; right: 14px; }
.pm-sheet .pm-save { width: 40px; height: 40px; }
.pm-sheet .pm-arpill { background: rgba(0, 0, 0, .48); font-size: 12px; font-weight: 590;
  letter-spacing: 0; -webkit-backdrop-filter: blur(12px); backdrop-filter: blur(12px); }
.pm-sheet .pm-subtitle { font-size: 15px; font-weight: 590; }

/* Covers fill the square instead of letterboxing. app.css uses object-fit:contain,
   which framed a 1024x853 cover inside a 440px square with grey bands above and
   below it. Images only: a 9:16 reel cropped to a square would lose its subject,
   so video slides and their posters keep contain. */
.pm-sheet img.pm-slide-media { object-fit: cover; }
/* bundleCollage returns an absolutely-positioned .bc-fill, so its slide has to be
   a positioned box or the tiles escape the carousel. */
.pm-slide-collage { position: relative; flex: 0 0 100%; width: 100%; height: 100%;
  scroll-snap-align: center; background: #fff; }

/* Type: one large title, then quiet grey section labels. app.css sets section
   headings at 18/700, which competes with the title instead of organising it. */
.pm-sheet .pm-title { font-family: var(--font-display); font-size: 28px; line-height: 1.12;
  letter-spacing: -.025em; font-weight: 600; margin: 20px 0 0; }
.pm-sheet .pm-sec { font-size: 15px; font-weight: 600; color: var(--a-text2);
  letter-spacing: 0; text-transform: none; margin: 28px 0 8px; }
.pm-sheet .pm-desc { font-size: 17px; line-height: 1.5; color: var(--text); }
.pm-sheet .pm-hint { font-size: 14px; color: var(--a-text3); }
.pm-sheet .pm-none, .pm-sheet .pm-makesub { font-size: 15px; color: var(--a-text2); }

/* Maker row reads as a grouped list row: hairline above and below, real chevron. */
.pm-sheet .pm-creator { margin-top: 18px; padding: 12px 0; min-height: 60px;
  border-top: 1px solid var(--a-sep); border-bottom: 1px solid var(--a-sep); }
.pm-sheet .pm-cav { width: 44px; height: 44px; }
.pm-sheet .pm-cname { font-size: 16px; font-weight: 590; }
.pm-sheet .pm-cshop { font-size: 14px; color: var(--a-text2); }
.pm-sheet .pm-chev { color: var(--a-text3); font-size: 20px; }

/* Grouped fills instead of bordered boxes. */
.pm-sheet .pm-tag { background: var(--a-track); color: var(--text); font-size: 14px;
  padding: 8px 14px; }
.pm-sheet .pm-mi { font-size: 14px; color: var(--a-text2); }
.pm-sheet .pm-mcard, .pm-sheet .pm-review, .pm-sheet .pm-rwrite {
  background: var(--a-grey); border-radius: 14px; padding: 16px; }
.pm-sheet .pm-mlabel { font-size: 14px; color: var(--a-text2); margin-bottom: 4px; }
.pm-sheet .pm-mvalue { font-size: 16px; font-weight: 590; }
.pm-sheet .pm-rauthor, .pm-sheet .pm-rwlabel { font-size: 15px; font-weight: 590; }
.pm-sheet .pm-rtext { font-size: 15px; color: var(--a-text2); }
.pm-sheet .pm-rinput { border-radius: 12px; border-color: var(--a-sep); font-size: 15px; padding: 12px; }
.pm-sheet .pm-make { border-radius: 14px; width: 84px; height: 84px; }

/* Shape only. The colour stays whatever app.css assigned for this CTA state. */
.pm-sheet .pm-cta { height: 52px; border-radius: var(--radius-pill); font-size: 17px;
  font-weight: 500; letter-spacing: -.01em; }
.pm-sheet .pm-foot { -webkit-backdrop-filter: saturate(180%) blur(20px);
  backdrop-filter: saturate(180%) blur(20px); }

/* ===========================================================================
   BUYER FILE LIST (.filelist / .fmtgroup)
   The downloads for a pattern you own. Component-scoped, not page-scoped,
   because it is mounted twice and the two must never disagree about what you
   bought: the /account sheet that the purchase email links to, and the top of
   the pattern page once you own the listing. Markup comes from
   assets/pattern-files.js; these styles moved out of account.html's inline
   <style> when the pattern page grew a second mount.
   =========================================================================== */
.filebox { background:var(--fill); border:1px dashed var(--line); border-radius:var(--radius-md);
  padding:14px; color:var(--muted); font-size:14px; text-align:center; }
/* Downloads list. A quiet divided list, not a stack of cards: the file name is
   the content and "Download" is the one action, so nothing competes. Storage
   files are <button>s (the URL is minted on click, never rendered into the
   DOM); an external instructions link is a plain <a>. */
.dl-head { display:flex; align-items:baseline; justify-content:space-between; gap:8px; margin:2px 0 2px; }
.dl-title { font-family:var(--font-display); font-size:20px; font-weight:600; letter-spacing:-.015em; margin:0; }
.dl-count { font-size:14px; color:var(--a-text3); }
.filelist { border-top:1px solid var(--a-sep); margin-bottom:18px; }
.filerow { display:flex; align-items:center; gap:12px; width:100%; min-height:60px; padding:10px 2px;
  text-align:left; background:none; border:0; border-bottom:1px solid var(--a-sep); color:var(--text);
  font-family:inherit; font-size:15px; cursor:pointer; text-decoration:none; }
.filerow:hover .dl-act { background:var(--fill-strong); }
.filerow[disabled] { opacity:.55; cursor:default; }
.dl-ico { flex:0 0 auto; width:40px; height:40px; border-radius:10px; background:var(--a-track);
  display:flex; align-items:center; justify-content:center; color:var(--a-text2); }
.dl-ico svg { width:19px; height:19px; }
/* A column: the name sits ABOVE its type/size. .dl-meta must be its own flex
   container, since the children are spans and would otherwise flow inline (and
   margin-top on an inline element is inert). min-width:0 is what lets the name
   ellipsize instead of forcing the row wider than the sheet. */
/* text-align too, not just align-items: the sheet centers its own text, so a
   wrapped sub-line came back centered under a left-aligned name. */
.dl-meta { flex:1; min-width:0; display:flex; flex-direction:column; align-items:flex-start;
  text-align:left; }
.dl-name { font-weight:590; max-width:100%; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.dl-sub { font-size:14px; color:var(--a-text2); margin-top:2px; }
/* The action reads as a button but is inside the row, so the whole row stays
   clickable while the affordance is unambiguous. */
.dl-act { flex:0 0 auto; display:inline-flex; align-items:center; min-height:44px; padding:0 18px;
  font-size:14px; font-weight:590; color:var(--accent); border:1px solid var(--a-sep);
  border-radius:999px; white-space:nowrap; }
button.dl-act[disabled] { opacity:.45; cursor:default; }
/* The master and the print-at-home file are one pattern in two shapes, so they
   are one item with a format choice rather than two rows that differ by a
   suffix. The heading is not itself clickable: there is no "just give me the
   pattern" file to hand over, and a header that looked tappable would promise
   one. The options carry the whole affordance, indented under it. */
.fmtgroup { border-bottom:1px solid var(--a-sep); padding:10px 2px 12px; }
.fmthead { display:flex; align-items:center; gap:12px; min-height:60px; }
.fmtopts { display:flex; flex-direction:column; gap:8px; margin:2px 0 0 52px; }
/* Indented to the width of .dl-ico + its gap, so the options sit under the
   pattern name rather than under its icon. Collapses on a phone, where 52px of
   dead gutter costs more than the alignment buys. */
@media (max-width:520px) { .fmtopts { margin-left:0; } }
.fmtopt { display:flex; align-items:center; gap:12px; width:100%; min-height:56px;
  padding:8px 12px; text-align:left; background:var(--fill); border:1px solid var(--a-sep);
  border-radius:var(--radius-md); color:var(--text); font-family:inherit; font-size:15px;
  cursor:pointer; transition:background .12s ease, border-color .12s ease; }
.fmtopt:hover { border-color:var(--muted); }
.fmtopt:hover .dl-act { background:var(--fill-strong); }
.fmtopt[disabled] { opacity:.55; cursor:default; }
/* The description wraps on a narrow screen, so the name must not be locked to
   one ellipsized line the way .dl-name is in a flat row. */
.fmtopt .dl-name { white-space:normal; overflow:visible; }
.fmtopt .dl-sub { white-space:normal; }

/* ===========================================================================
   POST-CHECKOUT (/purchase/success)
   Unscoped, like .a-btn above: these classes exist on exactly one page and
   scoping them to a body class would buy nothing but a longer selector. The
   page carries `ui-apple` for the shared shell (.page, .a-sec, .a-cta) and
   mounts the .filelist component above unchanged.
   Narrower than the storefront on purpose. This is a receipt with two things
   to do on it, not a catalogue, and a 1040px column of confirmation reads as
   an empty page.
   =========================================================================== */
.ps-page { max-width:660px; padding-top:8px; }
.ps-head { text-align:center; padding:26px 0 8px; }
/* The mark is the state. A spinner while the webhook lands, a tick once the
   entitlement is really there - never a tick over an unverified purchase. */
.ps-mark { width:56px; height:56px; border-radius:50%; margin:0 auto 18px; display:flex;
  align-items:center; justify-content:center; background:var(--a-track); color:var(--a-text2); }
.ps-mark.ok { background:rgba(52,199,89,.14); color:#1a7f37; }
.ps-mark svg { width:28px; height:28px; }
.ps-spin { width:22px; height:22px; border-radius:50%; border:2.5px solid rgba(120,120,128,.25);
  border-top-color:var(--a-text2); animation:ps-spin .8s linear infinite; }
@keyframes ps-spin { to { transform:rotate(360deg) } }
.ps-head h1 { font-family:var(--font-display); font-size:34px; line-height:1.1;
  letter-spacing:-.03em; font-weight:600; margin:0; }
.ps-head .ps-sub { font-size:17px; color:var(--a-text2); margin:10px 0 0; line-height:1.45; }

/* What they bought, stated once. Not a card: a bordered box here competes with
   the "Start here" card below it, which is the thing to press. */
.ps-item { display:flex; align-items:center; gap:16px; padding:20px 0 24px;
  border-bottom:1px solid var(--a-sep); margin-bottom:28px; }
.ps-cover { width:64px; height:64px; border-radius:12px; object-fit:cover;
  background:var(--a-grey); flex:0 0 auto; }
.ps-itxt { min-width:0; }
.ps-ititle { font-size:17px; font-weight:590; letter-spacing:-.01em; }
.ps-iby { font-size:15px; color:var(--a-text2); margin-top:2px; }

/* The one thing to do first. Filled, unmissable, and absent entirely when the
   listing ships no instructions booklet - an empty "Start here" is worse than
   no "Start here".
   Shared, like .filerow and .dl-act below it: the post-checkout page and the
   pattern page's own Files section both mount this card, and a second copy of
   these rules under a pd- prefix is how the two would drift apart. */
.ps-start { margin:0 0 38px; }
/* 14px, matching .a-feat .a-kick. The 13px an eyebrow wants is below the type
   floor the rest of these pages hold to. */
.ps-startk { font-size:14px; font-weight:590; letter-spacing:.02em; text-transform:uppercase;
  color:var(--a-text3); margin:0 0 10px; }
.ps-startbtn { display:flex; align-items:center; gap:14px; width:100%; min-height:72px;
  padding:14px 16px; text-align:left; background:var(--a-grey); border:0;
  border-radius:var(--radius-lg); font-family:inherit; cursor:pointer; }
.ps-startbtn:hover { background:var(--fill-strong); }
.ps-startbtn[disabled] { opacity:.55; cursor:default; }
.ps-starttxt { flex:1; min-width:0; display:flex; flex-direction:column; align-items:flex-start; }
.ps-startname { font-size:17px; font-weight:590; letter-spacing:-.01em; color:var(--text); }
.ps-startsub { font-size:14px; color:var(--a-text2); margin-top:3px; }
/* Overrides the bordered pill .dl-act draws in the list: in the list "Download"
   is one of several equal actions, here it is THE action. */
.ps-startbtn .dl-act { background:var(--accent); border-color:var(--accent); color:#fff; }
.ps-startbtn:hover .dl-act { background:var(--accent-strong); }

.ps-sec { margin-bottom:6px; }
.ps-secsub { font-size:15px; color:var(--a-text2); line-height:1.5; margin:0 0 18px; }

/* AR handoff. The QR is a desktop affordance - on a phone the buyer is already
   holding the device, so they get the deep link instead. Same split as the
   pattern page's app modal. */
.ps-ar { text-align:center; padding:8px 0 44px; }
.ps-qr { display:none; width:192px; margin:0 auto 14px; background:#fff; padding:12px;
  border:1px solid var(--a-sep); border-radius:var(--radius-lg); }
.ps-qr canvas, .ps-qr img { display:block; width:100%!important; height:100%!important; }
.ps-arnote { font-size:14px; color:var(--a-text3); margin:0 0 16px; line-height:1.5; }
.ps-open { min-width:220px; }
/* "Scan with your phone" belongs to the QR and has to disappear with it. Left
   standing on a phone it instructed the buyer to scan a code that wasn't
   there. */
.ps-deskonly { display:none; }
@media (min-width:760px) {
  .ps-qr, .ps-deskonly { display:block; }
  /* The QR is the desktop path; a button that opens an iPhone app from a laptop
     is a dead end. */
  .ps-open { display:none; }
}
/* This page has its own, better app handoff a few lines further down (a QR on
   desktop, a deep link on a phone), so the global floating "Get app" pill is
   both redundant and, once the page scrolls, sitting on top of the primary
   Open button. */
.ps-page ~ .getapp { display:none; }

.ps-slow { display:flex; flex-direction:column; align-items:center; gap:16px; padding:26px 0 44px; }
.ps-slow .a-cta { min-width:220px; }
.ps-foot { padding:26px 0 40px; border-top:1px solid var(--a-sep); }
.ps-links { display:flex; flex-wrap:wrap; justify-content:center; gap:10px 28px; }
.ps-link { display:inline-flex; align-items:center; min-height:44px; font-size:15px;
  font-weight:590; color:var(--accent); }
/* The order number, directly under the headline. Quiet, but present in every
   state including the ones that went wrong - a buyer whose files have not
   arrived needs this more than a buyer whose have. Monospace, because it gets
   pasted into an email; the Copy button is the convenience, not the only way. */
.ps-order { display:flex; flex-wrap:wrap; align-items:center; justify-content:center;
  gap:8px 12px; margin:14px 0 4px; }
.ps-ordl { font-size:14px; color:var(--a-text3); }
.ps-ordid { font-family:ui-monospace, SFMono-Regular, Menlo, monospace; font-size:14px;
  color:var(--a-text2); background:var(--a-track); border-radius:8px; padding:6px 10px;
  max-width:100%; overflow-wrap:anywhere; }
.ps-ordcopy { display:inline-flex; align-items:center; min-height:44px; padding:0 14px;
  border:0; background:none; font-family:inherit; font-size:14px; font-weight:590;
  color:var(--accent); cursor:pointer; }
@media (max-width:560px) {
  .ps-head h1 { font-size:29px; }
  .ps-head { padding:18px 0 4px; }
}
