/* ============================================================
   Shared accessibility layer — storefront AND admin
   ============================================================

   One file, loaded by both, so focus and motion behave identically everywhere.

   Before this, `:focus-visible` and `prefers-reduced-motion` existed in exactly
   one place: a runtime injection inside assets/js/site-rewrite.js. That script
   is not loaded by any admin page, so the entire admin had no focus system and
   no reduced-motion support at all. The rules now live in a stylesheet that
   both surfaces link, and site-rewrite.js no longer injects a competing copy.

   Load this AFTER the surface stylesheet so its focus rules win over the
   `outline: none` declarations scattered through the page-level CSS.
*/

/* ============================================================
   1. ACCESSIBLE FUNCTIONAL COLOUR TOKENS
   ============================================================

   These do NOT replace the brand palette — that belongs to the visual-direction
   phase. They are the text-safe siblings of brand colours that fail WCAG AA at
   normal text sizes, kept at the same hue and saturation and darkened only
   until they pass. Measured ratios are in docs/CONTRAST.md.

   Use the brand token for large display type and decorative fills; use the
   -text variant whenever the colour carries normal-size text or a UI label.
*/
:root {
  --gold-text:      #916B21;  /* was #B8882A 3.19:1 -> 4.55:1 on the darkest page background */
  --accent-text:    #98673E;  /* was #C4956E 2.67:1 -> 4.53:1 */
  --gold-surface:   #976F22;  /* white on this: 3.19:1 -> 4.55:1 */
  --accent-surface: #9F6B41;  /* white on this: 2.67:1 -> 4.51:1 */

  --muted-text:     #826E5F;  /* was #8A7565 4.09:1 on admin bg -> 4.53:1 */
  --warning-text:   #C74600;  /* was #E65100 3.79:1 -> 4.57:1 */
  --stock-ok-text:  #17833E;  /* was #22c55e 2.28:1 -> 4.52:1 */
  --stock-low-text: #9E6506;  /* was #f59e0b 2.15:1 -> 4.56:1 */
  --stock-out-text: #E21313;  /* was #ef4444 3.76:1 -> 4.54:1 */
  --whatsapp-text:  #17833F;  /* was #25D366 1.98:1 -> 4.52:1 */

  /* Gold on a DARK surface needs to be lighter, not darker. The repaired
     `--gold-text` is tuned for light page backgrounds and drops to 2.77:1 on
     the hero's #3D2B1F. This is the counterpart for dark sections. */
  --gold-on-dark: #BF8D2C;   /* 4.52:1 on #3D2B1F, 6.29:1 on #1A0F14 */

  /* The same for WhatsApp green. `--whatsapp-text` passes on light grounds and
     is 2.78:1 on the footer's #3D2B1F; the brand green itself passes there. */
  --whatsapp-on-dark: #25D366; /* 6.77:1 on #3D2B1F (its #1EBE57 hover: 5.47:1) */

  --focus-ring:        #1A0F14;
  --focus-ring-offset: #FFFFFF;
}

/* Dark sections: the hero and anything else on a deep brown or ink ground.
   `.overline` is the eyebrow label above the hero heading — it was gold on
   dark brown at 2.77:1, below AA for its 11.8px size. */
/* `!important` because a11y.css is linked BEFORE each page's inline <style>
   block, so an equal-specificity page rule would otherwise win on order. This
   file is the accessibility override layer; losing to page CSS defeats it. */
.hero .overline,
.hero .eyebrow,
.hero-content .overline,
[data-dark-surface] .overline {
  color: var(--gold-on-dark) !important;
}

/* ---- Repointing the failing tokens ----

   `--clr-gold` and `--clr-accent` are used for small text in ~20 places in
   store.css and for badge text in admin.css, and both fail AA on white (3.19:1
   and 2.67:1). Overriding each selector individually would leave the next
   usage broken, so the TOKENS are repointed at their accessible siblings.

   The change is a darkening within the same hue and saturation - it is not a
   palette redesign, and it fixes the inverse pair too: white text on a gold or
   accent fill now passes as well.

   The original values stay available as *-brand for decorative fills that
   carry no text, and for the visual-direction phase to work from. */
:root {
  --clr-gold-brand:   #B8882A;
  --clr-accent-brand: #C4956E;
  --gold-brand:       #B8882A;
  --accent-brand:     #C4956E;

  --clr-gold:   #916B21;
  --clr-accent: #98673E;
  --gold:       #916B21;
  --accent:     #98673E;
  --clr-muted:  #6F5A4A;   /* already passing at 6.49:1; pinned so it stays */
  --clr-whatsapp: #17833F;
}

/* The storefront footer is a dark surface (#3D2B1F), so its WhatsApp link
   takes the dark-ground green. Repointing the TOKEN inside the footer, rather
   than styling the link, also reaches the page-level
   `.footer-whatsapp { color: var(--clr-whatsapp) !important }` copies in four
   pages. The light-ground #17833F measured 2.78:1 here: the RB-3 finding. */
.site-footer {
  --clr-whatsapp: var(--whatsapp-on-dark);
}

/* Admin's muted token carries small secondary labels everywhere, and its
   warning and stock colours all fail as text. Repoint them here rather than
   auditing every usage individually. */
.admin-body, body {
  --muted:     var(--muted-text);
  --warning:   var(--warning-text);
  --stock-ok:  var(--stock-ok-text);
  --stock-low: var(--stock-low-text);
  --stock-out: var(--stock-out-text);
}

