/* md.css — /p/md (Merch Vault). Design source: MEMBERSHIP_REQUIREMENTS
   §8 (Module 3 spec) / §15.9 (Figma page 04, 1200px desktop), plus the
   2026-07-06 UI/UX revision pass (direct qty on card, conditions popup,
   planner redesign, category filter). */

*, *::before, *::after { box-sizing: border-box; }
body { background: var(--space-gradient), var(--bg-primary); background-repeat: no-repeat; background-attachment: fixed; color: var(--text-primary); font-family: var(--font-thai); margin: 0; }

/* flex column + min-height (2026-07-10) — lets the footer pin to the
   bottom of the viewport via margin-top:auto below when the catalog is
   short (2026-08-16: the catalog grows over time as products are added, so
   .md-shell itself stays top-pinned rather than centered — only the footer
   chases the bottom).
   position:relative/z-index:1 (2026-07-10, starfield ask) — required to
   paint above the fixed z-index:0 starfield layers (tokens.css); plain
   static-position content paints BEHIND a positioned z-index:0 sibling per
   CSS stacking rules. */
.md-page { position: relative; z-index: 1; max-width: 1200px; margin: 0 auto; padding: 40px 24px 20px; display: flex; flex-direction: column; min-height: calc(100vh - var(--navbar-total-h)); min-height: calc(100dvh - var(--navbar-total-h)); }
/* .md-shell just groups hero/toolbar/chip-row/content into one block — no
   margin of its own, so it stays pinned to the top of .md-page. */
.md-shell { display: flex; flex-direction: column; }
/* Legal footer is the last child of .md-page. margin-top: auto pins it to
   the bottom of the viewport; padding-top preserves the tuned 20px minimum
   gap for the overflow case, where auto resolves to 0. */
.md-page .lg-footer-link { margin-top: auto; padding-top: 20px; }

.md-hero { text-align: center; max-width: 560px; margin: 0 auto 28px; }
.md-hero h1 { font-size: 32px; font-weight: 700; margin: 0 0 8px; }
.md-hero p { font-size: 14px; color: var(--text-secondary); margin: 0; }
/* Planning-only disclaimer (2026-08-19) — same icon+tinted-box shape as
   giveaway.js's .nt-provenance-notice, but gold/warning-toned (matches
   .nt-detail__warning's accent-gold treatment there) rather than teal —
   this is a caution ("not a real order"), not an informational aside. */
.md-planning-hint {
  display: flex; align-items: flex-start; gap: 8px;
  font-size: 11.5px; line-height: 1.6; color: var(--text-secondary);
  padding: 10px 12px; border-radius: var(--radius-md); margin-bottom: 20px;
  background: rgba(var(--accent-gold-rgb),0.1); border: 1px solid rgba(var(--accent-gold-rgb),0.35);
}
.md-planning-hint .emoji-icon { flex-shrink: 0; margin-top: 1px; }

/* ── Toolbar ─────────────────────────────────────────────────────────── */
.md-toolbar { display: flex; align-items: center; gap: 10px; margin-bottom: 24px; flex-wrap: wrap; }
.md-toolbar__spacer { flex: 1; }
.md-pill {
  background: var(--dropdown-bg); border: 1px solid var(--border); color: var(--text-primary);
  border-radius: var(--radius-pill); padding: 10px 16px; font-size: 13px; font-family: var(--font-thai);
  cursor: pointer;
}
.md-search-wrap { position: relative; flex: 1; min-width: 180px; max-width: 320px; }
.md-search-icon {
  position: absolute; left: 14px; top: 50%; transform: translateY(-50%);
  font-size: 13px; opacity: 0.7; pointer-events: none;
}
.md-pill--search { width: 100%; max-width: none; cursor: text; padding-left: 34px; }
.md-pill--search::placeholder { color: var(--text-secondary); }
.md-pill--dd { appearance: none; padding-right: 30px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%238B8FA8'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 14px center;
}
.md-pill--dd:focus { border-color: var(--accent-teal); color: var(--text-primary); }
@media (hover: hover) { .md-pill--dd:hover { border-color: var(--accent-teal); color: var(--text-primary); } }
select.md-pill { appearance: none; }
/* Open option list is native/OS-rendered, ignores .md-pill's own theming
   above — see CLAUDE.md "Native <select> dropdown theming". */
.md-pill option { background: var(--bg-elevated); color: var(--text-primary); }
.md-toolbar__filters { display: contents; }

/* ── ประเภท chip row (2026-08-11 addition) — multi-select (2026-08-11
   revision, each chip toggles independently), own line below the
   search/หมวดหมู่/เรียง toolbar row, same chip visual language as
   giveaway.js's Fan Forge ประเภท/หมวดหมู่ filter (nt-chip), rebuilt here
   with .md-pill's border/pill look since giveaway.css isn't loaded on this
   page. Sized down from the initial pass (2026-08-11, 2nd revision) so this
   row reads as a lightweight secondary filter under the main toolbar
   controls, not equal-weight with them. ─────────────────────────────────── */
.md-chip-row { display: flex; flex-wrap: wrap; gap: 6px; margin: -12px 0 24px; }
.md-chip {
  background: var(--dropdown-bg); border: 1px solid var(--border); color: var(--text-secondary);
  border-radius: var(--radius-pill); padding: 5px 11px; font-size: 11px; font-family: var(--font-thai);
  font-weight: 600; cursor: pointer; transition: border-color 0.15s ease, color 0.15s ease, background 0.15s ease;
}
@media (hover: hover) { .md-chip:hover { color: var(--text-primary); border-color: var(--text-secondary); } }
.md-chip.is-active { color: var(--text-primary); background: rgba(var(--surface-rgb),0.12); border-color: rgba(var(--surface-rgb),0.3); }
/* Trailing product count (2026-08-19 ask, "คล้ายๆ chip ของ giveaway") — same
   dimmed-suffix look as giveaway.css's .nt-chip__count, own rule here since
   md.css doesn't import that file. */
