/* ============================================================================
   Digital Products Marketplace - premium design system (namespace: dpx-*)
   Theme: premium minimal SaaS. White / black / light-gray neutrals dominate (~85-90%);
   DARK is primary (buttons/avatars/FAB/hero). Accents are sparing + semantic: GREEN = success
   /positive ONLY, BLUE = interactive/active/focus, purple = featured, amber = pending, red =
   rejected, gold = best-seller. Charts stay colorful. Glassmorphism + premium elevation hover.
   Loaded on every module page via `<body class="... dpx">`. Fully responsive
   (Desktop / Laptop / Tablet / iPad / Mobile). No em-dash in copy.
   ============================================================================ */

:root {
  /* success/positive green ramp (semantic accent only - NOT the brand color) */
  --dpx-green-50: #e9fbf3;
  --dpx-green-100: #c9f5e1;
  --dpx-green-300: #5fe0a8;
  --dpx-green: #10b981;
  --dpx-green-600: #059669;
  --dpx-green-700: #047857;
  --dpx-green-800: #065f46;
  /* dark = primary (neutral charcoal -> black); success grad kept for positive actions */
  --dpx-grad: linear-gradient(135deg, #2a2e37 0%, #14161c 60%, #0c0e12 100%);
  --dpx-grad-hero: linear-gradient(160deg, #191b20 0%, #0f1013 60%, #0a0b0d 100%);
  --dpx-grad-text: linear-gradient(120deg, #2f333c, #17191f 60%, #0c0e12);
  --dpx-grad-success: linear-gradient(135deg, #12c98c 0%, #059669 55%, #047857 100%);
  /* neutral near-black for dark/glass moments */
  --dpx-ink: #0d0f13;
  --dpx-ink-2: #14171c;
  --dpx-ink-3: #1c2028;
  --dpx-on-dark: #f3f4f6;
  --dpx-on-dark-2: #a3a9b3;
  /* light neutral surfaces (browsing) */
  --dpx-bg: #f6f7f9;
  --dpx-surface: #ffffff;
  --dpx-surface-2: #f1f3f5;
  --dpx-line: #e6e8ec;
  --dpx-line-2: #eef0f3;
  /* text (neutral) */
  --dpx-text: #101319;
  --dpx-text-2: #40474f;
  --dpx-muted: #6b7280;
  --dpx-faint: #9ca3af;
  /* accents (semantic, sparing) */
  --dpx-blue: #2563eb;
  --dpx-blue-50: #eff4ff;
  --dpx-blue-600: #1d4ed8;
  --dpx-purple: #7c3aed;
  --dpx-orange: #f97316;
  --dpx-red: #ef4444;
  --dpx-yellow: #f59e0b;
  --dpx-gold: #b7791f;
  --dpx-gold-50: #fbf3e2;
  --dpx-gold-700: #8a5a0b;
  /* --------------------------------------------------------------------
     RADII (Phase 25). Eight steps derived from a census of all 23 DP
     stylesheets, not invented: 727 border-radius declarations carried 21
     distinct literals, clustering at 12 / 16 / 10 / 14 / 11 / 18 / 8 / 9.
     Each step is the mode of its cluster, so every substitution moves a
     corner by at most 1px. `--dpx-r` / `-sm` / `-xs` keep their original
     values as aliases so the 44 existing call sites do not move at all.
     Skin-independent on purpose: dpx-night.css flips colour tokens only.
     -------------------------------------------------------------------- */
  --dpx-r1: 6px;
  --dpx-r2: 8px;
  --dpx-r3: 10px;
  --dpx-r4: 12px;
  --dpx-r5: 14px;
  --dpx-r6: 16px;
  --dpx-r7: 18px;
  --dpx-r8: 22px;
  --dpx-pill: 999px;
  /* legacy aliases - unchanged values, kept so no existing rule shifts */
  --dpx-r: var(--dpx-r8);
  --dpx-r-sm: var(--dpx-r5);
  --dpx-r-xs: var(--dpx-r3);

  /* --------------------------------------------------------------------
     SPACING. An even 2px scale. The census found gap/padding literals at
     EVERY integer from 2 to 26 - there is no existing grid, the values are
     hand-tuned per component. So these tokens are the standard for new and
     touched code, and the Phase 25 sweep substitutes ONLY exact matches
     (zero pixel movement). Odd literals are deliberately left as-is rather
     than snapped; forcing them onto the scale would be a redesign wearing
     a refactor's clothes.
     -------------------------------------------------------------------- */
  --dpx-s1: 2px;
  --dpx-s2: 4px;
  --dpx-s3: 6px;
  --dpx-s4: 8px;
  --dpx-s5: 10px;
  --dpx-s6: 12px;
  --dpx-s7: 14px;
  --dpx-s8: 16px;
  --dpx-s9: 18px;
  --dpx-s10: 20px;
  --dpx-s12: 24px;
  --dpx-s16: 32px;
  --dpx-s24: 48px;

  /* --------------------------------------------------------------------
     TYPE. Target scale for new and touched code. NOT swept: the module
     currently ships 18 distinct font-size literals at half-pixel
     granularity (13 / 13.5 / 12.5 / 12 / 11.5 / 11 / 14 / 14.5 ...).
     Collapsing those onto 7 steps changes how the product reads and is a
     design decision, not a substitution - it needs its own approval.
     -------------------------------------------------------------------- */
  --dpx-fs-xs: 11px;
  --dpx-fs-sm: 12px;
  --dpx-fs-base: 13px;
  --dpx-fs-md: 15px;
  --dpx-fs-lg: 18px;
  --dpx-fs-xl: 22px;
  --dpx-fs-2xl: 27px;
  --dpx-lh-tight: 1.1;
  --dpx-lh-base: 1.5;
  --dpx-lh-loose: 1.6;

  /* --------------------------------------------------------------------
     MOTION. The first motion tokens in the repo. Durations are the modes
     of the four real clusters (0.10-0.13 / 0.14-0.18 / 0.20-0.24 /
     0.25-0.35); the second is the house default with ~150 declarations.
     Substitution error is <= 30ms and <= 10ms for the 137 declarations in
     the dominant 0.15-0.16 band. Values above 0.31s (0.32 / 0.34 / 0.35
     and every keyframe over 0.4s - shimmer, pulse, portal, burst) stay
     literal: they are character, not interface response, and snapping
     them would cost more than the consistency is worth.
     `--dpx-ease` is the curve the module already uses, spelled four
     different ways across the files. The 267 bare `ease` declarations are
     NOT converted: `ease` is cubic-bezier(.25,.1,.25,1) and swapping it
     would change how every transition feels, which is a redesign.
     DELAYS are never tokenised - `animation: x 0.6s 0.1s` has a 0.1s
     delay, and turning that into a 120ms duration token would nearly
     double it. Only the first time value in each shorthand layer moves.
     -------------------------------------------------------------------- */
  --dpx-dur-1: 120ms;
  --dpx-dur-2: 160ms;
  --dpx-dur-3: 220ms;
  --dpx-dur-4: 280ms;
  --dpx-ease: cubic-bezier(0.2, 0.7, 0.3, 1);
  --dpx-ease-out: cubic-bezier(0, 0, 0.2, 1);
  --dpx-ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);

  /* shadows (neutral) */
  --dpx-sh-1: 0 1px 2px rgba(16, 18, 24, 0.05);
  --dpx-sh-2: 0 10px 30px -18px rgba(16, 18, 24, 0.24);
  --dpx-sh-3: 0 26px 64px -32px rgba(15, 18, 26, 0.4);
  --dpx-glow: 0 14px 36px -16px rgba(15, 18, 26, 0.34);
  --dpx-glow-success: 0 14px 40px -16px rgba(16, 185, 129, 0.42);
  --dpx-blue-ring: 0 0 0 3px rgba(37, 99, 235, 0.15);
  /* fonts (loaded in page head) */
  --dpx-font: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --dpx-font-num: "Inter", sans-serif;
  --dpx-wrap: 1240px;
}

/* ---- page base ---- */
body.dpx {
  margin: 0;
  background: var(--dpx-bg);
  color: var(--dpx-text);
  font-family: var(--dpx-font);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}
body.dpx * { box-sizing: border-box; }

/* ---- typography floor (every DP surface inherits this) ----
   Headings balance so a two-line title never leaves one orphan word on its own
   line; body copy uses `pretty` so paragraphs never end on a single-word widow.
   Both degrade to plain `wrap` on engines that lack them, so this is additive.
   Digits are tabular everywhere numbers stack in a column (prices, counts,
   ledger rows) so decimal points line up down the page instead of drifting. */
body.dpx h1, body.dpx h2, body.dpx h3, body.dpx h4 { text-wrap: balance; }
body.dpx p, body.dpx li { text-wrap: pretty; }
body.dpx [class*="-price"], body.dpx [class*="-amount"], body.dpx [class*="-total"],
body.dpx [class*="-stat-v"], body.dpx [class*="-count"], body.dpx [class*="-money"] {
  font-variant-numeric: tabular-nums;
}
/* Freelancing HONOURS (seller level chip + trust badges) are a Freelancing-marketplace concept and
   must NOT appear anywhere in the Digital-Products marketplace - owner rule (2026-08-11): they show
   ONLY in freelancing. Hide the freelancing leaf elements plus the library's `.dpx-lb-honors`
   wrapper (an inline-flex that would otherwise leave a trailing gap once emptied). `display:none`
   drops the leaves out of their flex seller-lines (browse / detail / cart) so no gap is reserved.
   `!important` is needed: the store/browse ship (0,3,0) `display:flex` rules on these that a plain
   `body.dpx` (0,2,0) selector would lose to. DP-native pills (`.dpx-lb-badge` "Up to date", status
   chips) use different classes and are deliberately untouched. */
body.dpx .fp-level,
body.dpx .fp-badge,
body.dpx .fp-badge-row,
body.dpx .dpx-lb-honors { display: none !important; }
.dpx-wrap { width: 100%; max-width: var(--dpx-wrap); margin: 0 auto; padding: 0 24px; }
.dpx-num { font-family: var(--dpx-font-num); font-feature-settings: "tnum" 1; }
.dpx-sr { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); border: 0; }

/* ---- kicker + section headings ---- */
.dpx-kicker {
  display: inline-flex; align-items: center; gap: 7px; font-size: 12px; font-weight: 800;
  letter-spacing: 0.12em; text-transform: uppercase; color: var(--dpx-text-2);
}
.dpx-h1 { font-size: clamp(1.9rem, 1.4vw + 1.4rem, 2.8rem); font-weight: 800; letter-spacing: -0.03em; line-height: 1.06; margin: 0; }
.dpx-h2 { font-size: clamp(1.35rem, 0.8vw + 1.1rem, 1.8rem); font-weight: 800; letter-spacing: -0.02em; margin: 0; }
.dpx-lead { font-size: clamp(1rem, 0.3vw + 0.95rem, 1.12rem); color: var(--dpx-text-2); line-height: 1.55; margin: 0; }
.dpx-grad-ink { background: var(--dpx-grad-text); -webkit-background-clip: text; background-clip: text; color: transparent; }
.dpx-sec { padding: 42px 0; }
.dpx-sec-head { display: flex; align-items: flex-end; justify-content: space-between; gap: 18px; flex-wrap: wrap; margin-bottom: 22px; }

/* ---- buttons ---- */
.dpx-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  height: 46px; padding: 0 20px; border-radius: var(--dpx-r-sm); border: 1px solid transparent;
  font: 700 15px/1 var(--dpx-font); cursor: pointer; text-decoration: none; white-space: nowrap;
  transition: transform var(--dpx-dur-2) ease, box-shadow var(--dpx-dur-2) ease, background var(--dpx-dur-2) ease, border-color var(--dpx-dur-2) ease, color var(--dpx-dur-2) ease;
}
.dpx-btn svg { width: 18px; height: 18px; }
.dpx-btn-primary { background: var(--dpx-grad); color: #fff; box-shadow: var(--dpx-glow); }
.dpx-btn-primary:hover { transform: translateY(-2px); box-shadow: 0 22px 50px -14px rgba(15, 18, 26, 0.4); }
.dpx-btn-ghost { background: var(--dpx-surface); color: var(--dpx-text); border-color: var(--dpx-line); box-shadow: var(--dpx-sh-1); }
.dpx-btn-ghost:hover { border-color: var(--dpx-text); color: var(--dpx-text); transform: translateY(-1px); }
.dpx-btn-dark { background: var(--dpx-ink); color: var(--dpx-on-dark); box-shadow: var(--dpx-sh-2); }
.dpx-btn-dark:hover { transform: translateY(-2px); box-shadow: var(--dpx-sh-3); }
.dpx-btn-success { background: var(--dpx-grad-success); color: #fff; box-shadow: var(--dpx-glow-success); }
.dpx-btn-success:hover { transform: translateY(-2px); box-shadow: 0 20px 46px -16px rgba(16, 185, 129, 0.5); }
.dpx-btn-sm { height: 38px; padding: 0 15px; font-size: 13.5px; border-radius: var(--dpx-r-xs); }
.dpx-btn-lg { height: 52px; padding: 0 26px; font-size: 16px; }
.dpx-btn[disabled], .dpx-btn.is-disabled { opacity: 0.55; pointer-events: none; }

/* ---- glass utility + dark hero ---- */
.dpx-glass {
  background: rgba(255, 255, 255, 0.62); backdrop-filter: saturate(160%) blur(16px);
  -webkit-backdrop-filter: saturate(160%) blur(16px); border: 1px solid rgba(255, 255, 255, 0.7);
}
.dpx-hero {
  position: relative; overflow: hidden; border-radius: 28px; color: var(--dpx-on-dark);
  background:
    radial-gradient(120% 140% at 88% -10%, rgba(255, 255, 255, 0.06), transparent 55%),
    var(--dpx-grad-hero);
  box-shadow: var(--dpx-sh-3), inset 0 0 0 1px rgba(255, 255, 255, 0.08);
  padding: clamp(28px, 4vw, 56px);
}
.dpx-hero .dpx-kicker { color: var(--dpx-on-dark-2); }
.dpx-hero-glow { position: absolute; inset: 0; pointer-events: none; background: radial-gradient(40% 40% at 78% 20%, rgba(255, 255, 255, 0.05), transparent 70%); }

/* ---- chips (category / filter) ---- */
.dpx-chips { display: flex; gap: 9px; flex-wrap: wrap; }
.dpx-chip {
  display: inline-flex; align-items: center; gap: 7px; height: 36px; padding: 0 15px; border-radius: var(--dpx-pill);
  background: var(--dpx-surface); border: 1px solid var(--dpx-line); color: var(--dpx-text-2);
  font: 650 13.5px/1 var(--dpx-font); cursor: pointer; white-space: nowrap; transition: all var(--dpx-dur-2) ease; text-decoration: none;
}
.dpx-chip:hover { border-color: var(--dpx-text-2); color: var(--dpx-text); }
.dpx-chip.is-active { background: var(--dpx-blue-50); border-color: var(--dpx-blue); color: var(--dpx-blue-600); box-shadow: none; }
.dpx-chip .dpx-chip-n { font-size: 11.5px; opacity: 0.7; }

/* ---- badges (accent only) ---- */
.dpx-badge {
  display: inline-flex; align-items: center; gap: 5px; height: 24px; padding: 0 10px; border-radius: var(--dpx-pill);
  font: 800 11px/1 var(--dpx-font); letter-spacing: 0.02em; text-transform: uppercase;
  background: var(--dpx-surface-2); color: var(--dpx-text-2); box-shadow: inset 0 0 0 1px var(--dpx-line);
}
.dpx-badge svg { width: 13px; height: 13px; }
.dpx-badge--blue { background: #eaf1ff; color: #1d4ed8; box-shadow: inset 0 0 0 1px #cfe0ff; }
.dpx-badge--purple { background: #f3ecff; color: #6d28d9; box-shadow: inset 0 0 0 1px #e2d5ff; }
.dpx-badge--orange { background: #fff1e6; color: #c2410c; box-shadow: inset 0 0 0 1px #ffdcc2; }
.dpx-badge--red { background: #ffecec; color: #b91c1c; box-shadow: inset 0 0 0 1px #ffd2d2; }
.dpx-badge--yellow { background: #fff7e0; color: #a16207; box-shadow: inset 0 0 0 1px #ffe9ad; }
.dpx-badge--ink { background: rgba(255, 255, 255, 0.1); color: var(--dpx-on-dark); box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.18); }
.dpx-badge--green { background: var(--dpx-green-50); color: var(--dpx-green-700); box-shadow: inset 0 0 0 1px var(--dpx-green-100); }
.dpx-badge--gold { background: var(--dpx-gold-50); color: var(--dpx-gold-700); box-shadow: inset 0 0 0 1px #f0e0b8; }

/* ---- product grid + card ---- */
.dpx-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(266px, 1fr)); gap: 22px; }
.dpx-card {
  position: relative; display: flex; flex-direction: column; background: var(--dpx-surface);
  border: 1px solid var(--dpx-line); border-radius: var(--dpx-r); overflow: hidden; text-decoration: none; color: inherit;
  box-shadow: var(--dpx-sh-1); transition: transform var(--dpx-dur-3) cubic-bezier(0.4, 0, 0.2, 1), box-shadow var(--dpx-dur-3) ease, border-color var(--dpx-dur-3) ease;
}
.dpx-card:hover { transform: translateY(-4px); box-shadow: var(--dpx-sh-3); border-color: #d3d7de; }
.dpx-card-media { position: relative; aspect-ratio: 4 / 3; background: linear-gradient(135deg, #f1f3f5, #e6e8ec); overflow: hidden; }
.dpx-card-media img { width: 100%; height: 100%; object-fit: cover; display: block; transition: transform 0.4s ease; }
.dpx-card:hover .dpx-card-media img { transform: scale(1.045); }
.dpx-card-media-ph { position: absolute; inset: 0; display: grid; place-items: center; color: #aab0ba; }
.dpx-card-media-ph svg { width: 40px; height: 40px; opacity: 0.7; }
.dpx-card-tags { position: absolute; top: 12px; left: 12px; display: flex; gap: 6px; }
.dpx-card-fav {
  position: absolute; top: 10px; right: 10px; width: 34px; height: 34px; border-radius: 50%; border: 0; cursor: pointer;
  display: grid; place-items: center; color: var(--dpx-text); background: rgba(255, 255, 255, 0.82);
  backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px); box-shadow: var(--dpx-sh-1); transition: all var(--dpx-dur-2) ease;
}
.dpx-card-fav:hover { color: var(--dpx-red); transform: scale(1.06); }
.dpx-card-fav svg { width: 17px; height: 17px; }
.dpx-card-body { display: flex; flex-direction: column; gap: 7px; padding: 15px 16px 4px; flex: 1; }
.dpx-card-type { font-size: 11.5px; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase; color: var(--dpx-muted); }
/* Owner: hide the category/type label on digital-product cards. */
.dpx-card-type { display: none !important; }
.dpx-card-title { font-size: 15.5px; font-weight: 750; line-height: 1.32; color: var(--dpx-text); display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.dpx-card-foot { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 12px 16px 15px; margin-top: auto; border-top: 1px solid var(--dpx-line-2); }
.dpx-seller { display: inline-flex; align-items: center; gap: 8px; min-width: 0; text-decoration: none; color: var(--dpx-text-2); }
.dpx-seller-av { width: 24px; height: 24px; border-radius: 50%; object-fit: cover; background: var(--dpx-grad); color: #fff; display: grid; place-items: center; font: 800 10px/1 var(--dpx-font); flex: none; }
.dpx-seller-nm { font-size: 12.5px; font-weight: 650; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.dpx-price { display: flex; align-items: baseline; gap: 6px; flex: none; }
.dpx-price-now { font-family: var(--dpx-font-num); font-size: 17px; font-weight: 800; color: var(--dpx-text); }
.dpx-price-was { font-family: var(--dpx-font-num); font-size: 12.5px; color: var(--dpx-faint); text-decoration: line-through; }
.dpx-price-free { color: var(--dpx-green-700); }
.dpx-card-rating { display: inline-flex; align-items: center; gap: 4px; font-size: 12px; color: var(--dpx-text-2); font-weight: 650; }
.dpx-card-rating svg { width: 13px; height: 13px; color: var(--dpx-yellow); }

/* ---- skeleton ----
   The `.dpx-loader` spinner orb that used to live here is GONE (Phase 25), along with its
   `dpxSpin` keyframe and the `DPX.loader()` renderer. Standing rule: a loading state is a shaped
   skeleton in the real layout, never a spinner. It had exactly one live call site - the seller
   store's quick preview - which now draws its own skeleton in the preview's real two-column
   geometry. Deleting the primitive rather than leaving it unused is deliberate: an available
   spinner is an invitation to use one.
   The one surviving spinner in the module is `.dpx-co-proc` in digital-checkout.css, and that is
   correct - payment processing is genuinely indeterminate with no layout to shape. */

.dpx-skel { background: linear-gradient(90deg, #eef0f3 25%, #f7f8fa 50%, #eef0f3 75%); background-size: 480px 100%; animation: dpxShimmer 1.25s linear infinite; border-radius: var(--dpx-r-xs); }
@keyframes dpxShimmer { 0% { background-position: -240px 0; } 100% { background-position: 240px 0; } }
.dpx-sk-card { border: 1px solid var(--dpx-line); border-radius: var(--dpx-r); overflow: hidden; background: var(--dpx-surface); }
.dpx-sk-card .dpx-sk-media { aspect-ratio: 4 / 3; }
.dpx-sk-card .dpx-sk-body { padding: 15px 16px 18px; display: flex; flex-direction: column; gap: 10px; }
.dpx-sk-line { height: 12px; border-radius: var(--dpx-r1); }

/* ---- toast ----
   Lives HERE, in the file every DP page loads, because DPX.toastLite() is part of the shared
   component library and any page that can call it must be able to style it. These rules used to
   exist only in seller-dp-products.css and seller-store.css, so the four pages that load neither
   (coupons, customers, orders, analytics) appended a bare, unpositioned div to <body> on every
   toast. dpx-night.css re-skins these under body.dpx-night and still loads later, so the dark
   public pages keep their glass treatment. */
/* ---- focus floor (Phase 25) ----
   43 stylesheets in this repo contain `:focus-visible` and every single rule is scoped to one
   component - there was no baseline anywhere, so any DP control nobody thought about had no
   keyboard focus indication at all. This is the floor, and it has to LOSE to everything that
   was designed: 22 DP rules deliberately drop the outline and paint their own border+shadow
   ring instead, and a floor that outranked them would show both at once on every select and
   search box in the module.
   Hence `:where()` on BOTH compounds - it matches identically but contributes zero specificity,
   so the whole selector is (0,1,0) (just `:focus-visible`) and any real rule wins. Containment
   still works: `:where(body.dpx)` matches the class that is on all 27 DP pages and nowhere else.
   `:is()` would have been (0,3,1) here, because `:is()` takes the HIGHEST specificity among its
   arguments and `[tabindex]` is an attribute selector.
   No `border-radius` here: an outline already follows the element's own radius, and setting one
   would round a square control the moment it took focus. */
:where(body.dpx) :where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: 2px solid var(--dpx-blue);
  outline-offset: 2px;
}
/* The night skin's accent is violet; the blue would read as a foreign colour on it. Same
   specificity as the rule above, so this wins on source order, which is why it sits here. */
:where(body.dpx-night) :where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline-color: #8b5cf6;
}

/* Off-screen but still read aloud. DP had no visually-hidden class of its own, and the
   freelancing sheets that do are out of scope for this phase. `DPX.announce()` mounts its single
   polite live region with this class. Not `display:none` / `visibility:hidden` - both remove the
   node from the accessibility tree, which would make the region silent. */
.dpx-sr-only {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ---- broken + loading images (Phase 25) ----
   dpx-components.js swaps a 404 image's src for a 1x1 transparent pixel and adds .dpx-img-broken,
   so the element keeps its box (and its own border-radius, which is why a round avatar stays
   round) and the placeholder comes from here instead of the browser's broken-image glyph.
   The glyph is the same rect+mountain used by .dpx-card-media-ph, inlined because a background
   image cannot resolve currentColor - hence the separate night-skin stroke below. */
body.dpx .dpx-img-broken {
  background-color: var(--dpx-surface-2, #f1f3f7);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23aab0ba' stroke-width='1.6'%3E%3Crect x='3' y='3' width='18' height='18' rx='3'/%3E%3Ccircle cx='8.5' cy='8.5' r='1.6'/%3E%3Cpath d='M21 15l-5-5L5 21'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: center;
  background-size: 38%;
  object-fit: cover;
}
body.dpx-night .dpx-img-broken {
  background-color: #141a2b;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2339415a' stroke-width='1.6'%3E%3Crect x='3' y='3' width='18' height='18' rx='3'/%3E%3Ccircle cx='8.5' cy='8.5' r='1.6'/%3E%3Cpath d='M21 15l-5-5L5 21'/%3E%3C/svg%3E");
}
/* Blur-up. digital-cart.css had the only copy of this; this is the shared one. It is OPT-IN via
   `.dpx-blur` rather than keying on `is-loading` alone: a bare `body.dpx img.is-loading` rule
   would also hand a transition to every other image on 24 pages, which would quietly re-time any
   hover-scale that relied on having none. An image opts in with class="dpx-blur is-loading" and
   DPX.wireImages() drops `is-loading` on load. Cart and library keep their own older local rules;
   those still work and are left alone. */
body.dpx img.dpx-blur { transition: filter var(--dpx-dur-3) var(--dpx-ease-out), transform var(--dpx-dur-3) var(--dpx-ease-out); }
body.dpx img.dpx-blur.is-loading { filter: blur(14px); transform: scale(1.06); }
@media (prefers-reduced-motion: reduce) {
  /* A blur that resolves is motion. Under reduce, show the image plainly the moment it lands. */
  body.dpx img.dpx-blur { transition: none; }
  body.dpx img.dpx-blur.is-loading { filter: none; transform: none; }

  /* fp-upload's progress bar runs `animation: fpuSheen 1.5s linear infinite` on .fpu-fill, and
     that rule is INJECTED at runtime by fpuInjectStyle() with no media query at all - so an
     upload sheens forever even for someone who asked for reduced motion. (fp-upload.css's own
     reduced-motion block covers `.fp-up-*`, the dropzone, which is a different component.)
     Fixed here rather than in fp-upload.js because that file is on 14 freelancing pages and this
     phase does not touch them. `body.dpx .fpu-fill` is (0,2,1) against the injected rule's
     (0,1,0), so it wins regardless of the injected <style> coming later in the head.
     COVERS: the 11 DP pages that load fp-upload. The other 14 still sheen - that is a one-line
     change in fp-upload.js and needs an explicit decision to touch freelancing. */
  body.dpx .fpu-fill { animation: none; }
}

/* The collapsed-sidebar hover peek used to live here, scoped to body.dpx. It moved to
   editorial.css (search "peek open") once the owner approved it site-wide, because that is where
   the collapsed rail is styled and a second copy here would drift from it. The dark skin for the
   peeked rail is in dpx-night.css section 12, which already darkens .fp-side and its children. */

/* ---- contrast + forced colours floor (Phase 25) ----
   Only four stylesheets in the whole repo had `prefers-contrast` and two had `forced-colors`,
   and every one of those blocks was scoped to a single recently-added widget - the product grid,
   cards, cart, checkout, library and every seller page had none. Same `body.dpx` containment. */
@media (prefers-contrast: more) {
  body.dpx :is(.dpx-card, .dpx-empty, .dpx-input, .dpx-select, .dpx-textarea) { border-width: 2px; }
  body.dpx :is(.dpx-muted, .dpx-faint) { color: var(--dpx-text-2); }
  body.dpx .dpx-btn { border: 1px solid currentColor; }
}
@media (forced-colors: active) {
  /* Anything that carries meaning through background alone disappears here, so give it an edge. */
  body.dpx :is(.dpx-card, .dpx-empty, .dpx-toast, .dpx-chip, .dpx-btn) { border: 1px solid CanvasText; }
  body.dpx .dpx-skel { forced-color-adjust: none; background: Canvas; border: 1px solid GrayText; }
}

/* ---- form fields (Phase 25) ----
   The module shipped 24 phases with NO generic input primitive. `.ed-field` is the freelancing
   system and `.adm-form-*` is admin, so neither is available here; DP's only input rules were
   `.dpx-rvm-in` / `-ta`, scoped to the review composer. Every DP page therefore invented its own
   field classes, which is exactly why the forms drifted.
   The metrics are taken from what the module already uses rather than invented: the default is
   the denser seller-dashboard field (13px / 40px, the shape `seller-dp-coupons` settled on, since
   the form-heavy surfaces are the seller ones), and `.is-lg` is the roomier buyer size the review
   composer uses (14.5px / 46px). Focus is the blue ring the file header calls the interactive
   accent, not the composer's purple, which is the night skin's accent leaking into a light form.
   Adopt page by page; nothing is rewritten wholesale. */
.dpx-field { display: flex; flex-direction: column; gap: 5px; }
.dpx-label { font-size: 12px; font-weight: 700; color: var(--dpx-text); }
.dpx-label .dpx-req { color: #b42318; margin-left: 2px; }
.dpx-input, .dpx-select, .dpx-textarea {
  width: 100%; box-sizing: border-box; min-height: 40px; padding: 9px 11px;
  font: 600 13px/1.4 var(--dpx-font); color: var(--dpx-text);
  background: var(--dpx-surface); border: 1px solid var(--dpx-line);
  border-radius: var(--dpx-r3);
  transition: border-color var(--dpx-dur-2) ease, box-shadow var(--dpx-dur-2) ease;
}
.dpx-textarea { min-height: 74px; resize: vertical; line-height: 1.5; }
/* NO chevron and NO padding-right here on purpose. `theme-lancr.css` already gives every select on
   the platform one, with `appearance`, `background-image`, `background-position` and
   `padding-right: 36px` all marked `!important` - so a second arrow drawn here would lose the
   fight anyway, and winning it with `!important` of our own would give the Digital Products
   selects a different arrow from every other select on the site. Its remaining declarations are
   deliberately NOT important and carry the comment "page select rules override these", which is
   the hook this rule uses.
   `body.dpx` (0,2,0) out-specifies `.fp-ed select` (0,1,1); digital-products.css loads before
   theme-lancr.css, so source order alone would not be enough. The only thing worth overriding is
   the height and the corner: theme-lancr's baseline is 42px with an 11px radius, which left a DP
   select 2px taller than the input beside it and 1px rounder. `border-radius` has to be restated
   here rather than inherited from the grouped rule above, because that rule is (0,1,0) and also
   loses to `.fp-ed select`. */
body.dpx .dpx-select { min-height: 40px; cursor: pointer; border-radius: var(--dpx-r3); }
.dpx-input::placeholder, .dpx-textarea::placeholder { color: var(--dpx-faint); font-weight: 500; }
.dpx-input:focus, .dpx-select:focus, .dpx-textarea:focus {
  outline: none; border-color: var(--dpx-blue); box-shadow: var(--dpx-blue-ring);
}
.dpx-input:disabled, .dpx-select:disabled, .dpx-textarea:disabled {
  background: var(--dpx-surface-2); color: var(--dpx-muted); cursor: not-allowed;
}
/* Invalid state carries a border AND a message - colour alone is not a signal. */
.dpx-input.is-bad, .dpx-select.is-bad, .dpx-textarea.is-bad { border-color: #b42318; }
.dpx-input.is-bad:focus, .dpx-select.is-bad:focus, .dpx-textarea.is-bad:focus { box-shadow: 0 0 0 3px rgba(180, 35, 24, 0.16); }
.dpx-hint { font-size: 11.5px; font-weight: 600; color: var(--dpx-muted); }
.dpx-err { font-size: 11.5px; font-weight: 700; color: #b42318; }
.dpx-err:empty { display: none; }
/* The roomier buyer-side size (review composer, public forms). */
.dpx-field.is-lg .dpx-input, .dpx-field.is-lg .dpx-select, .dpx-field.is-lg .dpx-textarea {
  min-height: 46px; padding: 12px 14px; font-size: 14.5px; line-height: 1.6; border-radius: var(--dpx-r4);
  background: var(--dpx-surface-2);
}
.dpx-field.is-lg .dpx-textarea { min-height: 132px; }

.dpx-toast-wrap {
  position: fixed; left: 50%; bottom: 22px; transform: translateX(-50%); z-index: 1300;
  display: flex; flex-direction: column; align-items: center; gap: 8px; pointer-events: none;
}
.dpx-toast {
  padding: 11px 18px; border-radius: var(--dpx-r4); background: var(--dpx-ink, #0d0f13); color: #f3f4f6;
  font-weight: 600; font-size: 13.5px; box-shadow: 0 14px 34px -12px rgba(9, 11, 15, 0.55);
  opacity: 0; transform: translateY(10px); transition: opacity var(--dpx-dur-3), transform var(--dpx-dur-3); max-width: 90vw;
}
.dpx-toast.is-in { opacity: 1; transform: translateY(0); }
.dpx-toast.is-out { opacity: 0; }
.dpx-toast--ok { background: linear-gradient(135deg, #12c98c 0%, #059669 60%, #047857 100%); }
.dpx-toast--err { background: linear-gradient(135deg, #ef4444, #b91c1c); }

/* Toast actions (Phase 25). The wrap is `pointer-events: none` on purpose so a toast never
   swallows a click meant for the page underneath - but that also made every action inside one
   UNCLICKABLE. Measured: the cart's Undo button computed `pointer-events: none`,
   elementFromPoint at its centre returned the link BEHIND the toast, and the button received
   zero clicks. Four pages shipped a dead action this way (cart Undo, library, product detail,
   browse). Only a toast that actually has an action opts back in, so the no-action case keeps
   the click-through behaviour the wrap was written for. */
.dpx-toast.has-act { pointer-events: auto; }
.dpx-toast-act {
  margin-left: 10px; border: 0; background: transparent; color: inherit;
  font: inherit; font-weight: 800; text-decoration: underline; cursor: pointer;
  padding: 0; border-radius: var(--dpx-r1);
}
.dpx-toast-act:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }

/* ---- elegant empty / error state ---- */
.dpx-empty {
  display: grid; place-items: center; text-align: center; gap: 12px; padding: 66px 24px;
  border: 1px dashed var(--dpx-line); border-radius: var(--dpx-r); background: linear-gradient(180deg, #fbfbfc, var(--dpx-surface));
}
.dpx-empty-ic {
  width: 66px; height: 66px; border-radius: var(--dpx-r7); display: grid; place-items: center; color: var(--dpx-muted);
  background: var(--dpx-surface-2); box-shadow: inset 0 0 0 1px var(--dpx-line);
}
.dpx-empty-ic svg { width: 30px; height: 30px; }
.dpx-empty h3 { margin: 4px 0 0; font-size: 1.15rem; font-weight: 800; color: var(--dpx-text); }
.dpx-empty p { margin: 0; max-width: 42ch; color: var(--dpx-muted); font-size: 0.95rem; line-height: 1.55; }

/* ---- reveal-on-scroll ---- */
.dpx-rv { opacity: 0; transform: translateY(14px); transition: opacity 0.5s ease, transform 0.5s cubic-bezier(0.4, 0, 0.2, 1); }
.dpx-rv.is-in { opacity: 1; transform: none; }

/* ---- seller store header ---- */
.dp-store-head { position: relative; display: flex; align-items: center; gap: 20px; flex-wrap: wrap; padding: 24px 24px 24px 28px; border-radius: var(--dpx-r8); background: var(--dpx-surface); border: 1px solid var(--dpx-line); box-shadow: var(--dpx-sh-2); overflow: hidden; }
.dp-store-head::before { content: ""; position: absolute; inset: 0 auto 0 0; width: 5px; background: var(--dpx-grad); }
.dp-store-av { width: 80px; height: 80px; border-radius: var(--dpx-r8); flex: none; overflow: hidden; background: var(--dpx-grad); color: #fff; display: grid; place-items: center; font: 800 30px/1 var(--dpx-font); box-shadow: var(--dpx-glow); }
.dp-store-av img { width: 100%; height: 100%; object-fit: cover; }
.dp-store-id { flex: 1; min-width: 200px; }
.dp-store-meta { margin-top: 6px; font-size: 14px; color: var(--dpx-muted); font-weight: 600; }
.dp-store-id p { margin: 8px 0 0; color: var(--dpx-text-2); font-size: 14.5px; line-height: 1.5; max-width: 60ch; }
.dp-store-actions { display: flex; gap: 10px; flex-wrap: wrap; }

/* ---- product detail ---- */
.dp-detail { display: grid; grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr); gap: 32px; align-items: start; }
.dp-gallery { border-radius: var(--dpx-r8); overflow: hidden; border: 1px solid var(--dpx-line); background: var(--dpx-surface); box-shadow: var(--dpx-sh-2); }
.dp-gallery-main { aspect-ratio: 16 / 10; background: linear-gradient(135deg, #f1f3f5, #e6e8ec); display: grid; place-items: center; overflow: hidden; }
.dp-gallery-main img { width: 100%; height: 100%; object-fit: cover; }
.dp-gallery-main .dpx-card-media-ph svg { width: 54px; height: 54px; }
.dp-buybox { position: sticky; top: 88px; border: 1px solid var(--dpx-line); border-radius: var(--dpx-r8); background: var(--dpx-surface); box-shadow: var(--dpx-sh-2); padding: 24px; display: flex; flex-direction: column; gap: 16px; }
.dp-buybox-price { display: flex; align-items: baseline; gap: 10px; }
.dp-buybox-price .dpx-price-now { font-size: 30px; }
.dp-meta-list { display: grid; }
.dp-meta-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; font-size: 13.5px; padding: 10px 0; border-top: 1px solid var(--dpx-line-2); }
.dp-meta-row:first-child { border-top: 0; }
.dp-meta-row span:first-child { color: var(--dpx-muted); }
.dp-meta-row span:last-child { font-weight: 700; color: var(--dpx-text); text-align: right; }

/* ---- responsive ---- */
@media (max-width: 900px) {
  .dp-detail { grid-template-columns: 1fr; gap: 22px; }
  .dp-buybox { position: static; }
}
@media (max-width: 560px) {
  .dp-store-head { padding: 18px; gap: 14px; }
  .dp-store-av { width: 64px; height: 64px; border-radius: var(--dpx-r7); font-size: 24px; }
}
@media (max-width: 1000px) {
  .dpx-grid { grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 18px; }
  .dpx-sec { padding: 34px 0; }
}
@media (max-width: 720px) {
  .dpx-wrap { padding: 0 16px; }
  .dpx-grid { grid-template-columns: repeat(auto-fill, minmax(158px, 1fr)); gap: 14px; }
  .dpx-hero { border-radius: var(--dpx-r8); }
  .dpx-card-body { padding: 12px 13px 4px; }
  .dpx-card-foot { padding: 10px 13px 13px; }
}
@media (max-width: 420px) {
  .dpx-grid { grid-template-columns: 1fr 1fr; gap: 12px; }
}
@media (prefers-reduced-motion: reduce) {
  .dpx-rv { transition: none; opacity: 1; transform: none; }
  .dpx-skel, .dpx-card, .dpx-card-media img { animation: none; transition: none; }
  /* The toast still has to be VISIBLE with motion off, so kill the transition but keep the
     end state - dropping the transform without this would leave it parked 10px low at opacity 0. */
  .dpx-toast { transition: none; transform: none; }
  .dpx-toast.is-in { opacity: 1; transform: none; }
}

/* ============================================================================
   Reusable component library (rendered by assets/js/dpx-components.js / window.DPX)
   ============================================================================ */

/* icon buttons (wishlist / share) */
.dpx-icon-btn {
  width: 34px; height: 34px; border-radius: 50%; border: 0; cursor: pointer; display: grid; place-items: center;
  color: var(--dpx-text); background: rgba(255, 255, 255, 0.82); backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
  box-shadow: var(--dpx-sh-1); transition: transform var(--dpx-dur-2) ease, color var(--dpx-dur-2) ease, background var(--dpx-dur-2) ease;
}
.dpx-icon-btn svg { width: 17px; height: 17px; }
.dpx-icon-btn:hover { transform: scale(1.08); }
.dpx-wishlist:hover, .dpx-wishlist.is-on { color: var(--dpx-red); }
.dpx-wishlist.is-on svg { fill: var(--dpx-red); }
.dpx-card .dpx-wishlist { position: absolute; top: 10px; right: 10px; }

/* seller card */
.dpx-seller-card { padding: 18px; }
.dpx-seller-card-top { display: flex; align-items: center; gap: 12px; }
.dpx-seller-av-lg { width: 52px; height: 52px; border-radius: var(--dpx-r6); flex: none; overflow: hidden; background: var(--dpx-grad); color: #fff; display: grid; place-items: center; font: 800 18px/1 var(--dpx-font); box-shadow: var(--dpx-glow); }
.dpx-seller-av-lg img { width: 100%; height: 100%; object-fit: cover; }
.dpx-seller-card-handle { font-size: 12.5px; color: var(--dpx-muted); font-weight: 600; }
.dpx-seller-card-tag { margin: 12px 0 0; font-size: 13px; color: var(--dpx-text-2); line-height: 1.5; }

/* review */
.dpx-review { border: 1px solid var(--dpx-line); border-radius: var(--dpx-r6); background: var(--dpx-surface); padding: 16px 18px; box-shadow: var(--dpx-sh-1); }
.dpx-review-head { display: flex; align-items: center; gap: 11px; }
.dpx-review-nm { font-weight: 700; font-size: 14px; color: var(--dpx-text); }
.dpx-review-stars { display: flex; align-items: center; gap: 2px; margin-top: 2px; }
.dpx-review-stars span svg { width: 13px; height: 13px; color: #d8dee6; }
.dpx-review-stars span.is-on svg { color: var(--dpx-yellow); }
.dpx-review-stars time { margin-left: 8px; font-size: 12px; color: var(--dpx-faint); font-weight: 500; }
.dpx-review-body { margin: 11px 0 0; font-size: 14px; color: var(--dpx-text-2); line-height: 1.6; }

/* generic row card (orders / downloads) */
.dpx-row-card { display: flex; align-items: center; gap: 14px; border: 1px solid var(--dpx-line); border-radius: var(--dpx-r6); background: var(--dpx-surface); padding: 12px 15px; box-shadow: var(--dpx-sh-1); transition: border-color var(--dpx-dur-2) ease, box-shadow var(--dpx-dur-2) ease; }
.dpx-row-card:hover { border-color: #d3d7de; box-shadow: var(--dpx-sh-2); }
.dpx-row-media { width: 52px; height: 52px; border-radius: var(--dpx-r4); flex: none; overflow: hidden; background: linear-gradient(135deg, #f1f3f5, #e6e8ec); display: grid; place-items: center; color: #aab0ba; }
.dpx-row-media img { width: 100%; height: 100%; object-fit: cover; }
.dpx-row-media svg { width: 22px; height: 22px; }
.dpx-row-main { flex: 1; min-width: 0; }
.dpx-row-meta { font-size: 12.5px; color: var(--dpx-muted); margin-top: 3px; }
.dpx-row-end { flex: none; margin-left: auto; display: flex; align-items: center; gap: 10px; }

/* version + coupon */
.dpx-version { border: 1px solid var(--dpx-line); border-left: 3px solid var(--dpx-line); border-radius: var(--dpx-r4); background: var(--dpx-surface); padding: 12px 15px; }
.dpx-version-head { display: flex; align-items: center; gap: 10px; font-size: 14px; color: var(--dpx-text); }
.dpx-version-head time { color: var(--dpx-faint); font-size: 12px; font-weight: 500; margin-left: auto; }
.dpx-version-notes { margin: 8px 0 0; font-size: 13px; color: var(--dpx-text-2); line-height: 1.55; }
.dpx-coupon { display: flex; align-items: center; gap: 13px; border: 1px dashed var(--dpx-blue); border-radius: var(--dpx-r5); background: var(--dpx-blue-50); padding: 13px 16px; }
.dpx-coupon-ic { color: var(--dpx-blue-600); }
.dpx-coupon-ic svg { width: 22px; height: 22px; }
.dpx-coupon-code { font-family: var(--dpx-font-num); font-weight: 800; letter-spacing: 0.04em; color: var(--dpx-blue-600); }

/* stat card */
.dpx-stat { border: 1px solid var(--dpx-line); border-radius: var(--dpx-r6); background: var(--dpx-surface); padding: 16px 18px; box-shadow: var(--dpx-sh-1); }
.dpx-stat-val { font-family: var(--dpx-font-num); font-size: 1.7rem; font-weight: 700; color: var(--dpx-text); letter-spacing: -0.01em; }
.dpx-stat-lbl { font-size: 0.8rem; font-weight: 600; color: var(--dpx-muted); margin-top: 2px; }
.dpx-stat-sub { font-size: 0.76rem; color: var(--dpx-muted); font-weight: 700; margin-top: 4px; }
.dpx-stat--blue { border-color: #cfe0ff; } .dpx-stat--blue .dpx-stat-val { color: var(--dpx-blue); }
.dpx-stat--green .dpx-stat-val { color: var(--dpx-green-700); }
.dpx-stat--orange .dpx-stat-val { color: var(--dpx-orange); }

/* count-up target (animated number - DPX.cntSpan / DPX.countUp) */
.dpx-cnt { font-variant-numeric: tabular-nums; font-feature-settings: "tnum" 1; }

/* circular progress ring (DPX.ring) */
.dpx-ring { position: relative; width: 92px; max-width: 100%; margin: 0 auto; }
.dpx-ring svg { width: 100%; height: auto; display: block; }
.dpx-ring-bg { fill: none; stroke: var(--dpx-line); stroke-width: 8; }
.dpx-ring-fg { fill: none; stroke: url(#dpx-ring-grad); stroke-width: 8; stroke-linecap: round; transition: stroke-dashoffset 0.9s cubic-bezier(0.2, 0.7, 0.2, 1); }
.dpx-ring-top { text-anchor: middle; font-family: var(--dpx-font-num); font-weight: 800; font-size: 18px; fill: var(--dpx-text); }
.dpx-ring-sub { text-anchor: middle; font-family: var(--dpx-font); font-weight: 700; font-size: 11px; letter-spacing: 0.06em; text-transform: uppercase; fill: var(--dpx-muted); }

/* stacked breakdown bar + legend (DPX.breakdownBar) */
.dpx-breakdown { display: flex; height: 12px; border-radius: var(--dpx-pill); overflow: hidden; background: var(--dpx-line); }
.dpx-breakdown span { display: block; height: 100%; transition: width 0.6s ease; }
.dpx-seg-green { background: var(--dpx-green); }
.dpx-seg-slate { background: #64748b; }
.dpx-seg-amber { background: #f59e0b; }
.dpx-seg-blue { background: #3b82f6; }
.dpx-seg-gray { background: #9ca3af; }
.dpx-seg-orange { background: #f97316; }
.dpx-seg-red { background: #ef4444; }
.dpx-bd-legend { display: flex; flex-wrap: wrap; gap: 10px 16px; margin-top: 12px; font-size: 12.5px; }
.dpx-bd-item { display: inline-flex; align-items: center; gap: 6px; color: var(--dpx-muted); font-weight: 600; }
.dpx-bd-item b { color: var(--dpx-text); font-weight: 800; }
.dpx-bd-dot { width: 9px; height: 9px; border-radius: 3px; display: inline-block; flex: none; }

/* growth pill (DPX.growthPill) */
.dpx-growth { display: inline-flex; align-items: center; gap: 3px; font-size: 12px; font-weight: 650; border-radius: var(--dpx-pill); padding: 2px 8px; }
.dpx-growth svg { width: 10px; height: 10px; }
.dpx-growth.is-up { color: #0f7a48; background: rgba(16, 185, 129, 0.12); }
.dpx-growth.is-down { color: #c02626; background: rgba(239, 68, 68, 0.12); }
.dpx-growth.is-flat { color: var(--dpx-muted); background: rgba(0, 0, 0, 0.05); }

/* skeleton variants */
.dpx-sk-row { display: flex; align-items: center; gap: 14px; border: 1px solid var(--dpx-line); border-radius: var(--dpx-r6); background: var(--dpx-surface); padding: 12px 15px; }
.dpx-sk-stat { border: 1px solid var(--dpx-line); border-radius: var(--dpx-r6); background: var(--dpx-surface); padding: 16px 18px; display: grid; gap: 10px; }

/* quick-preview popup body */
.dpx-qp { display: grid; gap: 14px; }
.dpx-qp .dpx-gallery-main { border-radius: var(--dpx-r6); }

/* success pop (micro-interaction) */
@keyframes dpxPop { 0% { transform: scale(0.6); opacity: 0; } 60% { transform: scale(1.08); } 100% { transform: scale(1); opacity: 1; } }
.dpx-pop { animation: dpxPop 0.32s var(--dpx-ease-spring); }

@media (prefers-reduced-motion: reduce) {
  .dpx-icon-btn, .dpx-row-card, .dpx-pop { animation: none; transition: none; }
  .dpx-ring-fg, .dpx-breakdown span { transition: none; }
}

/* ---- seller store: sub-nav + panes ---- */
.dpx-subnav { display: flex; gap: 2px; overflow-x: auto; border-bottom: 1px solid var(--dpx-line); scrollbar-width: none; }
.dpx-subnav::-webkit-scrollbar { display: none; }
.dpx-subnav-tab { flex: none; padding: 13px 16px; font: 700 14px/1 var(--dpx-font); color: var(--dpx-muted); background: none; border: 0; border-bottom: 2px solid transparent; cursor: pointer; white-space: nowrap; transition: color var(--dpx-dur-2) ease; text-decoration: none; }
.dpx-subnav-tab:hover { color: var(--dpx-text); }
.dpx-subnav-tab.is-active { color: var(--dpx-blue-600); border-bottom-color: var(--dpx-blue); }
.dpx-subnav-tab .dpx-subnav-n { font-size: 11px; opacity: 0.6; margin-left: 5px; }
.dpx-store-meta { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 8px; font-size: 13px; color: var(--dpx-muted); font-weight: 600; }
.dpx-store-meta .dpx-card-rating svg { color: var(--dpx-yellow); }
.dpx-store-meta .dot { color: var(--dpx-faint); }
.dpx-prose { max-width: 68ch; color: var(--dpx-text-2); font-size: 15px; line-height: 1.7; }
.dpx-prose h3 { color: var(--dpx-text); font-size: 1.05rem; font-weight: 800; margin: 22px 0 8px; }
.dpx-prose h3:first-child { margin-top: 0; }
.dpx-collection-card { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 16px 18px; border: 1px solid var(--dpx-line); border-radius: var(--dpx-r6); background: var(--dpx-surface); box-shadow: var(--dpx-sh-1); text-decoration: none; color: inherit; transition: border-color var(--dpx-dur-2) ease, transform var(--dpx-dur-2) ease; cursor: pointer; }
.dpx-collection-card:hover { border-color: #d3d7de; transform: translateY(-1px); }
.dpx-collection-card b { font-size: 15px; color: var(--dpx-text); }
.dpx-collection-card span { font-size: 12.5px; color: var(--dpx-muted); font-weight: 600; }


/* ==========================================================================
   REVIEW COMPOSER (DPX.reviewModal) - shared by the product page and the buyer
   library. One labelled field per row so the eye has a single left edge to
   follow; every control on the same 44px rhythm. FPModal is already dark-themed
   by dpx-night.css, so this only describes the composer's own parts.
   ========================================================================== */
.dpx-rvm { display: flex; flex-direction: column; gap: 18px; text-align: left; }
.dpx-rvm-sub {
  margin: -2px 0 0; font-size: 13.5px; font-weight: 600; color: var(--dpx-muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Every field is label-over-control. The label is the alignment spine. */
.dpx-rvm-field { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
.dpx-rvm-lab {
  display: flex; align-items: baseline; gap: 7px;
  font-size: 11.5px; font-weight: 800; letter-spacing: 0.07em; text-transform: uppercase;
  color: var(--dpx-faint);
}
.dpx-rvm-lab em { font-style: normal; font-size: 11px; font-weight: 600; letter-spacing: 0.02em; text-transform: none; opacity: 0.85; }

.dpx-rvm-raterow { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.dpx-rvm-stars { display: flex; gap: 8px; }
.dpx-rvm-star {
  width: 46px; height: 46px; display: grid; place-items: center; cursor: pointer;
  border: 1px solid var(--dpx-line); border-radius: var(--dpx-r4); background: var(--dpx-surface-2);
  color: var(--dpx-faint); transition: transform var(--dpx-dur-2) ease, color var(--dpx-dur-2) ease, border-color var(--dpx-dur-2) ease, background var(--dpx-dur-2) ease;
}
.dpx-rvm-star svg { width: 22px; height: 22px; }
.dpx-rvm-star:hover { transform: translateY(-2px); }
.dpx-rvm-star.is-on { color: #fbbf24; border-color: rgba(251, 191, 36, 0.45); background: rgba(251, 191, 36, 0.13); }
.dpx-rvm-star.is-on svg { fill: currentColor; }
.dpx-rvm-word { font-size: 14px; font-weight: 700; color: var(--dpx-text); }

.dpx-rvm-in, .dpx-rvm-ta {
  width: 100%; box-sizing: border-box; font: inherit; font-size: 14.5px; line-height: 1.6;
  color: var(--dpx-text); background: var(--dpx-surface-2);
  border: 1px solid var(--dpx-line); border-radius: var(--dpx-r4); padding: 12px 14px;
  transition: border-color var(--dpx-dur-2) ease, box-shadow var(--dpx-dur-2) ease;
}
.dpx-rvm-in { min-height: 46px; }
.dpx-rvm-ta { min-height: 132px; resize: vertical; }
.dpx-rvm-in::placeholder, .dpx-rvm-ta::placeholder { color: var(--dpx-faint); }
.dpx-rvm-in:focus, .dpx-rvm-ta:focus {
  outline: none; border-color: #8b5cf6; box-shadow: 0 0 0 3px rgba(139, 92, 246, 0.22);
}

.dpx-rvm-shots { display: flex; flex-wrap: wrap; gap: 10px; }
.dpx-rvm-shot { position: relative; width: 96px; height: 96px; border-radius: var(--dpx-r4); overflow: hidden; border: 1px solid var(--dpx-line); }
.dpx-rvm-shot img { width: 100%; height: 100%; object-fit: cover; display: block; }
.dpx-rvm-shotx {
  position: absolute; top: 5px; right: 5px; width: 26px; height: 26px; border: 0; cursor: pointer;
  border-radius: var(--dpx-r2); display: grid; place-items: center; color: #fff; background: rgba(9, 11, 18, 0.74);
}
.dpx-rvm-shotx svg { width: 13px; height: 13px; }
.dpx-rvm-add {
  width: 96px; height: 96px; cursor: pointer; display: flex; flex-direction: column; align-items: center;
  justify-content: center; gap: 7px; padding: 8px; font: inherit;
  border: 1px dashed var(--dpx-line); border-radius: var(--dpx-r4); background: transparent; color: var(--dpx-muted);
  transition: border-color var(--dpx-dur-2) ease, color var(--dpx-dur-2) ease;
}
.dpx-rvm-add:hover { border-color: #8b5cf6; color: var(--dpx-text-2); }
.dpx-rvm-add svg { width: 20px; height: 20px; }
.dpx-rvm-add span { font-size: 12px; font-weight: 700; }

.dpx-rvm-yns { display: flex; gap: 9px; }
.dpx-rvm-yn {
  min-height: 42px; min-width: 84px; padding: 0 20px; cursor: pointer; font: inherit; font-size: 14px; font-weight: 700;
  border: 1px solid var(--dpx-line); border-radius: var(--dpx-r4); background: var(--dpx-surface-2); color: var(--dpx-text-2);
  transition: border-color var(--dpx-dur-2) ease, background var(--dpx-dur-2) ease, color var(--dpx-dur-2) ease;
}
.dpx-rvm-yn:hover { color: var(--dpx-text); }
.dpx-rvm-yn.is-on { border-color: rgba(52, 211, 153, 0.5); background: rgba(52, 211, 153, 0.15); color: #34d399; }

.dpx-rvm-guide {
  display: flex; flex-direction: column; gap: 7px;
  padding: 14px 16px; border-radius: var(--dpx-r4);
  border: 1px solid var(--dpx-line-2); background: rgba(255, 255, 255, 0.022);
}
.dpx-rvm-guide ul { margin: 0; padding-left: 17px; display: flex; flex-direction: column; gap: 4px; }
.dpx-rvm-guide li { font-size: 13px; line-height: 1.55; color: var(--dpx-muted); }
.dpx-rvm-note { margin: 0; font-size: 13px; line-height: 1.6; color: var(--dpx-muted); }
.dpx-rvm-confirm {
  display: flex; align-items: flex-start; gap: 11px; cursor: pointer;
  font-size: 13.5px; line-height: 1.55; color: var(--dpx-text-2);
}
.dpx-rvm-confirm input { margin: 1px 0 0; width: 18px; height: 18px; flex: none; accent-color: #8b5cf6; cursor: pointer; }

.dpx-rvm-done { display: flex; flex-direction: column; align-items: center; gap: 14px; text-align: center; padding: 8px 0 2px; }
.dpx-rvm-done-ic {
  width: 66px; height: 66px; border-radius: 50%; display: grid; place-items: center; color: #fff;
  background: linear-gradient(135deg, #34d399, #10b981); box-shadow: 0 0 36px -6px rgba(52, 211, 153, 0.6);
  animation: dpxRvPop 0.42s var(--dpx-ease-spring);
}
.dpx-rvm-done-ic svg { width: 32px; height: 32px; }
.dpx-rvm-done p { margin: 0; font-size: 14.5px; line-height: 1.6; color: var(--dpx-text-2); max-width: 42ch; }
@keyframes dpxRvPop { from { transform: scale(0.55); opacity: 0; } to { transform: scale(1); opacity: 1; } }
.dpx-rvm :focus-visible, .dpx-rvm-done :focus-visible { outline: 2px solid #a78bfa; outline-offset: 2px; border-radius: var(--dpx-r2); }
/* "Thank you for sharing" success modal (reviewThanks): the body (.dpx-rvm-done) is already
   centered, but FPModal lays out the icon + heading on the left and the buttons on the right,
   so the card reads lopsided. Center the whole card to match. Scoped via :has() on the unique
   .dpx-rvm-done marker so no other FPModal dialog is touched. */
.fpm-dlg:has(.dpx-rvm-done) { text-align: center; }
.fpm-dlg:has(.dpx-rvm-done) .fpm-ic { margin-left: auto; margin-right: auto; }
.fpm-dlg:has(.dpx-rvm-done) .fpm-acts { justify-content: center; }
@media (prefers-reduced-motion: reduce) {
  .dpx-rvm-star, .dpx-rvm-add, .dpx-rvm-yn, .dpx-rvm-in, .dpx-rvm-ta { transition: none; }
  .dpx-rvm-star:hover { transform: none; }
  .dpx-rvm-done-ic { animation: none; }
}
@media (max-width: 560px) {
  .dpx-rvm { gap: 15px; }
  .dpx-rvm-stars { gap: 5px; }
  .dpx-rvm-star { width: 44px; height: 44px; }
  .dpx-rvm-yn { flex: 1; min-width: 0; }
}
