:root {
    --chinimall-ink: #1a1a1a;
    --chinimall-muted: #666;
    --chinimall-border: #e4e4e4;
    --chinimall-bg: #fff;
    --chinimall-bg-alt: #fafafa;
    --chinimall-accent: #1e1e1e;
    --chinimall-accent-hover: #333;
    --chinimall-radius: 8px;
    --chinimall-max-width: 1120px;
}

* { box-sizing: border-box; }

/* docs/UI-UX-SPEC.md §34: no page should ever scroll horizontally. This is a safety net,
   not the fix itself — §35/§36 (fixed-width elements, long text, flex children without
   min-width:0, absolute-positioned elements) still have to be handled at the source.
   `clip`, not `hidden` (2026-09-09, real bug found live): `overflow-x: hidden` on html/body
   establishes a new scroll container, which silently breaks `position: sticky` for every
   descendant — including .chinimall-header, which was never actually sticking despite its own
   CSS saying so. `overflow: clip` clips the same horizontal overflow without creating a
   scroll container, so sticky works again; well-supported in every browser this project
   already assumes (Chrome 90+/Safari 16+/Firefox 113+, same baseline as -webkit-line-clamp
   elsewhere in this file). */
html, body { overflow-x: clip; max-width: 100%; }

body {
    margin: 0;
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Vazirmatn", Tahoma, Arial, sans-serif;
    color: var(--chinimall-ink);
    background: var(--chinimall-bg);
    line-height: 1.55;
    /* Bottom nav is fixed and shown at every width (see .chinimall-bottom-nav below) — this
       keeps its own height off the last bit of real content on every page, not just mobile. */
    padding-bottom: calc(58px + env(safe-area-inset-bottom));
}

img { max-width: 100%; height: auto; display: block; }

a { color: inherit; text-decoration: none; }

.chinimall-container {
    max-width: var(--chinimall-max-width);
    margin: 0 auto;
    padding: 0 20px;
}

/* Header — Header & Multi-Source Search Redesign, 2026-09-07: Logo+AI (row 1) and, on the
   homepage, the Search Bar (row 2, see .chinimall-search-row below) are one visual <header> —
   deliberately no border/background split between them any more (§1/§34). Account, Cart,
   Language, or menu still never live here — those live in the bottom nav / inside Account. */
.chinimall-header {
    position: sticky;
    top: 0;
    background: var(--chinimall-bg);
    z-index: 50;
    /* Owner request, 2026-09-17: the header had no border/shadow of its own at all, so once
       content scrolled underneath it (sticky), the two read as visually stuck together with no
       boundary. A permanent border/shadow would show even at the very top, which wasn't asked
       for — instead .is-header-scrolled (toggled by initHeaderScrollShadow() in theme.js, based
       on scrollY) adds a very faint shadow only once the page has actually scrolled, and removes
       it again back at the top. box-shadow (not border) so it doesn't add to the header's own
       height/layout; transitioned so it fades in/out rather than snapping. */
    box-shadow: none;
    transition: box-shadow 0.15s ease;
}
.chinimall-header.is-header-scrolled {
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
}
/* Owner request, 2026-09-17: logo centered in the header on every size, Back staying at the
   left edge, no new elements added. Plain flex (the previous layout) only ever lines items up
   left-to-right — it can't center the logo against two DIFFERENT-width siblings (Back is
   conditional/absent on Home, the AI toggle is always there), so it only looked "centered" by
   coincidence on pages with no Back button. A 3-column grid with two EQUAL (1fr) outer columns
   is the actual fix: Back and the AI toggle each get one outer column (so they carry the same
   width whether or not Back is present, keeping the outer columns balanced), and the logo's own
   `auto` column sits examctly between them — grid-column is set explicitly per element below
   instead of relying on DOM order, so the logo still lands in the true center column even when
   Back is the very first (or only) other child in the markup. */
.chinimall-header-inner {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    gap: 12px;
    height: 56px;
}
.chinimall-logo { display: flex; align-items: center; justify-content: center; flex-shrink: 0; grid-column: 2; justify-self: center; }
.chinimall-logo img { max-height: 36px; width: auto; }
.chinimall-logo-text { font-size: 18px; font-weight: 700; }

.chinimall-header-back {
    /* docs/UI-UX-SPEC.md §38 — real touch area is 40px even though the visible icon (22px)
       stays small; margin-left compensates so the icon still lines up near the container edge. */
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    margin-left: -10px;
    flex-shrink: 0;
    color: var(--chinimall-ink);
    grid-column: 1;
    justify-self: start;
}
.chinimall-header-back svg { width: 22px; height: 22px; }

/* AI Mode control (docs/PRINCIPLES-AND-ROADMAP.md UI list items 7-11, redesigned 2026-09-07
   §22-24) — a global mode switch, not navigation, pushed to the header's right edge since
   Account/Cart/Language never live here. OFF: plain "AI" glyph. ON: a Credit progress ring
   replaces it entirely (see the two children below) — never both at once.
   grid-column/justify-self (2026-09-17) replace the old flex-era `margin-left: auto` push-to-
   end trick, now that .chinimall-header-inner is a 3-column grid — see that rule's own comment. */
.chinimall-ai-mode-toggle {
    all: unset;
    box-sizing: border-box;
    position: relative;
    width: 40px;
    height: 40px;
    border-radius: 50%;
    border: 1px solid var(--chinimall-border);
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    color: var(--chinimall-muted);
    flex-shrink: 0;
    grid-column: 3;
    justify-self: end;
}
.chinimall-ai-mode-icon { font-size: 11px; font-weight: 700; letter-spacing: 0.02em; }