.md-chip__count { opacity: 0.55; }
/* Active state colored per type — only 'ppw' gets an accent color (gold);
   every other type (gmmtv/lol/riser/any future addition) falls back to the
   neutral/white .is-active rule above (2026-08-11 ask), keeping teal free
   for a possible future "artist's own brand" type instead of every GMMTV
   sub-event claiming an accent hue as this list grows. Hover uses the same
   per-type color as .is-active (2026-08-19 ask — was the generic neutral
   :hover above for every chip, so a gold/teal chip changed to white-ish on
   hover instead of previewing its own selected color). */
.md-chip--ppw.is-active { background: rgba(var(--accent-gold-rgb),0.15); border-color: var(--accent-gold); color: var(--accent-gold-text); }
@media (hover: hover) { .md-chip--ppw:hover { background: rgba(var(--accent-gold-rgb),0.15); border-color: var(--accent-gold); color: var(--accent-gold-text); } }
/* DIALBLACK (2026-08-19) — the first "artist's own brand" type the note
   above anticipated; teal, as reserved. APL (2026-08-19, same day) is the
   2nd — every tier-2 source id needs its own rule here, the class is
   generated per source id (`md-chip--${id}`), not inherited from a shared
   tier class. */
.md-chip--dialblack.is-active, .md-chip--apl.is-active { background: rgba(var(--accent-teal-rgb),0.15); border-color: var(--accent-teal); color: var(--accent-teal-text); }
@media (hover: hover) { .md-chip--dialblack:hover, .md-chip--apl:hover { background: rgba(var(--accent-teal-rgb),0.15); border-color: var(--accent-teal); color: var(--accent-teal-text); } }

/* ── Content: grid + planner ─────────────────────────────────────────── */
.md-content { display: flex; gap: 24px; align-items: flex-start; }
/* 2-col by default; only steps up to 3 once there's enough room per column
   (2026-07-07 — 3-col at every desktop width made cards cramped/overlapping
   between ~768–1099px, where the grid track is squeezed next to the fixed
   320px planner). */
.md-grid {
  flex: 1; display: grid; grid-template-columns: repeat(2, 1fr); gap: 18px;
}
@media (min-width: 1100px) {
  .md-grid { grid-template-columns: repeat(3, 1fr); }
}
.md-empty-catalog {
  grid-column: 1 / -1; text-align: center; color: var(--text-secondary);
  font-size: 14px; padding: 60px 20px; background: var(--bg-elevated);
  border: 1px solid var(--border); border-radius: var(--radius-3xl);
}
.md-coming-soon {
  max-width: 560px; margin: 0 auto; text-align: center; color: var(--text-secondary);
  font-size: 15px; padding: 60px 24px; background: var(--bg-elevated);
  border: 1px solid var(--border); border-radius: var(--radius-3xl);
}

/* ── Product card (2026-07-06 redesign: clearer hierarchy, hover lift,
   description + conditions link, direct qty stepper) ───────────────── */
.md-card {
  background: linear-gradient(180deg, var(--bg-elevated), var(--bg-secondary));
  border: 1px solid var(--border); border-radius: var(--radius-3xl);
  padding: 10px; display: flex; flex-direction: column; gap: 10px; min-width: 0;
  transition: transform 0.18s ease, border-color 0.18s ease, box-shadow 0.18s ease;
}
@media (hover: hover) { .md-card:hover { transform: translateY(-3px); border-color: rgba(var(--accent-teal-rgb),0.4);
  box-shadow: 0 10px 28px rgba(var(--shadow-rgb), calc(0.35 * var(--shadow-mult))); } }
/* Taller (2026-08-07 ask, clarified as the GRID CARD photo, not the
   lightbox preview — was 150px) so real product photos read as a proper
   photo instead of a cropped strip. */
.md-card__thumb {
  position: relative; height: 190px; border-radius: var(--radius-2xl);
  background: linear-gradient(135deg, rgba(var(--accent-teal-rgb),0.18), rgba(var(--accent-gold-rgb),0.14));
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px;
}
.md-card__thumb-emoji { font-size: 32px; }
.md-card__thumb-label { font-size: 10px; color: var(--text-secondary); }
/* Real product photo (2026-08-07 addition) — overrides the placeholder's
   gradient background/centered-content layout; click opens the full
   gallery via the shared lightbox. */
.md-card__thumb--photo { padding: 0; overflow: hidden; }
/* Blur-up fade-in (2026-08-07 ask — match giveaway.js's .nt-card__photo
   loading pattern instead of an instant pop-in once the file arrives). */