/* Status text that previously relied on a colour failing AA. */
.stock-ok,   [data-stock="ok"]   { color: var(--stock-ok-text); }
.stock-low,  [data-stock="low"]  { color: var(--stock-low-text); }
.stock-out,  [data-stock="out"]  { color: var(--stock-out-text); }
.low-stock                        { color: var(--stock-low-text); }
.out-of-stock                     { color: var(--stock-out-text); }

/* ============================================================
   2. FOCUS
   ============================================================

   A two-tone ring: a dark inner outline plus a light offset, so it is visible
   against both light page backgrounds and the dark admin sidebar. Never a
   colour change alone — a colour-only cue disappears for anyone who cannot
   distinguish the two colours, and vanishes entirely when the element already
   uses that colour.
*/
:where(a, button, input, select, textarea, summary, [tabindex], [contenteditable="true"]):focus-visible {
  outline: 3px solid var(--focus-ring);
  outline-offset: 2px;
  /* The white ring guarantees separation on dark surfaces. */
  box-shadow: 0 0 0 5px var(--focus-ring-offset);
  border-radius: 2px;
}

/* On the dark sidebar the colours invert so the ring stays visible. */
.sidebar :where(a, button, [tabindex]):focus-visible,
.admin-nav-overlay ~ .sidebar :where(a, button):focus-visible {
  outline-color: #FFFFFF;
  box-shadow: 0 0 0 5px rgba(0, 0, 0, 0.85);
}

/* `outline: none` appears 83 times across 30 files, often with only a
   border-colour change as a replacement. This wins over all of them for
   keyboard users, while leaving mouse users' pointer-focus styling alone. */
:where(.search-input, .filter-select, .rich-content, input, select, textarea, button):focus-visible {
  outline: 3px solid var(--focus-ring);
  outline-offset: 2px;
}

/* Skip link. site-rewrite.js still creates the anchor; its styling lives here. */
.tw-skip-link {
  position: absolute;
  left: 8px;
  top: -48px;
  z-index: 10000;
  background: var(--clr-ink, #1A0F14);
  color: #FFFFFF;
  padding: 10px 16px;
  font-family: var(--font-body, sans-serif);
  font-size: 14px;
  transition: top 0.15s ease;
}
.tw-skip-link:focus { top: 8px; }

/* ============================================================
   3. TOUCH TARGETS
   ============================================================ */
@media (pointer: coarse) {
  :where(button, a.btn, .btn, [role="button"], .gallery-thumb, .qty-btn) {
    min-height: 44px;
    min-width: 44px;
  }
}

/* ============================================================
   4. HOVER-ONLY ACTIONS
   ============================================================

   Quick-add and wishlist buttons are revealed on hover. On a touch screen
   there is no hover, so the action is simply unreachable; for a keyboard user
   it is invisible until it already has focus.

   Reveal them whenever the card is hovered OR contains focus, and show them
   unconditionally where hover is not available.
*/
.product-quick-add,
.product-card-wishlist,
[data-hover-action] {
  transition: opacity 0.2s ease;
}
/* store.css hides the quick-add with `transform: translateY(100%)`, not with
   opacity, so the reveal must undo the transform as well. */
.product-card:hover .product-quick-add,
.product-card:focus-within .product-quick-add,
.product-quick-add:focus-visible,
.product-card:hover .product-card-wishlist,
.product-card:focus-within .product-card-wishlist,
.product-card-wishlist:focus-visible,
.product-card:focus-within [data-hover-action],
[data-hover-action]:focus-visible {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transform: translateY(0);
}

/* No hover available at all: never hide an essential action behind it. */
/* No hover at all. The quick-add is positioned absolutely at the bottom of the
   card, so it is left visible in place rather than sliding. */
@media (hover: none), (pointer: coarse) {
  .product-quick-add,
  .product-card-wishlist,
  [data-hover-action] {
    opacity: 1 !important;
    visibility: visible !important;
    pointer-events: auto !important;
    transform: translateY(0) !important;
    position: static !important;
  }
}

/* ============================================================
   5. REDUCED MOTION
   ============================================================

   Decorative movement stops. State feedback does not: a control that changes
   appearance on press still does so, just instantly, and anything that
   communicates loading or progress keeps animating — removing that would hide
   information rather than reduce motion.
*/
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto !important; }

  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }

  /* Entrance animations are purely decorative; make their end state the
     start state so content is simply present. */
  .fade-up, .fade-in, .reveal, [data-animate] {
    opacity: 1 !important;
    transform: none !important;
  }

  /* Progress and loading indicators must keep moving — they are information.
     Re-granted after the blanket rule above. */
  .spinner, .loader, .loading-spinner, progress,
  [role="progressbar"], [aria-busy="true"] .spinner {
    animation-duration: 1s !important;
    animation-iteration-count: infinite !important;
  }

  /* The announcement ticker is decorative motion; park it. */
  .ticker-track { animation: none !important; transform: none !important; }
}

/* ============================================================
   6. DIALOGS
   ============================================================ */
.tw-dialog-open { overflow: hidden; }

.tw-dialog-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.5);
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
}

/* A dialog must fit the viewport and scroll internally rather than pushing
   its own actions off-screen at 360px. */