/* Progress ring (§24) — a real SVG ring, not a corner badge: full circle at the visitor's own
   starting Credit balance, shrinking toward empty as it's spent (stroke-dashoffset computed
   server-side in header.php). Grayscale intensity is the signal: normal/low/out map to
   light/medium/dark gray per CHINIMALL_Credit's own thresholds — the number in the middle is
   secondary context.
   Owner correction, 2026-09-15: this ring used to be blue/yellow/red (a real accent color) —
   ChiniMall has a strict no-color rule (docs/UI-UX-SPEC.md line 13) that this violated since
   the 2026-09-07 redesign. Kept three distinct visual weights (not one flat color for all
   three states) by varying gray shade/darkness instead of hue, so "getting low" is still
   visible at a glance without reintroducing color. */
.chinimall-ai-ring-wrap {
    position: relative;
    width: 40px;
    height: 40px;
    display: flex;
    align-items: center;
    justify-content: center;
}
.chinimall-ai-ring-svg { position: absolute; inset: 0; transform: rotate(-90deg); }
.chinimall-ai-ring-bg { fill: none; stroke: var(--chinimall-border); }
.chinimall-ai-ring-fg { fill: none; stroke-linecap: round; transition: stroke-dashoffset .3s ease; }
.chinimall-ai-ring-normal { stroke: #9ca3af; }
.chinimall-ai-ring-low { stroke: #4b5563; }
.chinimall-ai-ring-out { stroke: #111111; }
.chinimall-ai-mode-credit { position: relative; font-size: 12px; font-weight: 700; color: var(--chinimall-ink); }
/* Real bug found and fixed 2026-09-07 (kept here since the same specificity trap applies to
   any [hidden] element that also carries its own explicit `display`): the [hidden] attribute's
   own UA-stylesheet rule (display:none) loses to a class rule's explicit display:flex on
   specificity, so toggling the `hidden` DOM property (see setAiToggle() in theme.js) would
   otherwise leave the ring visibly flexed. An attribute-selector rule matches the class rule's
   specificity and, coming later in the stylesheet, wins. */
.chinimall-ai-ring-wrap[hidden] { display: none; }

/* Search Bar (row 2 of the unified header, homepage only) — Header & Multi-Source Search
   Redesign, 2026-09-07 §1/§14-21. One pill-shaped box holds the Source selector, the text
   input (keyword OR pasted link), and the camera icon; the Search button sits outside it.
   Deliberately no gray section background any more — see .chinimall-header's own comment. */
.chinimall-search-row {
    display: flex;
    align-items: stretch;
    gap: 10px;
    max-width: 680px;
    margin: 0 auto;
    padding: 0 0 20px;
}
.chinimall-search-box {
    flex: 1;
    display: flex;
    align-items: center;
    min-width: 0;
    border: 1px solid var(--chinimall-border);
    border-radius: 999px;
    background: #fff;
    height: 48px;
}
/* The Source select + text input live in their own <form> (a sibling of .chinimall-camera-
   trigger's own <form> — see chinimall_search_bar()'s own comment on why they can't nest);
   this fills the same flex role .chinimall-search-box itself used to play alone. */
.chinimall-search-fields {
    flex: 1;
    display: flex;
    align-items: center;
    min-width: 0;
    height: 100%;
}
/* §16: Source is the actual search router (1688/Taobao/Tmall), not a visual filter — see
   initSourceSelector() in theme.js and selected_source()/gateway_platform_for_source() in
   class-chinimall-frontend.php. Only a subtle separator, no heavy border, between it and the
   input (§18). */
.chinimall-source-dropdown {
    position: relative;
    flex-shrink: 0;
    height: 100%;
}
.chinimall-source-btn {
    all: unset;
    /* `all: unset` above resets font-family too, along with everything else — left on the
       browser's own UA default button font (not this theme's font stack at all) unless
       restored explicitly right after, which is why this looked visibly rougher than the
       search input next to it (owner, 2026-09-09: "فونت دراپ‌دان به نسبت باکس سرچ تمیزتر" —
       the input was never affected since a plain <input> doesn't need `all: unset` reset). */
    font-family: inherit;
    box-sizing: border-box;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 4px;
    width: 134px;
    height: 100%;
    border-right: 1px solid var(--chinimall-border);
    border-radius: 999px 0 0 999px;
    font-size: 14px;
    font-weight: 600;
    color: var(--chinimall-ink);
    padding: 0 8px 0 16px;
    cursor: pointer;
}
/* Taobao/Tmall merged into one option (2026-09-09) — its label is longer than "1688"/"JD.com",
   so the button's own text needs to truncate cleanly instead of wrapping/overflowing. */
#chinimall-source-btn-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chinimall-source-btn svg { width: 14px; height: 14px; flex-shrink: 0; color: var(--chinimall-muted); transition: transform .15s ease; }
.chinimall-source-btn[aria-expanded="true"] svg { transform: rotate(180deg); }
/* The open listbox — real CSS this time, not the OS's own native <select> popup chrome (see
   chinimall_search_bar()'s own comment on why the native element was replaced). */
.chinimall-source-list {
    position: absolute;
    top: calc(100% + 6px);
    left: 0;
    min-width: 150px;
    margin: 0;
    padding: 6px;
    list-style: none;
    background: #fff;
    border: 1px solid var(--chinimall-border);
    border-radius: 12px;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
    z-index: 60;
    font-family: inherit;
}
.chinimall-source-list[hidden] { display: none; }
/* Final Adjustment spec §6-7/§10 — item height 40-44px, hover/focus a flat #F5F5F5 (never the
   browser's own blue selection color or a heavy fill), selected marked by a ✓ alone (added via
   ::after, no markup change) plus a small weight bump — never a background fill, so a picked
   Source doesn't read as "still highlighted/hovered". */
.chinimall-source-list li {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    padding: 11px 14px;
    border-radius: 8px;
    font-size: 14px;
    color: var(--chinimall-ink);
    cursor: pointer;
}
.chinimall-source-list li:hover,
.chinimall-source-list li:focus-visible {
    background: #f5f5f5;
    outline: none;
}
.chinimall-source-list li.is-selected { font-weight: 600; }
.chinimall-source-list li.is-selected::after { content: "✓"; font-weight: 700; }
.chinimall-hero-input {
    flex: 1;
    border: none;
    outline: none;
    background: transparent;
    padding: 0 8px 0 12px;
    /* iOS Safari auto-zooms the whole page when a focused text input's font-size is under
       16px (its heuristic for "too small to read comfortably") — exactly the "page stretches,
       needs a pinch to fix" symptom reported after pasting into this field on an iPhone. 16px
       is the fix, not a design choice — going smaller re-introduces the bug. */
    font-size: 16px;
    min-width: 0;
    height: 100%;
}
/* §14 (owner request, 2026-09-10): swapped with the camera trigger below — Search moved INSIDE
   the box, at its end, icon-only always (no label span left in the markup at all anymore, see
   chinimall_search_bar()'s own comment) — same small round-icon treatment
   .chinimall-camera-icon-btn used to have in this exact spot, just on a real, always-clickable
   <button type="submit"> instead of a decorative icon under an invisible file input. */
.chinimall-search-btn {
    flex-shrink: 0;
    width: 36px;
    height: 36px;
    margin-right: 6px;
    border: none;
    border-radius: 50%;
    background: transparent;
    color: var(--chinimall-muted);
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
}
.chinimall-search-btn:hover { background: #f0f0f0; color: var(--chinimall-ink); }
.chinimall-search-btn svg { width: 20px; height: 20px; }

/* §14 (owner request, 2026-09-10): camera trigger moved OUTSIDE the search box, into the same
   visual slot (a round button beside the box) the Search button used to occupy — same accent-
   filled circle treatment .chinimall-search-btn used to have there. Still the plugin's own
   [chinimall_image_search] form; the invisible file input over a decorative icon underneath is
   unchanged, just resized/recentered for this larger 48px circle instead of the 36px slot it
   used to sit in inside the box. */
/* Owner feedback, 2026-09-10: the solid accent-black fill (originally borrowed from the old
   Search button's own "primary CTA" look) read as too heavy/large once Search itself moved to
   a plain icon inside the box — a light outline circle, same border color as the search box
   itself and closer to the icon's own size, matches the box's own visual weight instead of
   competing with it. */
.chinimall-camera-trigger {
    position: relative;
    flex-shrink: 0;
    align-self: center;
    width: 40px;
    height: 40px;
    border-radius: 50%;
    border: 1px solid var(--chinimall-border);
    background: #fff;
    display: flex;
    align-items: center;
    justify-content: center;
}
.chinimall-camera-trigger:hover { border-color: #bbb; }
/* Real bug found live (2026-09-10, mobile): this class's own `display: flex` above overrides
   the browser's default `[hidden] { display: none }` UA rule (author styles always win over
   the UA stylesheet, regardless of specificity/source order), so the [hidden] attribute
   initSourceSelector() sets in theme.js for JD (no working image search on this account) had
   no visual effect — the icon stayed clickable. Higher-specificity selector restores it. */
.chinimall-camera-trigger[hidden] {
    display: none;
}
.chinimall-camera-trigger .cm-image-search-form {
    margin: 0;
    width: 100%;
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
}
.chinimall-camera-trigger .cm-image-search-form button[type="submit"] { display: none; }
.chinimall-camera-trigger .cm-image-search-form input[type="file"] {
    position: absolute;
    inset: 0;
    opacity: 0;
    cursor: pointer;
    /* The plugin's own frontend.css sets `flex: 1 1 0%; min-width: 200px;` on this same input
       for its default standalone layout — min-width always wins as a floor over a smaller
       `width` regardless of selector specificity, so without overriding it here too, this
       invisible (opacity:0) input silently stayed 200px wide and pushed the whole homepage
       503px past the viewport on mobile with no visual sign anything was wrong (it's
       invisible). flex: none neutralizes the plugin's flex-basis/grow too. */
    flex: none;
    min-width: 0;
    width: 100%;
    height: 100%;
}
.chinimall-camera-icon-btn {
    width: 100%;
    height: 100%;
    border-radius: 50%;
    border: none;
    background: transparent;
    color: var(--chinimall-muted);
    display: flex;
    align-items: center;
    justify-content: center;
    pointer-events: none;
}
.chinimall-camera-icon-btn svg { width: 18px; height: 18px; }

/* theme.js routes a successful photo search to the search-results page (see
   initCameraTrigger()) so results never render back into this small icon area. This is only
   a fallback for the rare case JS didn't run before submit (the plugin's own form still posts
   to itself otherwise) — without it, a preview/results/error would try to render inside a
   36px-wide flex item and break the search box layout on both mobile and desktop. */
.chinimall-camera-trigger .cm-image-search-preview,
.chinimall-camera-trigger .cm-cat-products,
.chinimall-camera-trigger .cm-notice {
    position: absolute;
    top: 100%;
    right: 0;
    left: auto;
    margin-top: 10px;
    width: max-content;
    max-width: 320px;
}

/* §20/§21: the search row must stay one line at every width — no 2-row mobile fallback (the
   previous session's own fix for a similar overflow problem is explicitly rejected here).
   Compact the Source select and Search button instead of wrapping.
   Final Adjustment spec §2/§9: a phone has no room for the button's own label at all — "Search"
   or "AI Search" — so it becomes a plain icon button instead of trying to shrink the text. */
@media (max-width: 480px) {
    /* Real bug found live (2026-09-10): the CLOSED button's own label truncated "Taobao/Tmall"
       (the longest option) with an ellipsis on mobile. Owner's own preferred fix (rather than
       widening this, which would crowd the input field): the closed button shows a shorter
       "Taobao" label instead (see $source_button_labels in chinimall_search_bar()/theme.js's
       own setValue()) — the full "Taobao/Tmall" stays in the open dropdown list, where there's
       real room for it. 80px comfortably fits every button label now, including "Taobao". */
    .chinimall-source-btn { width: 80px; font-size: 12px; padding-left: 10px; }
    .chinimall-hero-input { padding: 0 4px 0 8px; }
    /* §14 (owner request, 2026-09-10): .chinimall-search-btn is icon-only at every width now
       (see its own base rule above) — nothing left to compact here. .chinimall-camera-trigger
       (the round button outside the box) stays its own fixed 48px at every width too. */
}

/* Shown the instant a photo is chosen (see showSearchingOverlay() in theme.js), covering the
   upload + live photo-search round-trip until the browser navigates to the results page. */
.chinimall-searching-overlay {
    position: fixed;
    inset: 0;
    background: rgba(255, 255, 255, 0.9);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 9999;
}
.chinimall-searching-box {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 16px 22px;
    background: #fff;
    border: 1px solid var(--chinimall-border);
    border-radius: var(--chinimall-radius);
    font-size: 14px;
    color: var(--chinimall-ink);
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
}
.chinimall-spinner {
    width: 18px;
    height: 18px;
    border: 2px solid var(--chinimall-border);
    border-top-color: var(--chinimall-ink);
    border-radius: 50%;
    flex-shrink: 0;
    animation: chinimall-spin 0.7s linear infinite;
}
@keyframes chinimall-spin {
    to { transform: rotate(360deg); }
}

/* Real bug found live (2026-09-10): this never set its own font-weight/line-height/family,
   so it just inherited the browser's own default <h2> styling. Pinned explicitly so every
   section title (Popular, Recently viewed, each Favorites source group) renders identically
   on mobile and desktop. */
.chinimall-section-title {
    font-size: 20px;
    font-weight: 700;
    line-height: 1.3;
    margin: 0 0 18px;
    display: flex;
    align-items: center;
    gap: 8px;
}
/* Owner, 2026-09-10: no colored emoji anywhere — black/white/gray only, the one deliberate
   exception in the whole design system being the red heart (.cm-heart). A plain stroke icon,
   same style as the bottom nav's own SVGs, sits before Home's two section titles instead. */
.chinimall-section-title-icon { width: 20px; height: 20px; flex-shrink: 0; }
/* Owner request, item 9 (2026-09-10) — "View all" sits inline with a section's own title
   (Popular, Recently viewed) instead of the title alone; the wrapper takes over the bottom
   margin .chinimall-section-title had on its own so the two don't stack. */
.cm-section-header { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 18px; }
.cm-section-header .chinimall-section-title { margin-bottom: 0; }
.cm-view-all-link { flex-shrink: 0; font-size: 13px; color: #555; text-decoration: none; }
.cm-view-all-link:hover { color: #1e1e1e; text-decoration: underline; }

/* docs/UI-UX-SPEC.md §1/§6: the one deliberate exception to the black/white/gray-only rule
   in the entire design system. Not wired to any save/toggle logic yet (that's the Product
   Card work in a later phase) — defined now so every later phase reuses this one class
   pair instead of inventing its own heart styling.
   Owner request, 2026-09-17: unsaved fill was `none` (see-through) — now that the product-card
   heart button has no white backdrop of its own (removed per an earlier request), an outline-
   only heart sat directly on the product photo with nothing behind it, hard to read against a
   busy/colorful image. White fill gives the shape itself a solid, legible silhouette on any
   photo without reintroducing a separate background circle. */
.cm-heart {
    width: 20px;
    height: 20px;
    fill: #fff;
    stroke: var(--chinimall-ink);
    stroke-width: 1.8;
}
.cm-heart.is-saved {
    fill: #e0263f;
    stroke: #e0263f;
}

/* Recently viewed — populated client-side from localStorage, see theme.js. Reuses the
   plugin's own .cm-cat-product(s) grid classes/styling for a consistent tile look. */
.chinimall-recently-viewed-section { padding: 32px 0 0; }

/* Favorites page (docs/UI-UX-SPEC.md §7) — one heading + grid per Source, built client-side
   by renderFavoritesPageIfPresent() in theme.js. */
.cm-favorites-group + .cm-favorites-group { margin-top: 32px; }

/* Fixed bottom nav — docs/UI-UX-SPEC.md §3: same five items, same order, same fixed
   style, shown at every viewport width (not just mobile) so the header never needs its
   own Account/Cart links back on desktop, and the nav truly "never moves between pages". */
.chinimall-bottom-nav {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 60;
    display: flex;
    background: #fff;
    border-top: 1px solid var(--chinimall-border);
    padding: 6px 6px calc(6px + env(safe-area-inset-bottom));
    max-width: var(--chinimall-max-width);
    margin: 0 auto;
}
.chinimall-bottom-nav-item {
    position: relative;
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 2px;
    padding: 6px 4px;
    font-size: 11px;
    color: var(--chinimall-muted);
}
.chinimall-bottom-nav-item svg { width: 22px; height: 22px; }
.chinimall-bottom-nav-item.is-active,
.chinimall-bottom-nav-item:hover { color: var(--chinimall-accent); }
/* Real bug found live (2026-09-10): the badge used to anchor to the whole nav item (`right:
   22%` of it), which is only ~22px next to the icon at mobile width but ~200px away on a wide
   desktop item — far enough to read as unrelated to the Cart icon. Anchored to this small,
   fixed-size wrapper instead so the offset is the same real distance from the icon everywhere. */
.chinimall-bottom-nav-icon-wrap { position: relative; display: inline-flex; }
.chinimall-bottom-nav-badge {
    position: absolute;
    top: -6px;
    right: -10px;
    background: var(--chinimall-accent);
    color: #fff;
    font-style: normal;
    font-size: 10px;
    font-weight: 700;
    line-height: 1;
    padding: 2px 5px;
    border-radius: 999px;
}

/* Search-results and Category pages: hide the embedded shortcodes' own inline search/upload
   boxes — the homepage hero is the single entry point for this theme. Each of these pages
   also carries [chinimall_lookup] purely so a tile's ?cm_preview=/?cm_url= click
   (always built against the page it's clicked from) has something to render — its search box
   is what this hides too. */
body.chinimall-hide-inline-search .cm-frontend-search,
body.chinimall-hide-inline-search .cm-search > .cm-frontend-search,
body.chinimall-hide-inline-search .cm-image-search-form {
    display: none;
}
/* Image Search Results spec item 1 (2026-09-10): this row sat right under the header with a
   full 20px top margin plus the header's own bottom padding, reading as one big gap before
   anything useful (the searched-photo thumbnail, results) appeared. Tightened to match the
   thumbnail row right below it. */
.chinimall-back-to-search { margin: 10px 0; font-size: 14px; display: flex; flex-wrap: wrap; align-items: baseline; gap: 12px; }
.chinimall-search-query { color: var(--chinimall-muted); }

/* Generic content areas */
.chinimall-main { min-height: 40vh; }
.chinimall-page-title { font-size: 26px; margin: 30px 0 16px; }
.chinimall-page-content { padding-bottom: 40px; }
.chinimall-post + .chinimall-post { margin-top: 28px; padding-top: 28px; border-top: 1px solid var(--chinimall-border); }

/* Staged loading overlay (Image Search Results spec items 10-11, 2026-09-10) — shown by
   theme.js's initLoadingOverlay() during the upload->search->translate navigation and while a
   Sort change is loading, so a full-page reload never looks like nothing happened. */
.cm-loading-overlay {
    position: fixed;
    inset: 0;
    z-index: 200;
    background: rgba(255, 255, 255, 0.85);
    display: flex;
    align-items: center;
    justify-content: center;
}
/* Same real bug already fixed once for .chinimall-camera-trigger (see that rule's own comment,
   further up this file): `display: flex` above is author CSS, which always wins over the UA
   stylesheet's `[hidden] { display: none }` regardless of specificity — without this, the
   overlay rendered visible on every single page load instead of only when JS shows it. */
.cm-loading-overlay[hidden] {
    display: none;
}
.cm-loading-box { display: flex; flex-direction: column; align-items: center; gap: 12px; font-size: 14px; color: var(--chinimall-ink); }
.cm-spinner {
    width: 28px;
    height: 28px;
    border: 3px solid var(--chinimall-border);
    border-top-color: var(--chinimall-accent);
    border-radius: 50%;
    animation: cm-spin 0.8s linear infinite;
}
@keyframes cm-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
    .cm-spinner { animation-duration: 2.4s; }
}

/* Footer */
.chinimall-footer {
    border-top: 1px solid var(--chinimall-border);
    padding: 24px 0;
    margin-top: 40px;
    color: var(--chinimall-muted);
    font-size: 13px;
}

/* Account (docs/UI-UX-SPEC.md §11) — WooCommerce's own tab bar is emptied out (see
   woocommerce/myaccount/navigation.php) so this flat menu list, plus the bottom nav, is the
   only Account-area navigation. */
.woocommerce-MyAccount-content { width: 100% !important; float: none !important; }
.cm-account-menu { list-style: none; margin: 20px 0 0; padding: 0; border-top: 1px solid var(--chinimall-border); }
.cm-account-menu li { border-bottom: 1px solid var(--chinimall-border); }
.cm-account-menu a { display: block; padding: 16px 4px; font-size: 15px; color: var(--chinimall-ink); }
.cm-account-menu a:hover { background: var(--chinimall-bg-alt); }
/* docs/UI-UX-SPEC.md §12.6 — text-only alignment for Persian/Arabic, scoped narrowly to the
   Account menu and its stub pages (see the CHINIMALL_I18n::is_rtl_locale() checks in
   woocommerce/myaccount/dashboard.php and page.php). Never a site-wide RTL switch. */
.cm-account-menu[dir="rtl"] a { text-align: right; }
.chinimall-page-content[dir="rtl"] { text-align: right; }

/* Cart page — docs/UI-UX-SPEC.md §9, overrides woocommerce/cart/cart.php (see
   chinimall-theme/woocommerce/cart/cart.php). Boxed cards, not WooCommerce's default table. */
.chinimall-empty-state { padding: 40px 0; text-align: center; color: var(--chinimall-muted); font-size: 15px; }
#chinimall-cart-root.is-updating { opacity: 0.6; pointer-events: none; }
/* Cart header (owner spec, 2026-09-15; "Clear Cart" removed 2026-09-17 — a whole-cart wipe was
   one accidental click away with no real recovery) — replaces page.php's own plain "Cart" <h1>
   on this page only (see the is_cart() guard there). */
.cm-cart-header { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 16px; }
.cm-cart-heading { margin: 0; font-size: 20px; font-weight: 700; color: var(--chinimall-ink); }
/* Owner request, 2026-09-17: a bit larger specifically on mobile. */
@media (max-width: 600px) {
    .cm-cart-heading { font-size: 24px; font-weight: 600; }
}
/* Screen-reader-only utility — same "clip" pattern WordPress core's own .screen-reader-text
   uses, kept local to this theme rather than assuming core's admin-facing class is enqueued on
   the frontend. */
.chinimall-visually-hidden { position: absolute !important; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
.cm-cart-items { display: flex; flex-direction: column; gap: 12px; margin: 20px 0; }
/* Vendor grouping (2026-09-14, boxed; made unconditional 2026-09-15 per the owner's cart spec —
   every store gets its own boxed group with a clickable header now, even a single-vendor cart).
   .cm-cart-vendor-group is ONE outer box wrapping the vendor name + every .cm-cart-item card
   that belongs to it, so the whole group reads as "all of this is one vendor" rather than a
   header floating above separate cards. Items inside keep their own .cm-cart-item border too
   (nested boxes) — the owner's own screenshot showed per-item boxes already looking right, only
   asked for an outer box to also wrap them together. */
.cm-cart-vendor-group {
    border: 1px solid var(--chinimall-border);
    border-radius: var(--chinimall-radius);
    padding: 12px;
    display: flex;
    flex-direction: column;
    gap: 12px;
    margin-bottom: 4px;
}
/* Owner spec, 2026-09-17: selection moved from (non-existent) per-product controls to one
   circle per Seller Box, so the header row now holds the circle PLUS the header link/text as
   two siblings — a <label>+<input> can't legally nest inside the header's own <a> (when the
   header is a link to the vendor's shop), hence this extra wrapper row instead of putting the
   circle inside .cm-cart-vendor-header itself. */
.cm-cart-vendor-header-row { display: flex; align-items: center; gap: 10px; }
.cm-cart-vendor-select { display: flex; align-items: center; flex-shrink: 0; cursor: pointer; width: 22px; height: 22px; }
.cm-cart-vendor-select input { position: absolute; opacity: 0; width: 22px; height: 22px; margin: 0; cursor: pointer; }
/* Redesigned 2026-09-17 (owner feedback: "must not look like a standard radio button", selected/
   unselected states needed to be clearer) — a rounded SQUARE, not a circle (a shape no native
   form control uses, so it can't read as a plain radio at a glance), plus an explicit checkmark
   when selected rather than relying on a filled-vs-empty dot alone to carry the meaning. Plain
   black/white/gray, no accent color — same rule as everywhere else on this site (the saved-heart
   red is the one deliberate exception, not this). */
.cm-cart-vendor-select-dot { position: relative; width: 22px; height: 22px; border-radius: 6px; border: 2px solid var(--chinimall-border); box-sizing: border-box; flex-shrink: 0; background: #fff; }
.cm-cart-vendor-select input:checked ~ .cm-cart-vendor-select-dot { border-color: var(--chinimall-ink); background: var(--chinimall-ink); }
.cm-cart-vendor-select input:checked ~ .cm-cart-vendor-select-dot::after {
    content: '';
    position: absolute;
    left: 6px;
    top: 2px;
    width: 6px;
    height: 11px;
    border: solid #fff;
    border-width: 0 2px 2px 0;
    transform: rotate(45deg);
}
.cm-cart-vendor-select input:focus-visible ~ .cm-cart-vendor-select-dot { outline: 2px solid var(--chinimall-ink); outline-offset: 2px; }
.cm-cart-vendor-header {
    display: flex;
    align-items: center;
    gap: 8px;
    flex: 1 1 0%;
    min-width: 0;
    color: var(--chinimall-ink);
    text-decoration: none;
}
a.cm-cart-vendor-header:hover .cm-cart-vendor-name { text-decoration: underline; }
.cm-cart-vendor-name { font-size: 14px; font-weight: 600; }
/* Plain text, not a link/button affordance (owner feedback, 2026-09-17: "1 item >" used to read
   like the whole header was clickable navigation — selection now happens via the circle, not by
   clicking a count). */
.cm-cart-vendor-count { margin-left: auto; font-size: 12px; color: var(--chinimall-muted); }
/* A Seller Box whose circle is unchecked stays fully visible/editable (qty, remove, etc. all
   still work — it just won't be part of the next Request) but dims slightly so the selected
   state is easy to scan at a glance. */
.cm-cart-vendor-group:has(.cm-vendor-checkbox:not(:checked)) { opacity: 0.55; }
.cm-cart-item {
    border: 1px solid var(--chinimall-border);
    border-radius: var(--chinimall-radius);
    padding: 10px 12px;
}
.cm-cart-item-top { display: flex; align-items: flex-start; gap: 12px; }
/* Owner spec, 2026-09-15: clicking anywhere on the row (image or text) opens the product page —
   previously only the image and the title text were separately clickable, the row itself did
   nothing. One <a> now wraps image+body instead of two smaller ones inside it. */
.cm-cart-item-link { flex: 1 1 0%; min-width: 0; display: flex; align-items: flex-start; gap: 12px; color: inherit; text-decoration: none; }
/* Owner request, 2026-09-17: was 64px, hard to make out the actual product on a small phone
   screen — sized up so the photo itself carries more of the "which product is this" job. */
.cm-cart-item-image { flex-shrink: 0; display: block; width: 84px; height: 84px; border-radius: 6px; overflow: hidden; }
.cm-cart-item-image img { width: 100%; height: 100%; object-fit: cover; display: block; }
.cm-cart-item-body { flex: 1 1 0%; min-width: 0; }
/* Owner spec, 2026-09-15: source badge + Product ID now lead the row (line 1) — the ID used to
   land near the bottom of the block, after the title/variant, which read as buried/unordered. */
.cm-cart-item-meta { display: flex; align-items: center; gap: 6px; margin-bottom: 4px; }
.cm-cart-item-meta .cm-source-badge { position: static; }
.cm-cart-item-id { font-size: 11px; color: var(--chinimall-muted); }
/* 2 lines, not 3 (owner, 2026-09-09 — same "max 2 lines + ellipsis" rule as the product page's
   own .cm-title) — 3 lines left a lot of dead space to the right of a typically-shorter cart
   line title, which read as an unbalanced row. */
.cm-cart-item-title { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; font-size: 14px; line-height: 1.4; color: var(--chinimall-ink); }
/* Owner spec, 2026-09-17 ("Selected Specifications" row, max 1-2 lines): line-clamped so a
   product with many attributes (color/size/model/plug, etc.) never pushes the card taller than
   its neighbors — the same reasoning as .cm-cart-item-title's own 2-line clamp just above. */
.cm-cart-item-variant { margin-top: 4px; font-size: 12px; color: var(--chinimall-muted); display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.cm-cart-item-variant p { margin: 0; display: inline; }
/* Real bug found live (2026-09-10, previously reported and still unfixed): this wraps
   wc_get_formatted_cart_item_data()'s own raw <dl><dt>Weight:</dt><dd><p>...</p></dd></dl>
   markup — only the <p>'s margin was ever reset above, so the browser's default <dl>/<dd>
   user-agent margins (about 1em top+bottom on <dl>, 40px left indent on <dd>) were still
   rendering as real, visible empty space in every cart line that has a variant/weight row.
   dt/dd side by side reads better here than the browser default block-stacked layout anyway. */
.cm-cart-item-variant dl { margin: 0; }
.cm-cart-item-variant dt, .cm-cart-item-variant dd { margin: 0; padding: 0; display: inline; }
.cm-cart-item-variant dt::after { content: " "; }
.cm-cart-item-variant dd:not(:last-child)::after { content: ", "; }
/* Weight/Dimensions (owner request, 2026-09-17) — same tone/size as the Product ID line above,
   not competing visually with the title or the Selected Specifications line. */
.cm-cart-item-dims { margin-top: 2px; font-size: 11px; color: var(--chinimall-muted); }
/* Calm, non-scary inline status (owner spec, 2026-09-17) replacing WooCommerce's own raw "not in
   stock"/"not enough stock" notices (suppressed site-wide for this page — see
   CHINIMALL_Frontend::suppress_cart_error_notices()) — same soft-amber tone as .cm-notice-warning
   elsewhere on the site, not the red/error language a failed purchase would use. The item this
   sits under always stays in the cart regardless. */
.cm-cart-item-availability { margin-top: 4px; font-size: 12px; font-weight: 600; color: #7a5b00; }
.cm-cart-item-remove {
    /* docs/UI-UX-SPEC.md §38 — 40px real touch area; margin pulls the card's own edge back in
       so the larger tap target doesn't visually widen the card. Trash icon (owner spec,
       2026-09-15) replaces the old plain "x" character for a clearer, more obvious delete
       affordance. */
    flex-shrink: 0;
    width: 40px;
    height: 40px;
    margin: -6px -6px 0 0;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    color: var(--chinimall-muted);
    text-decoration: none;
}
.cm-cart-item-remove svg { width: 18px; height: 18px; }
.cm-cart-item-remove:hover { background: var(--chinimall-bg-alt); color: var(--chinimall-ink); }
/* Price/Quantity polish (owner feedback, 2026-09-15 — "still feels like a separate layer below
   the product, too much gap, card is taller than it needs to be"). Divider removed entirely
   (the owner's own "not essential" option) — proximity (tight margin, no top border) now reads
   as "this belongs to the product above it" instead of a second detached row. */
.cm-cart-item-bottom { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-top: 8px; }
.cm-cart-item-price { font-size: 14px; font-weight: 600; }
/* Cart-scoped compact stepper — same 30x32 sizing already established for the mobile Buy Bar
   (see .cm-buy-bar .cm-qty-btn in frontend.css) reused here for consistency, not invented fresh.
   Plus/Minus keep a real tap height (32px) per docs/UI-UX-SPEC.md §38 ("visual icon can be
   small, the touch area can't") — only the qty number field itself (never tapped for its own
   sake) shrinks further, and the whole control's border/font get lighter so it reads as part of
   this compact row instead of a bulkier, separate widget next to a now-smaller price. */
.cm-cart-item-bottom .cm-qty-stepper { border-color: var(--chinimall-border); }
.cm-cart-item-bottom .cm-qty-btn { width: 30px; min-width: 30px; height: 32px; font-size: 15px; }
/* Real bug found live 2026-09-17 (owner screenshot, real cart with quantities like 100-200 —
   already visibly clipped at just 3 digits, and unusable well before a real B2B order quantity
   in the 10,000+ range): this 32px box was never widened for digit count the way the product
   page's own qty input already was (see .cm-buy-bar .cm-qty-stepper .cm-qty in frontend.css,
   fixed for the exact same reason) — and unlike that one, this rule was never scoped to mobile
   only, so it was undersized on every screen size, not just small ones. Widened to fit six
   digits comfortably (a real wholesale cart line can run into the tens of thousands). font-size
   14px -> 16px fixes a second bug the same box had: under 16px is iOS Safari's own auto-zoom
   threshold on a focused input (the exact same fix already applied in several other places in
   this project — see e.g. .cm-add-form select's own comment). */
.cm-cart-item-bottom .cm-qty-stepper .cm-qty { width: 66px; height: 32px; font-size: 16px; }

.cm-cart-summary-bar {
    position: sticky;
    bottom: calc(58px + env(safe-area-inset-bottom));
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    padding: 14px 16px;
    background: #fff;
    border: 1px solid var(--chinimall-border);
    border-radius: var(--chinimall-radius);
    margin-top: 8px;
}
/* Owner spec, 2026-09-17: this bar now reports only the selected Seller Box(es) — "sellers"
   line above the amount, both recomputed live by initCartSelection() (theme.js) whenever a
   circle is toggled. */
.cm-cart-summary-info { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.cm-cart-summary-sellers { font-size: 12px; color: var(--chinimall-muted); }
.cm-cart-summary-total { font-size: 16px; font-weight: 700; }
.cm-cart-summary-cta { flex-shrink: 0; background: var(--chinimall-accent); color: #fff; padding: 12px 22px; border-radius: var(--chinimall-radius); font-size: 15px; font-weight: 600; text-decoration: none; }
.cm-cart-summary-cta:hover { background: var(--chinimall-accent-hover); }
.cm-cart-summary-cta.is-disabled { background: var(--chinimall-muted); pointer-events: none; }

/* Owner feedback, 2026-09-17: the desktop-only row restructuring (image+info beside price+qty,
   bigger thumbnail) read as broken ("گند زدی به همه جا در دسکتاپ") — reverted. The item card's
   price/qty display is now identical at every width: the same stacked .cm-cart-item-top /
   .cm-cart-item-bottom layout tablet and mobile already use, with no desktop-only override left.
   Seller Box gap is likewise the same 12px at every width now (the earlier desktop-only 8px
   tightening is gone too). */

/* WooCommerce single product page — added 2026-09-06. This theme deliberately has no
   single-product.php of its own (see functions.php's comment on chinimall_woocommerce_wrapper_*),
   so WooCommerce renders its own default markup/classes; without this, a real WooCommerce
   product page (reached by adding a 1688-looked-up item to cart, which imports it as a real
   product) looked nothing like the rest of the site — WooCommerce's own default purple
   buttons/orange price, no relation to the minimal black/white look everywhere else. This
   restyles WooCommerce's own stable class names rather than replacing the template, so
   product-swatches.js's variation-form reskin (which depends on WooCommerce's own real
   <select>/form markup staying intact) keeps working unmodified. */
.woocommerce div.product .product_title {
    font-size: 22px;
    margin: 0 0 6px;
    line-height: 1.3;
}
.woocommerce div.product p.price,
.woocommerce div.product span.price {
    color: var(--chinimall-ink);
    font-size: 26px;
    font-weight: 600;
}
.woocommerce div.product p.price ins,
.woocommerce div.product span.price ins { text-decoration: none; }
.woocommerce nav.woocommerce-breadcrumb {
    color: var(--chinimall-muted);
    font-size: 13px;
    margin: 0 0 16px;
}
.woocommerce nav.woocommerce-breadcrumb a { color: var(--chinimall-muted); }
.woocommerce div.product form.cart .button,
.woocommerce #respond input#submit,
.woocommerce a.button,
.woocommerce button.button,
.woocommerce input.button {
    background: var(--chinimall-accent);
    color: #fff;
    border: none;
    border-radius: 4px;
    padding: 12px 20px;
    font-size: 15px;
    font-weight: 600;
    cursor: pointer;
}
.woocommerce div.product form.cart .button:hover,
.woocommerce a.button:hover,
.woocommerce button.button:hover,
.woocommerce input.button:hover {
    background: var(--chinimall-accent-hover);
    color: #fff;
}
.woocommerce div.product form.cart .button.disabled,
.woocommerce div.product form.cart .button:disabled {
    background: #ccc;
    cursor: not-allowed;
}
.woocommerce div.product .quantity input.qty {
    border: 1px solid var(--chinimall-border);
    border-radius: 4px;
    padding: 10px;
    width: 64px;
}
.woocommerce div.product table.variations select {
    border: 1px solid var(--chinimall-border);
    border-radius: 4px;
    padding: 8px 10px;
    font-size: 14px;
    min-width: 160px;
}
.woocommerce div.product table.variations td.label { font-weight: 600; padding-bottom: 6px; }
.woocommerce div.product table.variations tr { display: block; margin-bottom: 10px; }
/* Tabs (Description / Additional information / Reviews) restyled to match .cm-tab elsewhere
   on the site — same underlying idea (a plain button-row with an active underline), just
   applied to WooCommerce's own tab markup instead of the custom lookup page's. */
.woocommerce div.product .woocommerce-tabs ul.tabs {
    display: flex;
    gap: 4px;
    list-style: none;
    margin: 0 0 16px;
    padding: 0;
    border-bottom: 1px solid var(--chinimall-border);
}
.woocommerce div.product .woocommerce-tabs ul.tabs li { margin: 0; padding: 0; }
.woocommerce div.product .woocommerce-tabs ul.tabs li::before,
.woocommerce div.product .woocommerce-tabs ul.tabs li::after { content: none !important; }
.woocommerce div.product .woocommerce-tabs ul.tabs li a {
    display: block;
    padding: 10px 18px;
    font-size: 14px;
    font-weight: 600;
    color: var(--chinimall-muted);
    border-bottom: 2px solid transparent;
    margin-bottom: -1px;
}
.woocommerce div.product .woocommerce-tabs ul.tabs li.active a,
.woocommerce div.product .woocommerce-tabs ul.tabs li a:hover {
    color: var(--chinimall-ink);
    border-bottom-color: var(--chinimall-ink);
}
/* The informational 1688 quantity-tier-price table (CHINIMALL_Importer::tier_price_table_html())
   lives inside the short description, right under Add to Cart — a plain <table> with no class
   of its own, styled generically here rather than editing the PHP that generates it. */
.woocommerce div.product .woocommerce-product-details__short-description table,
.woocommerce div.product .woocommerce-Tabs-panel table {
    width: 100%;
    border-collapse: collapse;
    margin: 8px 0 16px;
}
.woocommerce div.product .woocommerce-product-details__short-description table th,
.woocommerce div.product .woocommerce-product-details__short-description table td,
.woocommerce div.product .woocommerce-Tabs-panel table th,
.woocommerce div.product .woocommerce-Tabs-panel table td {
    text-align: left;
    padding: 6px 10px;
    border: 1px solid var(--chinimall-border);
    font-size: 13px;
}
.woocommerce div.product .woocommerce-product-details__short-description em {
    font-size: 12px;
    color: var(--chinimall-muted);
    font-style: normal;
}

/* The bottom nav (all widths, see above) already covers every real destination this site
   has — the copyright footer is just dead chrome under an app-shell layout, so it's never
   shown rather than only hidden on mobile. */
.chinimall-footer { display: none; }

/* Responsive — the old "wrap to 2 rows on a narrow phone" fix that used to live here
   (.chinimall-search-bar/.chinimall-hero-form/.chinimall-hero-input-wrap, all now-dead class
   names) is explicitly rejected by the Header & Multi-Source Search Redesign, 2026-09-07 §20:
   the row must stay one line at every width. See .chinimall-search-row's own @media block
   above (compacting, not wrapping) for the real fix now. */