.md-card__thumb-img { width: 100%; height: 100%; object-fit: cover; cursor: zoom-in; opacity: 0; filter: blur(16px); transition: opacity 0.35s ease, filter 0.35s ease; }
.md-card__thumb-img.is-loaded { opacity: 1; filter: blur(0); }
/* Zoom hint button (matches giveaway.js's .nt-detail__zoom-btn). */
.md-card__zoom-btn {
  position: absolute;
  right: 10px;
  bottom: 10px;
  width: 28px;
  height: 28px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: var(--radius-full);
  background: rgba(var(--black-rgb),0.35);
  color: var(--white);
  opacity: 0.55;
  cursor: zoom-in;
  transition: opacity 0.2s ease;
}
.md-card__zoom-btn svg { width: 14px; height: 14px; }
.md-card__zoom-btn:focus-visible { opacity: 1; }
@media (hover: hover) { .md-card__zoom-btn:hover { opacity: 1; } }
.md-card__rank {
  position: absolute; top: 8px; left: 8px; background: var(--accent-gold); color: var(--gold-btn-text);
  font-size: 10px; font-weight: 700; padding: 4px 10px; border-radius: var(--radius-pill);
  box-shadow: 0 2px 8px rgba(var(--accent-gold-rgb),0.4);
}
/* Keeps dark mode's vivid gold + dark text in light mode too (2026-08-08 ask)
   — solid fill + in-box text has self-contained contrast, independent of
   the page background, so light mode's muted gold isn't needed here. */
:root[data-theme="light"] .md-card__rank { background: var(--brand-gold); color: var(--brand-gold-ink); box-shadow: 0 2px 8px rgba(var(--brand-gold-rgb),0.4); }
/* `flex: 1` (2026-08-19 ask) — lets the body absorb the leftover height a
   grid row's `align-items: stretch` gives a card whose neighbors have a
   longer description, so .md-card__size-row's margin-top:auto below has
   real space to push into instead of the card's bottom edge itself. */
.md-card__body { display: flex; flex-direction: column; gap: 6px; padding: 0 4px; min-width: 0; flex: 1; }
.md-card__name { font-size: 15px; font-weight: 700; line-height: 1.35; margin-top: 6px; }
.md-card__price { font-size: 18px; font-weight: 700; color: var(--accent-gold-text); }
/* ประเภท chip (2026-08-11 addition, generalized 2026-08-11 when lol/riser
   were split out of gmmtv) — 'ppw' gold; every other type (gmmtv/lol/riser/
   any future addition) falls back to this neutral/white base rather than
   getting its own color, so teal stays free for a possible future "artist's
   own brand" type instead of every GMMTV sub-event claiming an accent hue —
   see md-chip's matching note. Distinguished by label text, not color. */
.md-card__source {
  display: inline-flex; align-self: flex-start; font-size: 10.5px; font-weight: 700;
  border-radius: var(--radius-pill); padding: 3px 10px; border: 1px solid var(--border);
  background: rgba(var(--surface-rgb),0.12); color: var(--text-primary);
}
.md-card__source--ppw { background: rgba(var(--accent-gold-rgb),0.14); border-color: transparent; color: var(--accent-gold-text); }
.md-card__source--dialblack, .md-card__source--apl { background: rgba(var(--accent-teal-rgb),0.14); border-color: transparent; color: var(--accent-teal-text); }
/* Quota badge relocated to the image's top-right corner (2026-07-07 — was
   crowding the fav/qty/add action row). Went through several color passes
   the same day (2026-08-07): started illegible (rgba(var(--surface-rgb))
   background + var(--text-primary) text — near-identical tokens in both
   themes, since MD_PRODUCTS was empty until this day so nobody ever saw it
   render for real) → solid black scrim → solid teal → translucent teal →
   now translucent orange/gold (matches .md-card__rank's gold, so both
   badges share one accent instead of a gold/teal pair). */
/* 10% smaller again (2026-08-07 ask) — was 10px / 3px 10px padding. */
.md-card__quota {
  position: absolute; top: 8px; right: 8px;
  font-size: 9px; color: var(--brand-gold-ink); font-weight: 700; white-space: nowrap;
  background: rgba(var(--accent-gold-rgb),0.55); border: 1px solid rgba(var(--accent-gold-rgb),0.7);
  border-radius: var(--radius-pill); padding: 2.7px 9px; backdrop-filter: blur(3px);
  box-shadow: 0 2px 8px rgba(var(--accent-gold-rgb),0.25);
}
/* 10% smaller (2026-08-07 ask) — was 12px. */
.md-card__desc { font-size: 10.8px; color: var(--text-secondary); line-height: 1.5; }
/* Bullet-point layout (2026-08-07 ask) — description is split on its " · "
   fact separators (see md.js's descriptionBullets()) into a real <ul>. */
.md-card__desc-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 3px; }
.md-card__desc-list li { position: relative; padding-left: 12px; }
.md-card__desc-list li::before { content: '•'; position: absolute; left: 0; color: var(--accent-teal); }
/* Inline link inside a description bullet (2026-08-19 ask, APL's
   "Pre-order via the APL website" bullet) — see md.js's appendBulletContent().
   Arrow trails the text on purpose (that ask's own order), unlike
   giveaway.css's .nt-card__link/.nt-detail__related-link icon-first pattern. */
.md-card__desc-link { color: var(--accent-teal); text-decoration: underline; }
@media (hover: hover) { .md-card__desc-link:hover { color: var(--text-primary); } }
.md-card__desc-link-arrow { display: inline-flex; margin-left: 3px; }
.md-card__desc-link-arrow svg { width: 8px; height: 8px; vertical-align: -1px; }

/* Size picker (2026-08-07 addition) — only rendered for products with sibling
   variants (currently just the T-shirt's 5 sizes). Matches .md-pill--dd's
   select styling so it doesn't look like a foreign control on the card. */
/* margin-top:auto (2026-08-19 ask) — pins the size row (and, since they
   simply follow it in flow, any quota-warning row + the qty/add-to-plan
   controls right after .md-card__body) to the bottom of the card instead of
   sitting wherever the description happens to end — needed because a
   stretched grid row can leave empty space below a card with a shorter
   description than its row neighbors. */