.tw-dialog {
  background: var(--card-bg, #FFFFFF);
  color: var(--ink, #1A0F14);
  border-radius: 8px;
  max-width: min(560px, 100%);
  max-height: calc(100vh - 32px);
  overflow-y: auto;
  padding: 20px;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
}

/* ============================================================
   7. LINKS DISTINGUISHED BY MORE THAN COLOUR
   ============================================================

   A link inside body copy needs a non-colour cue. Navigation, buttons and
   cards are excluded: their position and shape already identify them.
*/
.post-content a:not(.btn),
.seo-text a:not(.btn),
.accordion-body a:not(.btn),
.faq-answer a:not(.btn) {
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* ============================================================
   8. CAROUSEL PAUSE CONTROL
   ============================================================
   Built by assets/js/carousel-a11y.js. Styled here so it inherits the shared
   focus ring and touch-target rules above. */
.hero-playpause {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  margin-left: 12px;
  padding: 0;
  font-size: 15px;
  line-height: 1;
  color: #FFFFFF;
  background: rgba(0, 0, 0, 0.55);
  border: 1px solid rgba(255, 255, 255, 0.7);
  border-radius: 50%;
  cursor: pointer;
}
.hero-playpause:hover { background: rgba(0, 0, 0, 0.75); }

/* Visually hidden, still announced. */
.sr-only {
  position: absolute !important;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* ============================================================
   9. ADMIN MOBILE NAVIGATION
   ============================================================

   THE authoritative mobile-sidebar rules.

   These lived in assets/css/admin.css, but only 5 of the 23 admin pages link
   that file — the other 18 carry their own inline CSS. The rules therefore
   reached a fraction of the admin: on the rest the sidebar stayed off-screen
   with no way back, and `.main` kept its desktop `margin-left`, pushing the
   page 473px wider than a 360px viewport.

   a11y.css is linked by every admin page, so they live here. The trigger and
   overlay are built by setupMobileNav() in assets/js/admin.js.
   Do not re-add a page-local copy. */

.admin-menu-toggle {
  display: none;                 /* desktop: hidden; shown in the query below */
  align-items: center;
  justify-content: center;
  width: 44px;                   /* 44px minimum touch target */
  height: 44px;
  margin-right: 12px;
  padding: 0;
  background: transparent;
  border: 1px solid var(--border, #d8d2c8);
  border-radius: 8px;
  cursor: pointer;
  color: inherit;
  flex: 0 0 auto;
  /* Above the overlay (99) so the trigger stays clickable while the menu is
     open. Without this the overlay covers it and the trigger cannot close
     what it opened - the menu still closed via the overlay, which looked
     correct but was not the documented behaviour. */
  position: relative;
  z-index: 101;
}
.admin-menu-toggle-bars,
.admin-menu-toggle-bars::before,
.admin-menu-toggle-bars::after {
  display: block;
  width: 18px;
  height: 2px;
  background: currentColor;
  border-radius: 2px;
  content: '';
}
.admin-menu-toggle-bars { position: relative; }
.admin-menu-toggle-bars::before { position: absolute; top: -6px; }
.admin-menu-toggle-bars::after  { position: absolute; top: 6px; }

.admin-nav-overlay {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.45);
  z-index: 99;                   /* below .sidebar (100), above the page */
}
.admin-nav-overlay.open { display: block; }

body.admin-nav-open { overflow: hidden; }

/* `.main` is a flex item on every admin page, and a flex item defaults to
   `min-width: auto` — it refuses to shrink below its content's intrinsic
   width. Wide content (the orders table at 1024px, the dashboard at 360px)
   therefore pushed the whole page sideways. This is the canonical fix and it
   applies at every width, not only mobile. */
.main { min-width: 0; }

/* Wide content — the orders table is 819px and does not shrink — must scroll
   inside its own region rather than making the whole page scroll sideways.
   A horizontally scrolling table is usable; a horizontally scrolling page is
   not, because it drags the header and navigation off-screen with it. */
.admin-content,
.table-wrap,
.table-responsive,
.table-card {
  min-width: 0;
  max-width: 100%;
  overflow-x: auto;
}

/* The trigger lives inside `.admin-header`, which is `position: sticky` with
   its own z-index — that creates a stacking context, so a z-index on the
   trigger alone can never lift it above the overlay. Raise the HEADER while
   the menu is open instead. The overlay then dims the page content but leaves
   the trigger clickable, so it can close what it opened. */
body.admin-nav-open .admin-header {
  position: relative;
  z-index: 101;
}

@media (max-width: 768px) {
  .admin-menu-toggle { display: inline-flex; }

  .sidebar {
    transform: translateX(-100%);
    transition: transform 0.28s ease;
    box-shadow: none;
  }
  .sidebar.open {
    transform: translateX(0);
    box-shadow: 0 0 40px rgba(0, 0, 0, 0.35);
  }

  /* Each admin page sets its own `.main { margin-left: <sidebar width> }` in
     inline CSS. Without this reset the content keeps that margin once the
     sidebar is off-screen and the page overflows horizontally.

     `min-width: 0` is the important half: `.main` is a flex item, and a flex
     item defaults to `min-width: auto`, which refuses to shrink below its
     content's intrinsic width. That alone pushed the dashboard 233px wider
     than a 360px viewport even after the margin was cleared. */
  .main {
    margin-left: 0 !important;
    width: auto !important;
    min-width: 0 !important;
  }

}

/* Reduced motion still gets the panel — just without the slide. */
@media (prefers-reduced-motion: reduce) {
  .sidebar { transition: none; }
}

/* ---- Site Editor topbar ----
   At 360px the topbar's three groups exceed the viewport and, because the row
   does not wrap, the Save and Discard controls were pushed 281px off-screen —
   visible to CSS, unreachable to the operator. Let the row wrap so every
   control stays on screen. */
@media (max-width: 900px) {
  .topbar {
    flex-wrap: wrap;
    height: auto;
    row-gap: 6px;
    padding-top: 6px;
    padding-bottom: 6px;
  }
  .topbar-left,
  .topbar-right {
    min-width: 0;
    flex: 1 1 auto;
  }
  .topbar-right { justify-content: flex-start; }

  /* Phase 4 added a status pill and a Publish button to this row.

     The pill must not be squeezed narrower than its own text. Its wrapper is a
     column flex box with `align-items: flex-end`, so a pill wider than the box
     overflows to the LEFT — measured at x = -17px in a 360px viewport, i.e.
     partly off the side of the screen, while `scrollWidth` reported no overflow
     because <body> is `overflow: hidden`. Letting the group wrap costs a row and
     keeps every control legible. */
  .topbar-right { flex-wrap: wrap; row-gap: 6px; }
  .topbar-status { flex: 0 0 auto; align-items: flex-start; }
  .status-pill { max-width: 100%; }

  /* The editor grid is sized against a fixed topbar height; once it wraps,
     let the app region take the remaining space instead. */
  .app { height: auto; min-height: 60vh; }
}

/* ============================================================
   10. FIXES FOUND BY REAL-BROWSER AXE
   ============================================================

   Calculated contrast checks the TOKEN values. A browser checks what is
   actually painted — including `opacity`, which multiplies the rendered
   colour and is invisible to a token-level calculation. These four failed in
   Chromium and passed every earlier check. */

/* `.section-subtitle` used a passing colour at reduced opacity, which drags
   the effective ratio down: #6F5A4A is 6.25:1 on cream but 2.57:1 once faded.
   The colour already carries the "secondary" meaning; the fade only cost
   legibility.

   `!important` throughout this section: a11y.css is linked BEFORE each page's
   inline <style>, so an equal-specificity page rule wins on order. */
.section-subtitle {
  opacity: 1 !important;
  color: var(--clr-muted, #6F5A4A) !important;
}

/* The footer sits on dark brown (#3D2B1F), so gold text there needs the
   LIGHT variant, not the light-background one. The tagline was the repaired
   #916B21 on that ground: 2.76:1. */
.site-footer .logo-tagline,
.footer-brand .logo-tagline {
  opacity: 1 !important;
  color: var(--gold-on-dark) !important;
}

/* Dark ink headings inside the dark footer: #1A0F14 on #3D2B1F is 1.39:1 —
   effectively invisible. */
.site-footer h3,
.site-footer h4,
.footer-newsletter-copy h3 {
  color: #FFFFFF !important;
}

/* The current-page breadcrumb was inheriting the SEPARATOR colour (#E8D5C4,
   a border tone) at 1.35:1. It is the one crumb a reader most needs. */
.breadcrumb [aria-current="page"],
.breadcrumb strong {
  color: var(--clr-ink, #1A0F14) !important;
  font-weight: 600;
}

/* Site Editor muted labels.

   Phase 2 forced these to #6B7176 because the editor's own `--soft` token was
   #8C9196 — 3.17:1 on its near-white panel. Phase 4 darkened `--soft` at source
   to #616568 (5.88:1 on white, 5.67:1 on #FAFBFB), so the override is no longer
   needed for `.pane-header h3` or `#frameUrl`.

   `.sec-status` is removed from this list for a second, stronger reason: it is
   now a STATE colour. Phase 4 gives the section list `.sec-status-ok`,
   `-draft`, `-hidden` and `-empty`, each with its own token, and an
   `!important` on the base class defeated all four — every status rendered
   grey. It also failed on its own terms: on the selected row's #EAEFFF,
   #6B7176 is 4.30:1, which is how the Phase 4 axe scan of the
   section-selected state found this rule at all.

   The replacements are 5.12:1 or better on both the panel and the selected
   row. `.muted` stays: it is a generic class with no state of its own. */
.left-pane .muted,
.right-pane .muted {
  color: #6B7176 !important;
}

/* An element that scrolls must be reachable by keyboard, or its content is
   unreadable without a mouse. The inspector pane scrolls when a section has
   many fields; axe flags it as scrollable-region-focusable. */
#rightBody,
#leftBody,
.left-pane,
.right-pane {
  /* -1 keeps it out of the tab order as a stop, but scrollable regions with
     no focusable children still need programmatic focus; where the pane DOES
     contain controls, those remain the real tab stops. */
  scrollbar-gutter: stable;
}

/* Admin sidebar group labels were white at 30% alpha on #1A0F14 — 2.67:1.
   0.65 gives 8.24:1 and still reads as a quieter label than the nav items. */
.sidebar .nav-section {
  color: rgba(255, 255, 255, 0.65);
}

/* The WhatsApp button hardcodes the brand green rather than using the token,
   so the token repair never reached it: #25D366 is 1.91:1 on white. */
#send-cart-whatsapp,
.whatsapp-link,
[data-whatsapp] {
  color: var(--whatsapp-text, #17833F) !important;
}

/* ============================================================
   11. PRODUCT EDITOR — FIVE-STEP WORKFLOW
   ============================================================
   Built by assets/js/product-workflow.js. Restrained on purpose: the job is
   to make the workflow legible, not to decorate the admin. No new card style,
   no animation beyond immediate state feedback, sentence-case labels. */

.pe-shell {
  display: grid;
  grid-template-columns: 200px minmax(0, 1fr) 260px;
  gap: 20px;
  align-items: start;
  margin-top: 16px;
}

/* ---- step list ---- */
.pe-step-list { list-style: none; margin: 0; padding: 0; position: sticky; top: 72px; }
.pe-step-list li { margin: 0 0 2px; }

.pe-step {
  display: grid;
  grid-template-columns: 22px 1fr;
  grid-template-areas: 'num label' '.   state';
  gap: 2px 8px;
  width: 100%;
  min-height: 44px;
  padding: 8px 10px;
  text-align: left;
  font: inherit;
  color: inherit;
  background: transparent;
  border: 0;
  border-left: 3px solid transparent;
  border-radius: 0 4px 4px 0;
  cursor: pointer;
}
.pe-step:hover { background: rgba(0, 0, 0, 0.04); }
.pe-step.is-active {
  background: rgba(0, 0, 0, 0.05);
  border-left-color: var(--primary, #3D2B1F);
  font-weight: 600;
}
.pe-step-num   { grid-area: num; opacity: 0.7; font-variant-numeric: tabular-nums; }
.pe-step-label { grid-area: label; }
.pe-step-state { grid-area: state; font-size: 0.74rem; }
/* State is text, never colour alone.

   These are NOT the shared --warning-text / --stock-ok-text tokens. The step
   button tints its background with 5% black when active, which drops those
   tokens to 4.1:1 — measured in Chromium, not assumed. Slightly darker
   variants tuned for that tinted ground. */
.pe-step-state.is-gap  { color: #BC4200; }   /* 4.51:1 on the tinted button */
.pe-step-state.is-done { color: #167B3A; }   /* 4.50:1 */

/* ---- panels ---- */
.pe-panel[hidden] { display: none; }

/* The step list already numbers the workflow, so the old per-section number
   badge would say it twice. */
.pe-panel .section-num { display: none; }

/* SEO is generated unless overridden, so it starts closed. */
.pe-collapsed-section .form-section-body { display: none; }

.pe-advanced { margin-top: 8px; }
.pe-advanced > summary { cursor: pointer; padding: 6px 0; font-size: 0.86rem; }

/* ---- checklist ---- */
.pe-side {
  position: sticky;
  top: 72px;
  padding: 14px;
  background: var(--card-bg, #fff);
  border: 1px solid var(--border, #E8D5C4);
  border-radius: 6px;
}
.pe-side h3 { margin: 0 0 8px; font-size: 0.92rem; }
.pe-side-status { margin: 0 0 10px; font-size: 0.86rem; color: var(--warning-text, #C74600); }
.pe-side-status.is-ready { color: var(--stock-ok-text, #17833E); }

.pe-checklist { list-style: none; margin: 0; padding: 0; }
.pe-check-item { margin-bottom: 4px; }
.pe-check-link {
  display: block;
  width: 100%;
  min-height: 44px;
  padding: 8px 10px;
  text-align: left;
  font: inherit;
  font-size: 0.84rem;
  color: inherit;
  background: transparent;
  border: 1px solid var(--border, #E8D5C4);
  border-radius: 4px;
  cursor: pointer;
}
.pe-check-link:hover { background: rgba(0, 0, 0, 0.04); }
.pe-check-link::before { content: '• '; color: var(--warning-text, #C74600); }

/* ---- review ---- */
.pe-review { margin-bottom: 18px; }
.pe-review h3 { margin: 0 0 10px; }
.pe-review-card {
  display: flex;
  gap: 16px;
  padding: 14px;
  border: 1px solid var(--border, #E8D5C4);
  border-radius: 6px;
}
.pe-review-media img { object-fit: cover; border-radius: 4px; }
.pe-review-noimg { font-size: 0.84rem; color: var(--muted-text, #826E5F); }
.pe-review-facts { display: grid; grid-template-columns: auto 1fr; gap: 4px 14px; margin: 0; }
.pe-review-facts dt { font-size: 0.8rem; color: var(--muted-text, #826E5F); }
.pe-review-facts dd { margin: 0; font-size: 0.88rem; }
.pe-review-note { margin: 10px 0 0; font-size: 0.82rem; color: var(--muted-text, #826E5F); }

/* ---- error summary (WCAG G139) ----
   Sits at the top of the editor, takes focus, and lists every problem as a
   link to the field it belongs to.

   Colours are measured, not chosen by eye. On the #FDF2F0 tint:
     #8A1C15 text    8.48:1  (AA needs 4.5:1)
     #B3261E border  5.96:1
   Recorded in docs/CONTRAST.md with the rest. */
.pe-error-summary {
  margin: 0 0 18px;
  padding: 14px 16px;
  background: #FDF2F0;
  border: 1px solid #B3261E;
  border-left-width: 4px;
  border-radius: 4px;
  grid-column: 1 / -1;
}
.pe-error-summary:focus { outline: none; }
.pe-error-summary:focus-visible {
  outline: 3px solid #B3261E;
  outline-offset: 2px;
}
.pe-error-summary h3 {
  margin: 0 0 6px;
  font-size: 1rem;
  color: #8A1C15;
}
.pe-error-summary-note {
  margin: 0 0 10px;
  font-size: 0.84rem;
  color: #7A1610;
}
.pe-error-summary ul { margin: 0; padding-left: 20px; }
.pe-error-summary li { margin-bottom: 6px; font-size: 0.88rem; }
.pe-error-summary a {
  color: #8A1C15;
  /* Underlined, because colour alone must not be the only thing marking a
     link (WCAG 1.4.1). */
  text-decoration: underline;
}
.pe-error-summary a:hover { text-decoration-thickness: 2px; }
.pe-error-summary a:focus-visible {
  outline: 3px solid #8A1C15;
  outline-offset: 2px;
}

/* The field itself, so someone who arrives by scrolling sees it too. */
.pe-shell [aria-invalid="true"] {
  border-color: #B3261E !important;
}

.pe-review-actions { margin: 12px 0 6px; }
.pe-review-actions .btn { min-height: 44px; }
.pe-preview-fallback { margin: 8px 0 0; font-size: 0.84rem; }
.pe-preview-fallback a { text-decoration: underline; }

/* ---- revision history ----
   Colours reuse the measured tokens from CONTRAST.md. The from/to pair is
   distinguished by a label and a line-through, not by colour alone
   (WCAG 1.4.1). */
.pe-history { margin-top: 24px; border-top: 1px solid var(--border, #E8D5C4); padding-top: 16px; }
.pe-history h3 { margin: 0 0 8px; font-size: 1rem; }
.pe-hist-status { margin: 0 0 12px; font-size: 0.86rem; color: var(--muted-text, #826E5F); }
.pe-hist-status.is-unavailable { color: #8C5A00; }
.pe-hist-status.is-failed { color: #8A1C15; }

.pe-hist-list { list-style: none; margin: 0; padding: 0; }
.pe-hist-item {
  padding: 12px 14px;
  margin-bottom: 8px;
  border: 1px solid var(--border, #E8D5C4);
  border-left: 3px solid var(--primary, #3D2B1F);
  border-radius: 4px;
}
.pe-hist-head { display: flex; flex-wrap: wrap; gap: 10px; align-items: baseline; }
.pe-hist-action { font-weight: 600; font-size: 0.9rem; }
.pe-hist-who { font-size: 0.84rem; }
.pe-hist-when { font-size: 0.8rem; color: var(--muted-text, #826E5F); }
.pe-hist-version { font-size: 0.75rem; color: var(--muted-text, #826E5F); }
.pe-hist-tag {
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  padding: 2px 6px;
  border-radius: 3px;
  background: #FDF2F0;
  color: #8A1C15;
}
.pe-hist-summary { margin: 6px 0 0; font-size: 0.88rem; }
.pe-hist-note { margin: 6px 0 0; font-size: 0.8rem; color: var(--muted-text, #826E5F); }

.pe-hist-diff { margin: 8px 0 0; font-size: 0.84rem; }
.pe-hist-diff dt { font-weight: 600; margin-top: 6px; }
.pe-hist-diff dd { margin: 2px 0 0; }
.pe-hist-change { display: block; word-break: break-word; }
.pe-hist-from { text-decoration: line-through; color: var(--muted-text, #826E5F); }
.pe-hist-to { font-weight: 500; }
/* Reads as " changed to " for a screen reader; it is real text, not a glyph. */
.pe-hist-arrow { color: var(--muted-text, #826E5F); }

.pe-hist-restore { margin-top: 10px; min-height: 44px; }
#pe-hist-more { margin-top: 8px; min-height: 44px; width: 100%; }

@media (max-width: 767px) {
  .pe-hist-head { gap: 6px; }
  .pe-hist-item { padding: 10px; }
}

/* ---- image workflow ----
   Every control is 44px minimum: these are the buttons an operator uses most,
   and they are the ones most often used on a phone in a shop. */
.pe-images { margin-bottom: 20px; }
.pe-images h3 { margin: 0 0 6px; font-size: 1rem; }
.pe-img-count, .pe-img-empty { margin: 0 0 10px; font-size: 0.86rem; color: var(--muted-text, #826E5F); }

.pe-img-list { list-style: none; margin: 0; padding: 0; }
.pe-img-item {
  display: grid;
  grid-template-columns: 80px minmax(0, 1fr);
  gap: 12px;
  padding: 12px;
  margin-bottom: 10px;
  border: 1px solid var(--border, #E8D5C4);
  border-radius: 4px;
  align-items: start;
}
.pe-img-item.is-pending { opacity: 0.85; border-style: dashed; }
.pe-img-item.is-failed { border-color: #B3261E; }

.pe-img-thumb { position: relative; }
.pe-img-thumb img { width: 80px; height: 100px; object-fit: cover; border-radius: 3px; display: block; }
.pe-img-badge {
  position: absolute; left: 0; bottom: 0; right: 0;
  font-size: 0.68rem; text-align: center; padding: 2px 0;
  background: rgba(61, 43, 31, 0.92); color: #fff;
  border-radius: 0 0 3px 3px;
}

.pe-img-body { min-width: 0; }
.pe-img-state { margin: 0 0 6px; font-size: 0.8rem; }
/* Not colour alone: each state carries its own words. */
.pe-img-state.is-ok { color: #17833E; }
.pe-img-state.is-failed { color: #8A1C15; }
.pe-img-alt-label { display: block; font-size: 0.78rem; margin-bottom: 3px; }
.pe-img-alt { width: 100%; min-height: 40px; }
.pe-img-note { font-size: 0.8rem; color: var(--muted-text, #826E5F); }

.pe-img-controls {
  grid-column: 1 / -1;
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 4px;
}
.pe-img-controls .btn { min-height: 44px; }
.pe-img-controls .btn[disabled] { opacity: 0.5; cursor: not-allowed; }

@media (max-width: 767px) {
  .pe-img-item { grid-template-columns: 64px minmax(0, 1fr); }
  .pe-img-thumb img { width: 64px; height: 80px; }
  .pe-img-controls .btn { flex: 1 1 calc(50% - 4px); }
}

/* ---- inherited values and templates ----
   The source of a value is carried by a word ("Store default" / "Product
   override"), not by colour, so it survives greyscale and colour-blindness. */
.pe-inherit { margin: 6px 0 0; font-size: 0.82rem; color: var(--muted-text, #826E5F); }
.pe-inherit strong { font-weight: 600; color: var(--primary, #3D2B1F); }
.pe-inherit.is-unknown { color: #8C5A00; }

.pe-template-slot:empty { display: none; }
.pe-template-offer, .pe-template-note {
  margin: 8px 0 0;
  font-size: 0.82rem;
  color: var(--muted-text, #826E5F);
}
.pe-template-offer {
  padding: 10px 12px;
  border: 1px solid var(--border, #E8D5C4);
  border-radius: 4px;
}
.pe-template-offer .btn { margin: 8px 0; min-height: 44px; }
.pe-template-note.is-empty {
  padding: 10px 12px;
  border-left: 3px solid #8C5A00;
  background: #FFF8EE;
  color: #6B4400;
}

.pe-template-conflicts { margin: 10px 0; }
.pe-template-conflict {
  padding: 10px 0;
  border-top: 1px solid var(--border, #E8D5C4);
}
.pe-template-conflict label { margin-left: 6px; font-weight: 600; font-size: 0.86rem; }
.pe-template-yours, .pe-template-theirs {
  margin: 4px 0 0 24px;
  font-size: 0.8rem;
  word-break: break-word;
}
.pe-template-yours { color: var(--primary, #3D2B1F); }
.pe-template-theirs { color: var(--muted-text, #826E5F); }

/* ============================================================
   PRODUCT IMPORT
   ============================================================

   Colours are measured, not chosen. On white:
     #17833E ok       4.82:1
     #8C5A00 warning  5.87:1
     #826E5F muted    4.83:1
     #3D2B1F primary 13.43:1
   On their tints:
     #8A1C15 on #FDF2F0  8.48:1
     #6B4400 on #FFF8EE  8.12:1
   Recorded in docs/CONTRAST.md.

   Every status also carries a WORD, never colour alone (WCAG 1.4.1), and the
   row list is cards rather than a wide table so it survives a phone. */

.imp-lede {
  margin: 0 0 18px;
  padding: 12px 14px;
  background: #FFF8EE;
  border-left: 3px solid #8C5A00;
  border-radius: 4px;
  font-size: 0.9rem;
  color: #6B4400;
}

/* ---- stage indicator ---- */
.imp-steps { margin-bottom: 20px; }
.imp-step-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.imp-step {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 44px;
  padding: 8px 12px;
  font: inherit;
  font-size: 0.84rem;
  color: inherit;
  background: transparent;
  border: 1px solid var(--border, #E8D5C4);
  border-radius: 4px;
  cursor: pointer;
}
.imp-step.is-active {
  border-color: var(--primary, #3D2B1F);
  border-bottom-width: 3px;
  font-weight: 600;
}
.imp-step.is-done .imp-step-num::after { content: ' \2713'; }
.imp-step-num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--border, #E8D5C4);
  font-size: 0.74rem;
}
.imp-step.is-active .imp-step-num { background: var(--primary, #3D2B1F); color: #fff; }
.imp-step-state { font-size: 0.7rem; color: var(--muted-text, #826E5F); }
.imp-step:focus-visible { outline: 3px solid var(--primary, #3D2B1F); outline-offset: 2px; }

/* ---- panels ---- */
.imp-panel { max-width: 860px; }
.imp-panel h3 { margin: 0 0 8px; font-size: 1.05rem; }
.imp-panel h3:focus { outline: none; }
.imp-panel h3:focus-visible { outline: 3px solid var(--primary, #3D2B1F); outline-offset: 3px; }
.imp-panel p { font-size: 0.9rem; line-height: 1.6; }
.imp-status { font-size: 0.86rem; color: var(--muted-text, #826E5F); min-height: 1.2em; }
.imp-note { font-size: 0.82rem; color: var(--muted-text, #826E5F); }
.imp-note.is-important {
  padding: 10px 12px;
  background: #FFF8EE;
  border-left: 3px solid #8C5A00;
  color: #6B4400;
}
.imp-drafts-note {
  padding: 10px 12px;
  background: #F2F7F3;
  border-left: 3px solid #17833E;
  border-radius: 4px;
  font-size: 0.88rem;
}

.imp-nav {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-top: 20px;
  padding-top: 14px;
  border-top: 1px solid var(--border, #E8D5C4);
}
.imp-nav .btn { min-height: 44px; }

/* ---- column reference ---- */
.imp-columns { margin: 12px 0; font-size: 0.86rem; }
.imp-columns summary { min-height: 44px; display: flex; align-items: center; cursor: pointer; }
.imp-column-list { display: grid; grid-template-columns: auto 1fr; gap: 4px 14px; margin: 8px 0; }
.imp-column-list dt { font-weight: 600; font-size: 0.82rem; }
.imp-column-list dt.is-required { color: #8A1C15; }
.imp-column-list dd { margin: 0; font-size: 0.82rem; color: var(--muted-text, #826E5F); }

/* ---- mapping ---- */
.imp-map-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 4px 16px;
  padding: 12px 0;
  border-bottom: 1px solid var(--border, #E8D5C4);
  align-items: center;
}
.imp-map-label { font-weight: 600; font-size: 0.88rem; }
.imp-map-example {
  grid-column: 1;
  margin: 0;
  font-size: 0.78rem;
  color: var(--muted-text, #826E5F);
  word-break: break-word;
}
.imp-map-select { grid-column: 2; grid-row: 1 / span 2; min-height: 44px; width: 100%; }
.imp-map-select[aria-invalid="true"] { border-color: #B3261E; }
.imp-map-warning { grid-column: 1 / -1; margin: 0; font-size: 0.8rem; color: #8A1C15; }
.imp-map-warning:empty { display: none; }

/* ---- summary ---- */
.imp-summary-list {
  display: grid;
  grid-template-columns: auto auto;
  gap: 4px 16px;
  margin: 0 0 16px;
  font-size: 0.9rem;
}
.imp-summary-list dt { color: var(--muted-text, #826E5F); }
.imp-summary-list dd { margin: 0; font-weight: 600; }
.imp-summary-list .is-ok { color: #17833E; }
.imp-summary-list .is-warning { color: #8C5A00; }
.imp-summary-list .is-error, .imp-summary-list .is-conflict { color: #8A1C15; }

.imp-file-errors {
  padding: 12px 14px;
  margin-bottom: 14px;
  background: #FDF2F0;
  border-left: 3px solid #B3261E;
  border-radius: 4px;
  font-size: 0.86rem;
  color: #8A1C15;
}
.imp-file-errors h4 { margin: 0 0 6px; font-size: 0.92rem; }
.imp-ignored { margin-bottom: 14px; font-size: 0.86rem; }
.imp-ignored summary { min-height: 44px; display: flex; align-items: center; cursor: pointer; }

/* ---- rows, as cards ---- */
.imp-row-list { list-style: none; margin: 0; padding: 0; }
.imp-row {
  padding: 12px 14px;
  margin-bottom: 8px;
  border: 1px solid var(--border, #E8D5C4);
  border-left-width: 3px;
  border-radius: 4px;
}
.imp-row.is-valid, .imp-row.is-created { border-left-color: #17833E; }
.imp-row.is-warning { border-left-color: #8C5A00; }
.imp-row.is-error, .imp-row.is-failed { border-left-color: #B3261E; }
.imp-row.is-conflict { border-left-color: #8C5A00; border-left-style: dashed; }

.imp-row-head { display: flex; flex-wrap: wrap; gap: 10px; align-items: baseline; }
.imp-row-number { font-weight: 600; font-size: 0.84rem; }
.imp-row-status { font-size: 0.8rem; }
.imp-row.is-valid .imp-row-status, .imp-row.is-created .imp-row-status { color: #17833E; }
.imp-row.is-warning .imp-row-status, .imp-row.is-conflict .imp-row-status { color: #8C5A00; }
.imp-row.is-error .imp-row-status, .imp-row.is-failed .imp-row-status { color: #8A1C15; }
.imp-row-line { font-size: 0.76rem; color: var(--muted-text, #826E5F); }
.imp-row-link { font-size: 0.8rem; text-decoration: underline; }
.imp-row-name { margin: 4px 0 0; font-size: 0.92rem; word-break: break-word; }
.imp-row-fields { margin: 4px 0 0; font-size: 0.78rem; color: var(--muted-text, #826E5F); word-break: break-word; }
.imp-row-message { margin: 4px 0 0; font-size: 0.82rem; word-break: break-word; }
.imp-row-message.is-error { color: #8A1C15; }
.imp-row-message.is-conflict { color: #8C5A00; }
.imp-row-message.is-warning { color: #8C5A00; }
.imp-results-headline { font-size: 0.95rem; font-weight: 600; }

/* ---- small screens ---- */
@media (max-width: 767px) {
  .imp-step-list { overflow-x: auto; flex-wrap: nowrap; padding-bottom: 6px; }
  .imp-step { flex: 0 0 auto; white-space: nowrap; }
  .imp-map-row { grid-template-columns: minmax(0, 1fr); }
  .imp-map-select { grid-column: 1; grid-row: auto; }
  .imp-nav { position: sticky; bottom: 0; background: var(--surface, #fff); }
  .imp-nav .btn { flex: 1 1 calc(50% - 5px); }
  .imp-summary-list { grid-template-columns: minmax(0, 1fr) auto; }
}

/* ---- previous / next ---- */
.pe-prevnext { display: none; gap: 10px; margin-top: 18px; }
.pe-prevnext .btn { flex: 1 1 0; min-height: 44px; }

/* ---- tablet ---- */
@media (max-width: 1199px) {
  .pe-shell { grid-template-columns: minmax(0, 1fr); }
  .pe-step-list {
    position: static;
    display: flex;
    gap: 6px;
    overflow-x: auto;
    padding-bottom: 6px;
  }
  .pe-step-list li { flex: 0 0 auto; }
  .pe-step {
    border-left: 0;
    border-bottom: 3px solid transparent;
    border-radius: 4px 4px 0 0;
    white-space: nowrap;
  }
  .pe-step.is-active { border-left: 0; border-bottom-color: var(--primary, #3D2B1F); }
  .pe-side { position: static; }
}

/* ---- mobile ---- */
@media (max-width: 767px) {
  .pe-prevnext { display: flex; }
  .pe-step-state { display: none; }   /* the checklist below says the same thing */
  .pe-review-card { flex-direction: column; }
  /* Publish belongs in Review, not pinned over the fields: a permanently
     visible Publish button invites publishing an unfinished product. */
  .pe-shell:not([data-step="review"]) ~ .form-save-bar .save-btn-primary,
  .pe-shell:not([data-step="review"]) ~ * .save-btn-primary { display: none; }
}