.md-card__size-row { display: flex; align-items: center; gap: 8px; margin-top: auto; }
.md-card__size-label { font-size: 12px; color: var(--text-secondary); flex-shrink: 0; }
.md-card__size-select {
  flex: 1; min-width: 0; height: 32px; box-sizing: border-box;
  /* Was `var(--surface)` (2026-08-07 fix) — that token doesn't exist in
     tokens.css (only `--surface-rgb`, the raw-channel version used for
     rgba() overlays elsewhere), so the property silently fell through to
     the browser's default white <select> background in both themes. */
  background: var(--bg-elevated); border: 1px solid var(--border); color: var(--text-primary);
  border-radius: var(--radius-md); padding: 0 10px; font-size: 12px; font-family: var(--font-thai); cursor: pointer;
}
.md-card__size-select:focus { border-color: var(--accent-teal); }
@media (hover: hover) { .md-card__size-select:hover { border-color: var(--accent-teal); } }
/* Open option list is native/OS-rendered, ignores the closed box's theming
   above — see CLAUDE.md "Native <select> dropdown theming". */
.md-card__size-select option { background: var(--bg-elevated); color: var(--text-primary); }

/* Row 1 (2026-07-07 rework): fav on the left, qty stepper right-aligned via
   justify-content:space-between — just these two controls share this row now. */
.md-card__actions {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  margin-top: auto; padding: 8px 4px 0; border-top: 1px solid var(--border);
}
.md-fav-btn {
  flex-shrink: 0; height: 36px; box-sizing: border-box;
  display: flex; align-items: center; justify-content: center; gap: 6px;
  background: rgba(var(--accent-teal-rgb),0.1); border: 1px solid rgba(var(--accent-teal-rgb),0.3); color: var(--accent-teal-text);
  border-radius: var(--radius-pill); padding: 0 12px; font-size: 10.8px; font-weight: 600; cursor: pointer;
  font-family: var(--font-thai); white-space: nowrap;
}
.md-fav-btn.is-active { background: rgba(var(--accent-teal-rgb),0.25); }
/* Icon kept at a fixed whole-pixel size (2026-08-07 fix) — it used to
   inherit the button's own font-size via "1em" sizing, so shrinking the
   button to 10.8px (10% smaller ask) put the outline heart's stroke-width
   at a sub-pixel value, which rendered as a fuzzy/uneven outline instead of
   a clean heart shape. Text (the fave count) still shrinks with the button;
   only the icon is decoupled from it. */
.md-fav-btn__icon { font-size: 13px; display: inline-flex; }

/* Row 2: "เพิ่มลงแพลน" — its own full-width row below, separate from fav/qty.
   Solid teal (2026-07-07 — violet didn't fit the site's gold/teal space
   theme; teal is the page's own secondary accent, already used for the fav
   pill, so a solid fill here reads as on-brand instead of an outside color).
   No margin-top of its own — spacing from the row above comes purely from
   .md-card's flex `gap: 10px`, matching every other gap in the card instead
   of stacking an extra margin on top of it (that mismatch was the "uneven
   spacing" bug). */
.md-add-btn {
  display: block; width: 100%; height: 38px; box-sizing: border-box;
  background: var(--accent-teal); color: var(--teal-btn-text); border: none;
  border-radius: var(--radius-lg); padding: 0 14px; font-size: 13px; font-weight: 700; cursor: pointer;
  font-family: var(--font-thai); white-space: nowrap;
}
@media (hover: hover) { .md-add-btn:hover { filter: brightness(1.08); } }
:root[data-theme="light"] .md-add-btn { background: var(--brand-teal); color: var(--text-primary); }

/* Direct-qty stepper on the card — +/- are true circles now (2026-07-07,
   were rounded-squares). */
.md-stepper--card {
  flex-shrink: 0; height: 36px; box-sizing: border-box;
  background: rgba(var(--accent-gold-rgb),0.1); border: 1px solid rgba(var(--accent-gold-rgb),0.35);
  border-radius: var(--radius-pill); padding: 0 6px;
}
.md-stepper--card .md-stepper__btn { background: var(--accent-gold); color: var(--gold-btn-text); border: none; border-radius: var(--radius-full); }
:root[data-theme="light"] .md-stepper--card .md-stepper__btn { background: var(--brand-gold); color: var(--brand-gold-ink); }

/* ── Planner panel (2026-07-06 redesign — distinct identity from product
   cards: gold-accented header band, pill-style rows, prominent total). ── */
/* Height-capped + internal scroll (2026-08-07 fix, desktop only — see the
   max-height:none override in the mobile media query below) — a long cart
   used to just grow the sidebar taller than the viewport with no way to
   reach the total/save button at the bottom (sticky doesn't add scrolling
   on its own). Now the ITEM LIST scrolls internally (.md-planner__list
   below) while the total/save button row stays permanently pinned and
   visible — replaced an earlier floating "jump to summary" bar, which
   fought with this element's own `position: sticky` during scrollIntoView
   and looked jumpy/flickery. */
.md-planner {
  width: 320px; flex-shrink: 0; background: var(--bg-elevated); border: 1px solid rgba(var(--accent-gold-rgb),0.3);
  border-radius: var(--radius-3xl); padding: 0; position: sticky; top: calc(var(--navbar-total-h) + 20px);
  overflow: hidden; box-shadow: 0 12px 32px rgba(var(--shadow-rgb), calc(0.3 * var(--shadow-mult)));
  max-height: calc(100vh - var(--navbar-total-h) - 40px);
  display: flex; flex-direction: column;
}
.md-planner__head {
  font-size: 16.8px; font-weight: 700; padding: 18px 20px; margin: 0;
  background: linear-gradient(135deg, rgba(var(--accent-gold-rgb),0.22), rgba(var(--accent-gold-rgb),0.06));
  border-bottom: 1px solid rgba(var(--accent-gold-rgb),0.3);
}
.md-planner__body { display: flex; flex-direction: column; flex: 1; min-height: 0; }
.md-planner__list { display: flex; flex-direction: column; gap: 10px; padding: 16px 20px 4px; overflow-y: auto; overscroll-behavior: contain; flex: 1; min-height: 0; }
.md-planner__empty { font-size: 12px; color: var(--text-secondary); text-align: center; padding: 20px 8px; }
.md-planner__row {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  background: rgba(var(--surface-rgb),0.04); border: 1px solid var(--border);
  border-radius: var(--radius-xl); padding: 10px 12px;
}
/* Alpha doubled for light mode (2026-08-08 fix) — 4% of the light-mode
   "ink" color on an already-light card read as too flat to register as its
   own raised row (this alpha was tuned for dark mode, where 4% white on
   near-black lifts much more visibly). */
:root[data-theme="light"] .md-planner__row { background: rgba(var(--surface-rgb),0.08); }
.md-planner__info { flex: 1 1 100%; min-width: 0; }
.md-planner__name { font-size: 13px; font-weight: 600; white-space: normal; overflow-wrap: break-word; }
.md-planner__unit { font-size: 10px; color: var(--text-secondary); }
.md-planner__line { font-size: 13px; font-weight: 700; color: var(--accent-gold-text); margin-left: auto; }

.md-planner__remove {
  /* color was a literal #FF9CA8 (2026-08-08 fix) — tuned to read against the
     DARK planner card only; on the light card it measured 1.72:1 contrast,
     effectively invisible. var(--like-red) is theme-aware (3.63:1 in light
     mode, ~4.2:1 in dark — close enough to the original that dark mode
     looks unchanged). */
  background: rgba(var(--like-red-rgb),0.12); border: 1px solid rgba(var(--like-red-rgb),0.35); color: var(--like-red);
  border-radius: var(--radius-md); width: 26px; height: 26px; display: flex; align-items: center; justify-content: center;
  cursor: pointer; font-size: 12px; padding: 0; flex-shrink: 0;
}
@media (hover: hover) { .md-planner__remove:hover { background: rgba(var(--like-red-rgb),0.22); } }

/* +/- buttons are true circles (2026-07-07 — were rounded-squares). Qty
   readout is a matching circular badge. */
.md-stepper { display: flex; align-items: center; gap: 6px; flex-shrink: 0; }
.md-stepper__btn {
  width: 22px; height: 22px; border-radius: var(--radius-full); border: 1px solid var(--border);
  background: rgba(var(--surface-rgb),0.06); color: var(--text-primary); cursor: pointer; font-size: 13px;
  display: flex; align-items: center; justify-content: center; padding: 0;
}
.md-stepper__qty {
  font-size: 12px; font-weight: 700; min-width: 22px; height: 22px; text-align: center;
  display: flex; align-items: center; justify-content: center;
  border-radius: var(--radius-full); background: rgba(var(--surface-rgb),0.08);
}
.md-stepper--card .md-stepper__qty { background: rgba(var(--black-rgb),0.2); }

/* Plain text only (2026-07-07 — no more box/border, was too heavy). */
.md-quota-warn { color: var(--accent-gold-text); font-size: 11px; font-weight: 600; }

.md-planner__hr { border-top: 1px solid var(--border); margin: 14px 0; }
.md-planner__srow { display: flex; justify-content: space-between; font-size: 12px; color: var(--text-secondary); margin-bottom: 6px; }
/* Label stays normal-ish size (13px → 15.6px, +20% per 2026-07-07 request) —
   only the amount itself (.md-planner__total-val) is meant to be large. */
.md-planner__srow--total { align-items: center; font-size: 15.6px; font-weight: 600; color: var(--text-primary); }
.md-planner__total-val { font-size: 26px; font-weight: 800; color: var(--accent-gold-text); text-shadow: 0 2px 14px rgba(var(--accent-gold-rgb), calc(0.4 * var(--glow-mult))); }
.md-planner__note { font-size: 10px; color: var(--text-secondary); margin-top: 8px; }

/* flex-shrink:0 (2026-08-07) — sits alongside .md-planner__list in a flex
   column where the list is the one that scrolls/shrinks; this must keep
   its natural height and stay fully visible, never compressed. */
.md-planner__summary { padding: 0 20px 10px; flex-shrink: 0; }

/* Button group (2026-07-07, fixed same day: was missing visible spacing
   between the 3 buttons — margin-top on each individually read as ~0 next
   to the others). Explicit flex column + gap replaces the old per-button
   margin-top rules. Hierarchy: save = solid gold (primary), Advance
   Purchase = outline blue (secondary), conditions = bordered teal button
   (tertiary but still a real button, not a text link). */
.md-planner__actions { display: flex; flex-direction: column; gap: 12px; padding: 0 20px 20px; margin-top: 6px; flex-shrink: 0; }

.md-save-btn {
  width: 100%; background: var(--accent-gold); color: var(--gold-btn-text); border: none;
  border-radius: var(--radius-lg); padding: 13px; font-size: 16.1px; font-weight: 700; cursor: pointer;
  font-family: var(--font-thai);
}
@media (hover: hover) { .md-save-btn:hover { filter: brightness(1.06); } }
:root[data-theme="light"] .md-save-btn { background: var(--brand-gold); color: var(--brand-gold-ink); }

/* Advance Purchase CTA — outline blue (secondary), from the existing
   identity-color palette (data-p.js COLOR_PALETTE '#0170fe'), not a new token.
   Hidden (2026-08-19 ask) — kept in the DOM/CSS rather than deleted, in case
   it's wanted back; `display: none` here is the single toggle point. */
.md-advance-btn {
  display: none; width: 100%; background: rgba(1,112,254,0.12); color: #5CA8FF;
  border: 1px solid rgba(1,112,254,0.55);
  border-radius: var(--radius-lg); padding: 12px; font-size: 14px; font-weight: 700; cursor: pointer;
  font-family: var(--font-thai); text-align: center; text-decoration: none; box-sizing: border-box;
}
@media (hover: hover) { .md-advance-btn:hover { background: rgba(1,112,254,0.2); } }
/* 2026-08-08 fix — badge-pattern bug, measured 2.14:1 in light mode. Same
   hue, darkened to ~3.3:1. */
:root[data-theme="light"] .md-advance-btn { color: #006DEB; }

/* Conditions button in the planner — a real bordered button (teal, distinct
   from gold/blue above), not a plain text link. */
.md-planner-cond-btn {
  width: 100%; background: rgba(var(--accent-teal-rgb),0.12); color: var(--accent-teal-text);
  border: 1px solid rgba(var(--accent-teal-rgb),0.4); border-radius: var(--radius-lg); padding: 7px 11px; font-size: 12px;
  font-weight: 600; cursor: pointer; font-family: var(--font-thai);
}
@media (hover: hover) { .md-planner-cond-btn:hover { background: rgba(var(--accent-teal-rgb),0.2); } }

/* Mobile collapse toggle (2026-07-06) — hidden on desktop; see media query
   below for the >768px override that always shows the body expanded.
   Only the chevron arrow is enlarged (+15%, 2026-07-07) — label/badge stay
   at their original size (a previous pass wrongly scaled the whole bar). */
.md-planner__toggle {
  display: none; width: 100%; align-items: center; gap: 10px;
  background: rgba(var(--surface-rgb),0.04); border: none; border-bottom: 1px solid rgba(var(--accent-gold-rgb),0.3);
  color: var(--text-primary); padding: 14px 20px; font-size: 13px; font-weight: 600;
  cursor: pointer; font-family: var(--font-thai); text-align: left;
}
.md-planner__toggle-badge {
  flex-shrink: 0; min-width: 22px; height: 22px; border-radius: var(--radius-full);
  background: var(--accent-gold); color: var(--gold-btn-text); font-size: 12px; font-weight: 700;
  display: flex; align-items: center; justify-content: center;
}
:root[data-theme="light"] .md-planner__toggle-badge { background: var(--brand-gold); color: var(--brand-gold-ink); }
.md-planner__toggle-label { font-size: 13px; }
/* Qty/price numbers inside the toggle label + floating bar (2026-08-07 ask)
   — stand out from the surrounding plain text. */
.md-summary-num { color: var(--accent-gold-text); font-weight: 700; }
.md-planner__toggle-chevron { margin-left: auto; font-size: 15px; transition: transform 0.2s ease; }
.md-planner__toggle.is-open .md-planner__toggle-chevron { transform: rotate(180deg); }

/* Floating quick-jump bar (2026-08-07 addition, orange theme + 🛒 icon added
   same day) — mobile only. Compact pill, hidden by default (only
   .is-visible flips it on, via JS IntersectionObserver tracking on the
   inline toggle — see buildPlanner() in md.js); the observed target is
   display:none on desktop and never intersects, so this never shows there
   — no viewport-width media query needed on `.is-visible` itself. (A
   same-day desktop version — showing when the sidebar's total/save button
   scrolled out of view — was tried and reverted; scrollIntoView fought
   with that element's own `position: sticky` and looked jumpy. Desktop's
   real fix lives on .md-planner/.md-planner__list instead: the sidebar is
   height-capped with the item list scrolling internally, so the total/save
   button just stays permanently visible and there's nothing to jump to.)
   Solid accent-gold fill (not just a gold border/accent like the toggle
   bar) so it reads as its own attention-grabbing "cart" affordance while
   floating over page content. */
.md-float-bar {
  /* Centered + width-capped — reads fine at any width this could plausibly
     show at, though it's mobile-only today. */
  display: none; position: fixed; left: 50%; transform: translateX(-50%);
  bottom: calc(16px + var(--safe-bottom) + var(--inapp-bottom)); z-index: 200; width: min(360px, calc(100% - 32px));
  align-items: center; gap: 10px;
  background: var(--accent-gold); border: none;
  border-radius: var(--radius-pill); padding: 10px 16px; box-shadow: 0 6px 20px rgba(var(--accent-gold-rgb),0.45);
  cursor: pointer; font-family: var(--font-thai); color: var(--gold-btn-text);
}
:root[data-theme="light"] .md-float-bar { background: var(--brand-gold); color: var(--brand-gold-ink); }
.md-float-bar.is-visible { display: flex; }
.md-float-bar__icon { flex-shrink: 0; font-size: 18px; line-height: 1; display: flex; }
.md-float-bar__badge {
  flex-shrink: 0; min-width: 20px; height: 20px; border-radius: var(--radius-full);
  background: var(--fixed-bg-3); color: var(--on-dark-text); font-size: 11px;   /* fixed dark chip, both themes (2026-10-22; was PPW #1a1200) */ font-weight: 700;
  display: flex; align-items: center; justify-content: center;
}
.md-float-bar__label { font-size: 12px; font-weight: 700; flex: 1; text-align: left; }
.md-float-bar__chevron { font-size: 11px; opacity: 0.7; flex-shrink: 0; }
/* .md-summary-num defaults to accent-gold text (2026-08-07 ask) — invisible
   against this bar's own solid-gold background, so override to the same
   ink used for the rest of the bar's text/badge (2026-08-08: switched to
   var(--gold-btn-text) so this also flips correctly in light mode, where
   --accent-gold itself is a dark color and the old fixed #1a1200 would
   collide with it). */
.md-float-bar__label .md-summary-num { color: var(--gold-btn-text); }

/* ── Cart-merge popup (§8.3) / conditions popup (shared modal shell) ─── */
.md-modal-scrim {
  position: fixed; inset: 0; background: rgba(var(--black-rgb),0.55);
  /* Was 999 — one below .ppw-nav's 1000 (nav.css), so the sticky navbar
     rendered on top of this popup instead of behind it. */
  z-index: 1100;
  /* Safe-area padding (2026-08-22) — the scrim is inset:0 so it now covers
     the status bar and home indicator too; padding it keeps the CARD inside
     the safe area without touching any of the card's own vh-based max-heights. */
  display: flex; align-items: center; justify-content: center;
  padding: calc(20px + var(--safe-top)) calc(20px + var(--safe-right)) calc(20px + var(--safe-bottom)) calc(20px + var(--safe-left));
}
.md-modal {
  position: relative;
  width: 100%; max-width: 460px; background: var(--bg-elevated); border: 1px solid var(--border);
  border-radius: var(--radius-3xl); padding: 24px; display: flex; flex-direction: column; gap: 12px;
}
.md-modal__title { font-size: 17px; font-weight: 700; }

/* Top-right "×" close button (2026-07-07 — replaces a plain "ปิด" text
   button at the bottom, matching the corner-close convention already used
   by p/modals.js's auth-modal shell). */
.md-modal__close-x {
  position: absolute; top: 14px; right: 14px; width: 28px; height: 28px;
  display: flex; align-items: center; justify-content: center;
  background: rgba(var(--surface-rgb),0.06); border: 1px solid var(--border); border-radius: var(--radius-full);
  color: var(--text-secondary); font-size: 18px; line-height: 1; cursor: pointer;
}
@media (hover: hover) { .md-modal__close-x:hover { background: rgba(var(--surface-rgb),0.14); color: var(--text-primary); } }
.md-modal__box {
  background: rgba(var(--surface-rgb),0.04); border: 1px solid var(--border); border-radius: var(--radius-xl);
  padding: 12px 14px; display: flex; flex-direction: column; gap: 4px; max-height: 140px; overflow-y: auto;
}
.md-modal__box-label { font-size: 11px; color: var(--text-secondary); margin-bottom: 4px; }
.md-modal__box-item { font-size: 12px; }
/* Alpha doubled for light mode (2026-08-08 fix) — same reasoning as
   .md-planner__row above. */
:root[data-theme="light"] .md-modal__box { background: rgba(var(--surface-rgb),0.08); }
:root[data-theme="light"] .md-modal__option { background: rgba(var(--surface-rgb),0.10); }

.md-modal__option {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  background: rgba(var(--surface-rgb),0.05); border: 1px solid var(--border); color: var(--text-primary);
  border-radius: var(--radius-lg); padding: 12px 14px; font-size: 13px; font-family: var(--font-thai);
  cursor: pointer; text-align: left; flex-wrap: wrap;
}
@media (hover: hover) { .md-modal__option:hover { background: rgba(var(--surface-rgb),0.09); } }
.md-modal__option--reco { border-color: var(--accent-gold); }
.md-modal__badge { font-size: 10px; font-weight: 700; padding: 3px 8px; border-radius: var(--radius-pill); }
.md-modal__badge--gold { background: rgba(var(--accent-gold-rgb),0.18); color: var(--accent-gold-text); }
.md-modal__option-warn { width: 100%; font-size: 11px; color: var(--accent-gold-text); }

.md-modal__footer {
  background: transparent; border: none; color: var(--text-secondary); font-size: 12px;
  cursor: pointer; font-family: var(--font-thai); padding: 6px; text-align: center;
}
@media (hover: hover) { .md-modal__footer:hover { color: var(--text-primary); } }

/* Conditions popup (2026-07-06 redesign — numbered chips instead of a plain
   <ol>, easier to scan). */
.md-modal--conditions {
  max-width: 520px; max-height: 80vh; overflow-y: auto;
  background: linear-gradient(180deg, var(--bg-elevated), var(--bg-primary));
  border-color: rgba(var(--accent-teal-rgb),0.3);
}
.md-modal--conditions .md-modal__title { padding-right: 30px; }
/* Tab switcher (2026-08-07 addition — Advance Purchase / การซื้อหน้างาน,
   real terms replacing the mockup-era single list). */
.md-cond-tabs { display: flex; gap: 8px; margin: 14px 0 4px; }
.md-cond-tab {
  flex: 1; padding: 8px 10px; border-radius: var(--radius-pill); border: 1px solid var(--border);
  background: transparent; color: var(--text-secondary); font-size: 12px; font-weight: 600;
  font-family: var(--font-thai); cursor: pointer; text-align: center;
}
.md-cond-tab.is-active { background: rgba(var(--accent-teal-rgb),0.18); border-color: var(--accent-teal); color: var(--accent-teal-text); }
/* No per-item box (2026-07-07 — the bordered card look was bulky/wasted
   space); a thin divider between rows replaces it. */
.md-conditions-list { margin: 0; padding: 0; list-style: none; display: flex; flex-direction: column; counter-reset: cond; }
.md-conditions-list li {
  counter-increment: cond;
  display: flex; gap: 12px; align-items: flex-start;
  font-size: 13px; line-height: 1.6; color: var(--text-primary);
  padding: 10px 2px; border-bottom: 1px solid var(--border);
}
.md-conditions-list li:last-child { border-bottom: none; }
/* Single flex child holding the rich text (2026-08-07 fix) — see md.js
   comment. min-width:0 lets it shrink below its content's natural width
   instead of overflowing the flex row. */
.md-cond-text { flex: 1; min-width: 0; }
.md-conditions-list li::before {
  content: counter(cond); flex-shrink: 0; width: 20px; height: 20px; border-radius: var(--radius-full);
  background: rgba(var(--accent-teal-rgb),0.18); color: var(--accent-teal-text); font-size: 11px; font-weight: 700;
  display: flex; align-items: center; justify-content: center; margin-top: 1px;
}
/* Inline emphasis (2026-08-07 addition, color removed same day — reported
   as messy/distracting) — <strong> = the one critical fact per sentence
   (dates/times/prices/required documents), <u> = prohibition verbs
   (ห้าม/ไม่ได้/ตัดสิทธิ์). Plain bold/underline only, no color. */
.md-conditions-list li strong { font-weight: 700; }

/* Success popup (2026-07-06 addition; login link + prominent OK button
   added 2026-07-07). */
.md-modal--success { text-align: center; align-items: center; }
.md-modal--success .md-modal__title { font-size: 19px; }
.md-modal__desc { font-size: 13px; color: var(--text-secondary); line-height: 1.6; }
/* Guest case: login button + OK sit side by side, deliberately different
   colors (teal vs gold) so they read as two distinct actions (2026-07-07). */
.md-modal__btn-row { display: flex; gap: 10px; width: 100%; margin-top: 6px; }
.md-modal__login-btn {
  flex: 1; display: flex; align-items: center; justify-content: center; gap: 6px;
  background: rgba(var(--accent-teal-rgb),0.14); color: var(--accent-teal-text); border: 1px solid rgba(var(--accent-teal-rgb),0.45);
  border-radius: var(--radius-lg); padding: 12px; font-size: 14px; font-weight: 700; cursor: pointer;
  font-family: var(--font-thai); text-decoration: none;
}
@media (hover: hover) { .md-modal__login-btn:hover { background: rgba(var(--accent-teal-rgb),0.24); } }
.md-modal__ok-btn {
  flex: 1; background: var(--accent-gold); color: var(--gold-btn-text); border: none;
  border-radius: var(--radius-lg); padding: 12px; font-size: 14px; font-weight: 700; cursor: pointer;
  font-family: var(--font-thai);
}
@media (hover: hover) { .md-modal__ok-btn:hover { filter: brightness(1.06); } }
:root[data-theme="light"] .md-modal__ok-btn { background: var(--brand-gold); color: var(--brand-gold-ink); }

@media (max-width: 768px) {
  /* Bottom padding widened 20px -> 90px (2026-08-08 fix) — .md-float-bar
     floats at bottom:16px with ~40px height (~56px total footprint); the
     old 20px wasn't enough clearance, so it sat on top of the legal footer
     links (completely unclickable) and nearly touched the last grid row's
     add-to-plan buttons. */
  .md-page { padding: 28px 16px 90px; }
  .md-hero h1 { font-size: 24px; }
  .md-content { flex-direction: column; }
  .md-grid { grid-template-columns: repeat(2, 1fr); width: 100%; }
  /* Still full-width/static/first-in-flow on mobile (unchanged from
     2026-07-06) — only the max-height cap now carries over from the
     desktop rule above instead of being reset to none (2026-08-08 fix,
     reverted the 2026-08-07 max-height:none override). .md-planner above
     already caps height + scrolls .md-planner__list internally with a
     pinned summary/save row. Without the cap, list rows scrolled the whole
     PAGE instead of just themselves, pushing .md-planner__toggle off-screen
     within a few items and firing .md-float-bar's "you've scrolled past
     the plan" trigger while the visitor was still browsing their own list,
     not the product grid. Scoping the scroll to the list itself means the
     toggle stays put (and the float bar stays hidden) until the visitor
     has actually scrolled past the whole plan panel into the grid. */
  .md-planner { width: 100%; position: static; order: -1; }
  .md-toolbar { flex-direction: column; align-items: stretch; }
  .md-search-wrap { max-width: none; }
  .md-toolbar__filters { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; width: 100%; }
  .md-toolbar__filters .md-pill--dd { width: 100%; }

  /* Collapsible plan list (mobile only) — desktop always shows it expanded,
     since .md-planner__toggle is display:none there and .is-collapsed only
     has meaning inside this media query. */
  .md-planner__toggle { display: flex; }
  .md-planner__body.is-collapsed { display: none; }
}

@media (max-width: 480px) {
  .md-grid { grid-template-columns: 1fr; }
}

/* Red-fog theme (events/jasper-temptation-2026, 2026-10-12) — scoped so PPW is untouched.
   The fill red (#C0201C) is not an ink: text uses --accent-gold-text, red actions get white ink. */
:root[data-motif-pattern="red-fog"][data-project] .md-card__price { color: var(--accent-gold-text); }
:root[data-motif-pattern="red-fog"][data-project] .md-summary-num { color: var(--text-primary); }
:root[data-motif-pattern="red-fog"][data-project][data-theme="light"] .md-card__quota { background: var(--accent-gold); }
:root[data-motif-pattern="red-fog"][data-project] .md-card__quota { color: #FFFFFF; }
:root[data-motif-pattern="red-fog"][data-project] .md-add-btn,
:root[data-motif-pattern="red-fog"][data-project][data-theme="light"] .md-add-btn { background: var(--brand-teal); color: #0A0A0A; }
