/*
Theme Name:        JE Memory Florist
Theme URI:         https://jememoryflowers.my
Author:            yoongshang@gmail.com
Author URI:        mailto:yoongshang@gmail.com
Description:       A warm, editorial WooCommerce theme for JE Memory Florist (Bandar Sri Damansara). Sage-and-cream Malaysian florist palette, Cormorant Garamond + Manrope + Noto Serif SC typography, bilingual EN+中文 ready, FPX checkout, WhatsApp customer-service FAB, mobile-first responsive design.
Version:           1.3.6
Requires at least: 6.4
Tested up to:      6.6
Requires PHP:      7.4
WC requires at least: 8.0
WC tested up to:   10.8
License:           GPL-2.0-or-later
License URI:       https://www.gnu.org/licenses/gpl-2.0.html
Text Domain:       je-memory
Tags:              e-commerce, woocommerce, florist, custom-logo, custom-menu, featured-images, threaded-comments, translation-ready, full-width-template, rtl-language-support

Visual styles live in assets/css/main.css — this file only registers the theme.
*/

/*
Changelog
─────────
1.3.6 — 2026-06-03
  • Custom Order: reworded the post-send success message to
    "Thank you — we've received your request and will contact you shortly
    to bring your arrangement to life." (template default + AJAX response).

1.3.5 — 2026-06-03
  • Custom Order hero: swapped the big photo to a soft pastel hand-tied
    bouquet (custom_hero default), and reworded the first trust chip from
    "Reply within the hour" → "We reply quickly".

1.3.4 — 2026-06-03
  • Custom Order: reworded the form note to reassure the customer we'll
    reach out — "Hit send and leave the rest to us — we'll contact you
    shortly to bring your arrangement to life."

1.3.3 — 2026-06-03
  • Custom Order email: Name / Phone / Email are now grouped under a
    "Contact" sub-heading, separated from the order details list.

1.3.2 — 2026-06-03
  • Custom Order email: now sent as structured HTML so it renders as a
    clean bulleted list (Gmail was collapsing the plain-text newlines onto
    one line). Subject is prefixed with the brand —
    "JE Memory Florist - Custom order request — {name} ({occasion})".
    Customer email stays as Reply-To.

1.3.1 — 2026-06-03
  • Custom Order refinements:
    – Recipient gender + Main colour options are now English-only (dropped
      the 中文 sub-labels on the chips/swatches and from their submitted
      values); field titles keep their small 中文 accent for consistency.
    – Request-note copy no longer exposes the studio email address
      (sends in the background): "Pressing send delivers your request
      straight to our studio — no email app needed."
  • Chore: bump JEMEM_VERSION → 1.3.1 to cache-bust main.css/main.js.

1.3.0 — 2026-06-02
  • NEW: Custom Order page — a guided bespoke-order request form matching
    the theme's editorial language, auto-provisioned at /custom-order/ on
    activation.
    – page-custom-order.php — Template: "Custom Order Page".
    – template-parts/custom-order.php — hero + 3-step process + the
      request form (occasion, recipient, recipient-gender chips, delivery
      date [min today+3], delivery area w/ datalist, budget chips, main-
      colour swatches, specific-flowers + gift-card message [120-char live
      counter, serif italic], name/phone/email) + a confirmation dialog
      summarising the request + pricing-tier guidance.
      English-led with light 中文 accents; all strings translation-ready.
    – Server-side send: the form POSTs over admin-ajax
      (wp_ajax[_nopriv]_jemem_custom_order → jemem_handle_custom_order())
      and the studio is emailed via wp_mail() with the customer's email
      as Reply-To — NO customer email app involved. Nonce-checked,
      required-field validated, with a honeypot. The page shows an inline
      success state on send and falls back to WhatsApp on failure.
      NOTE: deliverability depends on the site being able to send mail —
      install an SMTP plugin (e.g. WP Mail SMTP) if wp_mail() isn't
      configured. Studio inbox: Appearance → Customize → Brand & Contact
      → Email.
    – assets/css/main.css — .co-* / .pricing / .tier / dialog styles
      (built on existing warm tokens; no new variables).
    – functions.php — jemem_create_custom_order_page() (idempotent auto-
      provision + self-heal on admin_init; option key
      jemem_custom_order_page_id) and jemem_custom_order_url() helper. New
      Customizer-overridable hero image via jemem_get('custom_hero').
    – header.php + footer.php — nav, mobile drawer and footer "Custom
      Order" links now point to jemem_custom_order_url() (was /#process).
      NOTE: these are FALLBACKS — a menu assigned to the Primary/Mobile
      location overrides them; add a "Custom Order" item there too.
  • Chore: realigned JEMEM_VERSION (was lagging at 1.2.3) with the
    style.css header at 1.3.0 to cache-bust main.css/main.js on the live
    install.

1.2.15 — 2026-06-02
  • Checkout fix (real cause): the gift-message counter stayed at 0
    because the textarea and its wrapping <section> BOTH had
    id="gift_message" — a duplicate id, so getElementById returned the
    section (no .value) and the count never read. Renamed the textarea to
    id="gift_message_input" (name stays "gift_message") and moved the
    counter into the enqueued assets/js/main.js (footer bundle) so a
    JS-optimisation plugin can't strip it. Delegated input listener.

1.2.14 — 2026-06-02
  • Checkout fix: the Gift card message character counter (0/120) stayed
    at 0 while typing. Rewrote the inline counter in form-checkout.php as
    a delegated `input` listener (with a DOMContentLoaded init) so it
    works even when a cache/optimisation plugin defers or relocates the
    inline script, or WooCommerce re-renders the checkout.

1.2.13 — 2026-06-02
  • Checkout fix: the "Gift card message" reused WC's order_comments — the
    same field as the native "Order notes" — so the two collided and one
    overwrote the other on submit. Separated them: Gift card message now
    posts as its own `gift_message` field, saved to `_gift_message` order
    meta and shown on the admin order screen + confirmation email. WC's
    native Order notes (order_comments) is kept enabled for delivery
    instructions and saved as the customer note. Mockup Checkout.html
    gains a distinct "Order notes" field.

1.2.12 — 2026-06-02
  • Fix: the Occasion/Customization fact strip lives in the Description
    tab, but WooCommerce only registers that tab when the product has
    description text — so products with attributes but no description
    showed no Occasion strip (and no Description tab at all). Now
    jemem_register_product_tabs() force-adds the Description tab when
    Occasion or Customization attributes exist, even with empty content.

1.2.11 — 2026-06-02
  • Hotfix: restore the missing closing brace on jemem_specs_strip() in
    functions.php (introduced in 1.2.7). The unclosed function caused a
    fatal "Parse error: Unclosed '{'" / critical error on the live site.

1.2.10 — 2026-06-02
  • Meta: updated theme Author to yoongshang@gmail.com (Author URI
    mailto:yoongshang@gmail.com).

1.2.9 — 2026-06-02
  • SEO quick wins: (1) the FAQ page <title> could show the raw
    "page-faq" template slug — added document_title_parts +
    rank_math/frontend/title filters that replace it with "Frequently
    Asked Questions". (2) The blog (posts page) had an empty meta
    description — added a contextual description for the FAQ and blog
    pages, wired into rank_math/frontend/description (fills only when
    EMPTY, never overrides an editor value) with a plugin-free wp_head
    fallback. New helpers jemem_seo_plugin_active() +
    jemem_meta_description_for_context().
    NOTE: the cleanest real fix for the title is also to rename the page
    from "page-faq" to "FAQ" (Pages → edit), and to set the Blog page's
    description in Rank Math → these filters are the safety net.

1.2.8 — 2026-06-02
  • Fix: footer "Shop" column and the mobile drawer hardcoded Bouquets,
    Grand Opening and Condolences to /shop/ instead of their real
    category archives. Added jemem_cat_url() — resolves a product_cat URL
    from candidate slugs (bouquets / grand-opening-arrangement /
    condolences-arrangements, with aliases) and falls back to the shop
    page only if the term is missing. footer.php + header.php fallbacks
    now use it; Custom Order also corrected to home_url('/#process').
    NOTE: these are FALLBACKS — if a menu is assigned to the "Shop" footer
    location (Appearance → Menus) it overrides this; update that menu's
    links there instead.

1.2.7 — 2026-06-02
  • PDP: switched the product facts from a standalone "At a glance" block
    to facts mixed INTO the tab panels (Option B). Removed the
    priority-25 summary hook; added jemem_specs_strip() and a bilingual
    fact strip at the top of each tab — Description (Occasion +
    Customization), What's included (Flowers + Format & Size), Care &
    delivery (Care + Delivery). Still attribute-driven via
    jemem_product_specs(); Product schema additionalProperty + material
    filter unchanged. .spec-glance styles replaced by .spec-strip in
    main.css; mockup Product.html updated to match.

1.2.6 — 2026-06-02
  • SEO/UX: product copy was poetic but not machine-extractable. Added an
    "At a glance" bilingual spec block to the single-product summary
    (jemem_render_product_specs, hooked at priority 25 — between excerpt
    and add-to-cart) surfacing six fact groups: Occasion · Flowers ·
    Format & Size · Delivery · Care · Customization. Facts are read from
    per-product WooCommerce attributes via jemem_product_specs(); Delivery
    falls back to a store-wide default. Same facts are appended to WC's
    Product schema as additionalProperty + material (filter on
    woocommerce_structured_data_product) so Google & AI answer engines can
    extract them. New .spec-glance styles in main.css; mockup Product.html
    updated with the block + JSON-LD.
    SETUP: add these attributes to each product (Products → edit →
    Attributes; global "pa_" or custom), the helper matches by name
    case-insensitively: Occasion, Flowers, Format (or Size), Delivery
    (optional), Care, Customization.

1.2.5 — 2026-06-02
  • SEO: product category archives (Bouquets, Condolences, Grand Opening)
    were bare product grids with no local-intent content. Added
    jemem_category_seo() — category-aware, KL/Selangor delivery-focused
    copy + FAQs (detected by slug) — rendered below the grid in
    woocommerce/archive-product.php as an intro/body/delivery-areas block
    plus an accordion FAQ, and emitted as FAQPage JSON-LD (additive; Rank
    Math does not auto-generate FAQ schema for archives). The category
    hero lede now also falls back to this localized intro when the term
    has no description. New .cat-seo / .cat-faq styles in main.css. Mockup
    Category.html updated to preview the pattern.

1.2.4 — 2026-06-02
  • Update: full studio address everywhere — now
    "M2-19-06 Menara 2, 8Trium, Lot 62539, SD 23/5, Persiaran Cempaka,
    Bandar Sri Damansara, 52200 Kuala Lumpur". Updated the 'address'
    fallback in template-parts/contact.php and template-parts/faq.php
    (the functions.php Customizer default already carried the full form).
    NOTE: if an address was already saved in the Customizer it overrides
    this default — update it under Appearance → Customize, and update
    Rank Math → Local SEO → Address to match for consistent NAP/schema.

1.2.3 — 2026-06-02
  • Update: Facebook page URL changed from the old profile.php?id= form to
    the page URL
    https://www.facebook.com/p/JE-Memory-Florist-记忆花坊-61566033754137/
    in the Customizer 'facebook' default (drives the footer link site-wide).
    NOTE: if a value was already saved in the Customizer it overrides this
    default — update it under Appearance → Customize as well.

1.2.2 — 2026-06-02
  • Fix: header desktop nav "About" and "Custom Order"/"Contact" used bare
    anchors (#about / #process / #contact). Those only resolve on the
    homepage — on the FAQ, Blog and other pages they pointed at
    /faq/#about etc. (a section that isn't there), so the links went
    nowhere. Now use home_url('/#about') / '/#process' / '/#contact' so
    they jump to the correct homepage section from ANY page. (The mobile
    drawer and footer already did this.)

1.2.1 — 2026-06-02
  • Rename "Journal" → "Blog" everywhere user-facing: nav label, mobile
    drawer, footer, listing eyebrow ("Blog · 手记"), single-post "Back to
    blog" + "More from the blog". Posts page slug is now /blog/ (auto-
    provisioned "Blog" page); helpers renamed jemem_blog_url() /
    jemem_create_blog_page() with option key jemem_blog_page_id.
  • Nav order: Blog now sits next to Contact, and the redundant "Categories"
    item (duplicated Shop's link) was removed for a cleaner, consistent bar:
    Shop · About · Custom Order · FAQ · Blog · Contact. Mockup headers
    (homepage, Blog, Blog Post) updated to match.
  • Note: internal CSS class names (.journal-hero/.journal__grid/…) are
    unchanged — they're invisible to users and renaming them adds only risk.

1.2.0 — 2026-06-02
  • NEW: Journal (blog) — editorial templates matching the theme, so posts
    written in the WP back office render in the studio's visual language.
    – index.php — listing: centered editorial header + responsive card
      grid; the first post on page 1 renders as a wide feature card.
      Doubles as the category / tag / search-results template.
    – single.php — article: centered display title + meta (date · reading
      time · author), rounded banner, a rich `.prose` content column
      (h2/h3, blockquote, lists, figures, code), tag footer, and a
      "More from the journal" related strip (same category).
    – template-parts/post-card.php — reusable editorial post card.
    – assets/css/main.css — `.journal*`, `.jcard*`, `.post*`, `.prose`
      styles (warm tokens, hover-zoom media, pill pagination).
    – functions.php — auto-provisions a published "Journal" page (slug
      /journal/) and wires it as the Posts page (Settings → Reading) on
      activation, idempotently; jemem_journal_url() + jemem_reading_time()
      helpers added.
    – header.php / footer.php — Journal added to primary nav, mobile
      drawer, and the footer "Help" column.
  • Mockups: Blog.html (listing) + Blog Post.html (single) for preview.

1.1.3 — 2026-06-02
  • SEO: rewrote the homepage hero for on-page search signal.
    – H1 is now keyword-led: "Florist in Kuala Lumpur & Selangor"
      (was the brand-voice "Heartfelt blooms, thoughtfully arranged").
    – New `.hero__sub` services subheading: "Fresh Flower Delivery, Grand
      Opening Flowers & Condolence Stands".
    – Eyebrow now reads "Hand-crafted in Sri Damansara · Est. 2024" (adds
      neighbourhood locale instead of duplicating the H1 keyword).
    – Chinese line reinforces locale for zh search: 吉隆坡 · 雪兰莪 鲜花速递.
    – Brand voice preserved in the lede; `.hero__sub` styles added to
      main.css. hero.php + the design mockup updated together.

1.1.2 — 2026-06-02
  • Fix: FAQ questions showed TWO toggle indicators (a styled circle + a
    bare "+") on the live site. Cause: an earlier `.faq` accordion scaffold
    still in main.css targeted `.faq__item summary::after` with its own
    +/× circle, which collided with the new `.faq__icon` toggle. Removed
    the orphan scaffold (`.faq`/`.faq__item`/`summary::after`/`.faq__body`)
    so only the new toggle renders. JEMEM_VERSION bumped to cache-bust the
    stylesheet on the live install.

1.1.1 — 2026-06-02
  • Fix: /faq/ returned the 404 page after updating theme files over an
    already-active install — `after_switch_theme` only fires on a real
    theme switch, so the FAQ page was never auto-created. The provisioner
    now ALSO runs on `admin_init` (idempotent; reuses an existing /faq/
    page, never duplicates) and flushes rewrite rules on creation, so the
    page self-heals on the next wp-admin load. Manual alternative: create a
    Page titled "FAQ" — page-faq.php applies automatically via the template
    hierarchy (slug = faq).

1.1.0 — 2026-06-02
  • NEW: FAQ page — bilingual (EN + 中文) accordion with FAQPage JSON-LD
    structured data baked in, plugin-independent (no Yoast / Rank Math /
    AIOSEO needed for the schema). Grouped Q&A: Delivery & areas, Ordering
    & custom, Payment, Our flowers, and Visit & contact (studio address,
    Mon–Fri 10:00–19:00 working hours with a WhatsApp-anytime note, and the
    JE MEMORY FLORIST (RA0115928-V) registration).
    – template-parts/faq.php — SINGLE SOURCE OF TRUTH: one $faq_groups array
      drives BOTH the visible accordion AND the JSON-LD, so they can never
      drift apart (Google requires schema to match visible content). NAP /
      hours / reg-no pull from the Customizer fields.
    – page-faq.php — "FAQ Page" template.
    – functions.php — auto-provisions a published "FAQ" page (slug /faq/)
      assigned the FAQ template on theme activation, idempotently (records
      jemem_faq_page_id); jemem_faq_url() resolves the link for fallbacks.
    – header.php / footer.php — FAQ added to the primary nav, mobile drawer,
      and footer "Help" column fallbacks.
    – assets/css/main.css — .faq* styles (warm tokens, native <details>
      accordion, circular +/× toggle, no JS). Answers render English then
      中文 in refined editorial type.
  • Fix: JEMEM_VERSION (1.0.64) lagged the style.css header (1.0.65) —
    realigned both to 1.1.0.

1.0.65 — 2026-05-31
  • Instagram "Follow our garden" — now a LIVE feed via Smash Balloon.
    – template-parts/instagram.php: auto-detects the plugin
      (`shortcode_exists('instagram-feed')`). When active, renders the
      live `[instagram-feed feed=1]` shortcode (Smash Balloon v6+ requires
      a feed created in WP Admin → Instagram Feed; change `feed=` if your
      Feed ID differs); falls back to the manual `jemem_ig_urls` grid
      (then the demo set) when the plugin is off, so the section never
      renders empty.
    – assets/css/main.css: added `.ig--live` rules that override Smash
      Balloon's output into our 3×2 square grid (4px gutter, cognac hover
      wash) and hide the plugin's own header / follow button / load-more —
      our section keeps its "Follow our garden." header + Follow button.
    – Behaviour note: live tiles now open the real Instagram post (plugin
      default), so the custom `#qv` lightbox no longer fires on the live
      feed (it still works on the static fallback). Intended.
    – README: added Smash Balloon connect/setup steps.

1.0.64 — 2026-05-31
  • WooCommerce 10.8.x template audit — cleared every "out of date /
    version header missing" warning under WooCommerce → Status →
    Templates. Added/corrected the `@version` docblock tag on all 10
    WC template overrides to match the current core template versions
    shipped with WC 10.8.1:
        archive-product.php ......... 8.6.0
        content-product.php ......... 9.4.0
        content-single-product.php .. 3.6.0  (was 1.0.19)
        single-product.php .......... 1.6.4
        single-product/related.php .. 10.3.0 (was 1.0.16)
        cart/cart.php ............... 10.8.0
        cart/cart-empty.php ......... 7.0.1
        checkout/form-checkout.php .. 9.4.0
        checkout/payment.php ........ 9.8.0
        checkout/review-order.php ... 5.2.0
    No markup/behaviour changes — these are hook-faithful layout
    overrides; only the version headers were stale.
  • cart/cart-empty.php — restored the `woocommerce_cart_is_empty`
    action hook (core fires it here) so empty-cart plugins still run.
  • Note: the "theme has a woocommerce.php" notice is expected — our
    woocommerce.php intentionally wraps + includes archive-product.php.
    It is informational, not an error, and is left as-is by design.

1.0.63 — 2026-05-26
  • Mobile header — still had a gap above it on 1.0.62. `position: sticky`
    was getting killed by an ancestor with non-visible overflow (iOS
    Safari can trip even on `overflow-x: clip`). Rather than chase the
    offending rule, switched the mobile header (≤720px) to plain
    `position: fixed; top: 0` — works regardless of ancestor overflow.
    – Padded `<body>` by 64px to compensate for the now out-of-flow
      header (110px when the WP admin bar is present, so header sits
      below it).
    – Topbar hidden on mobile (the "3-day delivery / KL & Selangor"
      info is duplicated in the hero / breadcrumb anyway and the
      compact mobile chrome looks cleaner without it).
    – Dropped the `html { overflow-x: clip }` rule from 1.0.62 — was
      partially what tripped the sticky behaviour. The grid-level
      `min-width: 0` + `box-sizing: border-box` from 1.0.62 already
      contains the horizontal overflow on its own.

1.0.62 — 2026-05-26
  • Mobile shop page — sticky header + right-column overflow.
    – Right column of `.shop-grid` was clipping past the viewport
      edge. `<li.product>` had `min-width: auto` (the grid-item
      default) so wide intrinsic content (image, long product name)
      could widen its column past 1fr and overflow horizontally.
      Forced `min-width: 0; max-width: 100%` on cards + explicit
      `box-sizing: border-box; width: 100%` on the grid container
      so the gutter padding stays inside the viewport.
    – Sticky header was rendering mid-page on mobile. Root cause:
      the horizontal overflow above triggered a parent
      `overflow-x: hidden` rule (from a plugin / device viewport
      handling) — `overflow: hidden` kills `position: sticky` on
      descendants. Switched `html` + `body` to `overflow-x: clip`,
      which clamps overflow WITHOUT creating a containing block,
      so sticky resolves against the viewport again.
    – Re-asserted `.header { position: sticky; top: 0 }` with
      `!important` under `@media (max-width: 720px)` to defeat any
      plugin CSS flipping it to `static`/`relative` on mobile.

1.0.61 — 2026-05-26
  • Catalog mode confirmed working end-to-end (shop, category, PDP,
    admin-toggle in sync). Removed the admin-only debug marker +
    YITH options dump from the page source.

1.0.60 — 2026-05-26
  • Catalog mode: detection rewritten to match this install's actual
    YITH option layout, confirmed by the 1.0.59 debug dump.
    – `yith-loaded: no` showed the class-existence gate was blocking
      everything; this YITH version loads under a different class
      name than the ones we checked. Removed the class-existence
      gate and switched to option-presence as the install signal.
    – Active YITH options on this install:
        ywctm_admin_view = yes/no
        ywctm_disable_shop = yes
        ywctm_hide_variations = yes
        ywctm_hide_add_to_cart_settings = [action=hide, where=all, items=all]
      So the real "catalog mode is on" signal is
      `ywctm_hide_add_to_cart_settings.action === 'hide'`, and the
      "apply to admins" toggle is `ywctm_admin_view`.
    – New logic:
        Non-admin → hide as soon as YITH is in hide mode.
        Admin     → hide only when `ywctm_admin_view = yes`.
    – Kept the singleton API check as a fallback for newer YITH
      builds that don't write the legacy option keys.

1.0.59 — 2026-05-26
  • Catalog mode: PDP still OFF on 1.0.58 — root cause was the
    admin-exclusion check defaulting to "exclude admins" when YITH's
    option key wasn't found, AND the global on/off flag may live
    under a different option name in this YITH version.
    – Removed the admin-exclusion logic entirely. If catalog mode is
      ON globally, our body class is added for everyone. (YITH itself
      still respects its own admin-exclusion setting for its own
      button hiding; ours just kicks in on top of that.)
    – Now tries five YITH option-name variants in order:
      `ywctm_enable_plugin`, `ywctm_general_enable_plugin`,
      `ywctm_general_enable`, `ywctm_enable_catalog_mode`,
      `ywctm_catalog_mode_enabled`. First `yes` value wins.
    – Debug marker beefed up — now also lists `yith-loaded: yes/no`
      and dumps every `ywctm_*` / `*catalog_mode*` option from the DB
      directly into the page source. View-source on any page to see
      the real option names + values YITH is writing on this install.
      That tells us conclusively which option to look at if the
      shipped list still misses.

1.0.58 — 2026-05-26
  • Catalog mode: PDP detection mismatch fix.
    The debug marker confirmed `jemem-catalog-mode: ON` on SHOP but
    `OFF` on PDP — YITH's `is_catalog_mode_enabled()` /
    `check_user_admin_enable()` methods are CONTEXT-AWARE and return
    different values per page type, so our body class only landed
    on the shop. Switched detection to read YITH's global on/off
    flag directly:
    – `get_option('ywctm_enable_plugin') === 'yes'` (gated on YITH
      class existence so a stale option from an uninstalled YITH
      version can't trigger us).
    – Respects `ywctm_exclude_admin_view` for admin users — admins
      stay in catalog mode only when YITH is set to "apply to
      administrators".
    Result: detection is now identical on shop, category, and PDP.

1.0.57 — 2026-05-26
  • Catalog mode debug + harder PDP sweep:
    – New admin-only debug marker in the footer:
      `<!-- jemem-catalog-mode: ON|OFF | context: PDP|SHOP|OTHER -->`.
      View-source on shop and PDP — if values differ, detection is
      mismatched (likely YITH per-page setting); if values match but
      button still shows, it's CSS/JS specificity or a cached page.
    – JS sweep now injects its own `<style data-jemem>` block at the
      top of `<head>` and force-sets `visibility:hidden` AND
      `display:none` inline on every buy-button. Also matches by
      `name="add-to-cart"` attribute (variation forms, AJAX-rendered
      buttons).

1.0.56 — 2026-05-26
  • Catalog mode: PDP Add-to-bag still showed for admins even when
    YITH's "apply to administrators" was on and shop/category hid
    correctly. Likely cause: the live PDP renders the form through a
    page-builder widget / parent-theme template whose container class
    our CSS sweep doesn't target — or inline styles trump our
    `display:none`.
    – New JS enforcement layer in `main.js` (Section 9). When
      `body.jemem-catalog-mode` is present, sweeps the DOM for any
      buy-button selector (loop links, `.single_add_to_cart_button`,
      `form.cart`, `.quantity`, variation/grouped forms, sticky bars,
      Buy-Now buttons, Elementor / WPBakery add-to-cart widgets) and
      sets `display:none !important` inline. MutationObserver
      re-sweeps after every DOM change so AJAX add-to-cart fragments
      and variation-form re-renders don't bring the button back.

1.0.55 — 2026-05-26
  • Catalog mode "stuck on" regression. After disabling YITH catalog
    mode in admin, Add to bag still didn't return on shop / category /
    PDP. Root cause: the filter-callback scan introduced in 1.0.53
    matched YITH callbacks (`ywctm_*`) that stay registered in
    `$wp_filter` even when the feature is disabled — so
    `jemem_is_catalog_mode()` returned true forever.
    – Removed the `$wp_filter` scan.
    – Removed the loose option-name fallback list — those flags can
      stick "yes" after disabling and produced the same false-positive.
    – `jemem_is_catalog_mode()` now only trusts:
        1. Customizer toggle (`theme_mod('jemem_catalog_mode')`)
        2. `JEMEM_CATALOG_MODE` constant
        3. YITH's official `is_catalog_mode_enabled()` /
           `check_user_admin_enable()` API on the
           `YITH_WC_Catalog_Mode[_Premium]` singleton — this flips
           in lockstep with YITH's admin switch.
        4. `jemem_catalog_mode` filter (final override).
    – Anything else? Use the Customizer checkbox to force on or off.

1.0.54 — 2026-05-26
  • PDP Sale badge regression fix. 1.0.52 stripped WC's
    `woocommerce_show_product_sale_flash` action AND hid `.onsale`
    site-wide — when the live PDP isn't using our `single-product.php`
    override (parent-theme, page-builder, or plugin template), the
    sale pill disappeared entirely.
    – Restored the WC action (no more `remove_action`).
    – `main.css` now styles ANY `.onsale` on body.single-product as a
      terracotta pill with `position: absolute; top: 16px; left: 16px;`
      and forces every plausible gallery wrapper
      (`.pdp__gallery-main`, `.woocommerce-product-gallery`,
      `.woocommerce-product-gallery__wrapper`) to `position: relative`
      so the absolute anchor is the image rect.
    – `main.js` relocator: on PDP load, finds any stray `.onsale` and
      moves it INTO the closest gallery wrapper if it landed outside.
      Idempotent, also fires `position: relative` defensively. Covers
      every page-builder / child-theme variant we don't directly
      control.

1.0.53 — 2026-05-26
  • Catalog mode: PDP Add-to-bag button kept showing through. Three
    fixes layered:
    1. Customizer toggle "Catalog mode — hide Add to bag everywhere"
       under JE Memory → Brand & Contact. Sets
       `theme_mod('jemem_catalog_mode')` → guaranteed manual override
       when plugin auto-detection misses.
    2. `jemem_is_catalog_mode()` detection broadened:
       – Checks 8 YITH option-name variants
         (`ywctm_enable_plugin`, `ywctm_general_enable`,
         `ywctm_catalog_mode_enabled`,
         `ywctm_hide_add_to_cart_loop`/`_single`/`_header`,
         `ywctm_enable_catalog_mode`, `yith_wcctm_enable`).
       – New filter-callback scan — inspects `$wp_filter` for any
         callback on `woocommerce_loop_add_to_cart_link` or
         `woocommerce_is_purchasable` whose name matches
         `/ywctm|yith.*catalog|catalog_?mode|catalog_visibility|elex.*catalog/i`.
         Bulletproof catch-all: catalog-mode plugins all hook one of
         those two filters, so we don't have to know each plugin's
         option name.
       – Per-request cache now only sticks after `wp_loaded` so early
         calls (from a plugin's `after_setup_theme`) can't poison it.
    3. `main.css` sweep expanded — now also targets
       `button.single_add_to_cart_button`, `.pdp__summary form.cart`,
       `.pdp__summary .quantity`, `.pdp__summary .pdp__label`,
       `.variations_form`, `.grouped_form`, sticky-cart bars
       (`.wc-sticky-add-to-cart`, `.single-product-sticky`,
       `.pdp__sticky-cta`), and Buy-Now/Direct-Checkout plugin
       buttons.

1.0.52 — 2026-05-26
  • PDP sale flash: pinned the badge to the product image corner
    (matches `Product.html` wireframe). Root cause: WC's default
    `<span class="onsale">Sale!</span>` was being injected outside
    `.pdp__gallery-main`, so its absolute positioning anchored to
    `<main>` instead of the image. Fix:
    – `remove_action('woocommerce_before_single_product_summary',
      'woocommerce_show_product_sale_flash', 10)` (top-level + late
      `wp` priority-100 belt-and-braces).
    – `single-product.php` renders an editorial
      `.pdp__gallery-tag--sale` pill (terracotta) inside
      `.pdp__gallery-main` when `is_on_sale()`.
    – `main.css` hides any stray `.onsale` element a third-party
      plugin re-injects, except when it's nested inside
      `.pdp__gallery-main` (where it'd render correctly).
  • Mobile PDP: tighter sale-tag positioning (12px inset, 9px label)
    and a defensive `position: relative` on `.pdp__gallery-main` so
    the badge always anchors to the image rect.

1.0.51 — 2026-05-26
  • Catalog mode: extend the hide-Add-to-bag logic to the PDP and to
    any third-party UI we can't reach in PHP. New `jemem-catalog-mode`
    body class (set via `body_class` filter when
    `jemem_is_catalog_mode()` returns true). `main.css` sweeps:
    `.product__cart-wrap`, `li.product > a.button.add_to_cart_button`,
    `form.cart`, `.summary .quantity`, `.summary .pdp__label`,
    `.woocommerce-variation-add-to-cart`, `.yith-wcqv-button`,
    `.ywctm-button`, mini-cart buttons, the WC block cart/checkout
    blocks, and the header cart icon (`.icon-btn--cart` /
    `[data-cart-count]`) so visitors don't land on an empty cart.

1.0.50 — 2026-05-26
  • Catalog mode: new `jemem_is_catalog_mode()` helper. Detects YITH
    WooCommerce Catalog Mode (free + premium — checks both the
    `YITH_WC_Catalog_Mode[_Premium]` singletons and the
    `ywctm_enable_plugin` / `ywctm_hide_add_to_cart_loop` /
    `ywctm_hide_add_to_cart_single` options), plus a theme-level
    Customizer toggle (`jemem_catalog_mode`) and a `JEMEM_CATALOG_MODE`
    constant; ends with a `jemem_catalog_mode` filter for final override.
  • When catalog mode is on:
    – `.product__cart-wrap` is not rendered in `jemem_card_body()`, so
      shop + category cards show only title / price / View.
    – `woocommerce_loop_add_to_cart_link` is filtered to empty so any
      third-party loop (related products, upsells, page-builder grids)
      that calls the WC helper directly also drops its button.
    – The PDP "Options & quantity" block (label + qty + Add-to-bag) is
      skipped in `single-product.php`; `woocommerce_template_single_add_to_cart`
      is also detached from `woocommerce_single_product_summary` as
      belt-and-braces.
  • Category hero: refreshed `cat-hero__media` fallback imagery to
    bouquet-shop shots (florist counter / hand-tied bouquets) and added
    `opening` + `dried` slug aliases.

1.0.49 — 2026-05-26
  • Shop / Category: bouquet-themed hero fallback for `cat-hero__media`
    — term thumbnail still wins; otherwise a slug-matched bouquet shot
    (wedding / sympathy / grand-opening / birthday / anniversary /
    roses / preserved) is used, with a generic hand-tied bouquet
    fallback.
  • Shop "All": resilient shop URL (falls back to the product
    post-type archive when the WC shop page slug is empty) +
    in-template `WP_Query` safety net so the shop archive always shows
    products even if the configured shop page returns nothing.
  • Toolbar: "Sort by" label + WC ordering select now share one
    baseline (line-height + margin reset on the `.woocommerce-ordering`
    form, padding/height normalised on the select).
  • Product card: removed the `.product__quickview` hover button, its
    AJAX endpoint, the JS click handler, and the CSS — cards now link
    straight to the PDP. The `#qv` modal mount stays (still used by
    the Instagram lightbox).

1.0.27 — 2026-05-20
  • Cart: full template overhaul to match `Cart.html` 1:1.
    – Added Home / Your Bag breadcrumb above the hero.
    – Quantity widget wrapped in the prototype's pill with ± buttons;
      WC's number input restyled (borderless, display-font, centered)
      so the three pieces read as one continuous control. A 700ms
      debounced auto-submit fires `update_cart` after any ± click or
      typed change so users don't have to press "Update cart".
    – Order summary gains the always-on "Card & signage · Included"
      row and a "FREE" delivery fallback when WC has no shipping
      methods configured.
    – Promo placeholder now reads "Promo code (e.g. BLOOM10)".
    – Added the 4th trust badge: "Florist-arranged · 5-day freshness".
    – Footer row: single "Keep shopping" link on the left; Update
      cart demoted to a discreet small-caps ghost button on the
      right (auto-disables while debounced submit pending).
    – New "You may also love" cross-sell rail below the cart.
      Pulls WC cross-sells when tagged; otherwise falls back to the
      four most recent published products, excluding cart items.
      Renders with the same `.product` card markup as the rest of
      the shop so `section.related` styling kicks in for free.
    – Phone + WhatsApp now read from Customizer (`jemem_get`).
    – All WC hooks + filters preserved (cart_item_visible,
      cart_item_permalink, cart_item_subtotal, cart_item_class,
      cart_item_product*, before/after_cart_table, etc).
  • Chore: bump JEMEM_VERSION → 1.0.27.

1.0.26 — 2026-05-20
  • WhatsApp FAB icon: swapped the simplified house-style glyph for
    the official WhatsApp brand path (speech-bubble outline with
    phone receiver inside) so the "Chat with us" button reads
    instantly as WhatsApp at any size. White-on-brand-green styling
    via `currentColor` is unchanged — only `jemem_wa_svg()`'s SVG
    path data was updated.
  • Chore: bump JEMEM_VERSION → 1.0.26.

1.0.25 — 2026-05-20
  • Loop cards: bulletproof fix for the double Add-to-bag button.
    1.0.23 and 1.0.24 removed WC's default
    `woocommerce_template_loop_add_to_cart` hook on
    `woocommerce_after_shop_loop_item` — but on the live site a
    plugin (or a WC core re-registration we couldn't see in dev) was
    putting the default hook back in time for the archive to render,
    producing a manual button + a default button = two pills per
    card. Switched `woocommerce/content-product.php` to call our
    render functions (`jemem_card_media()` + `jemem_card_body()`)
    directly instead of firing the WC `do_action` chain. With the
    action chain bypassed nothing else can sneak a duplicate button
    into the card, no matter what's hooked.
  • PDP "You may also love": the existing `is_product()` guard inside
    `jemem_card_body()` continues to skip the button — and with the
    action chain gone, no rogue plugin hook can render one either.
  • Chore: bump JEMEM_VERSION → 1.0.25.

1.0.24 — 2026-05-20
  • Shop / category loops: 1.0.23 only removed WC's default loop
    add-to-cart hook on `wp` priority 100; the cart button was still
    rendering before that, producing TWO buttons per card on the shop
    archive (the manual one inside `.product__body` + WC's auto one
    after the body). Added a top-level `remove_action` for
    `woocommerce_after_shop_loop_item` →
    `woocommerce_template_loop_add_to_cart` so the default is gone
    immediately when functions.php loads. Kept the `wp` priority-100
    removal as belt-and-braces against plugins that re-attach the
    hook later.
  • PDP "You may also love": don't render the Add-to-bag button on
    cards inside the related-products rail. Wrapped the
    `.product__cart-wrap` output in `if ( ! is_product() )` so cards
    only show the View link — cards in this rail are a discovery
    bridge, not a quick-buy strip.
  • Chore: bump JEMEM_VERSION → 1.0.24.

1.0.23 — 2026-05-20
  • Loop: "Add to bag" button was missing from the "You may also
    love" related-products section on the PDP. Root cause: WC's
    `woocommerce_template_loop_add_to_cart` fires on
    `woocommerce_after_shop_loop_item` and silently no-ops in the
    related-products loop path because `wc_setup_loop()` isn't called
    there — the action runs but the loop-context check inside the
    callback short-circuits.
  • Fix: render the button MANUALLY inside `jemem_card_body()`,
    wrapped in `.product__cart-wrap` so it sits inside `.product__body`
    with the title + price (matches the static
    `Shop.html` / `Category.html` `.product__cart` placement). Default
    hook is removed at `wp` priority 100 to override any late re-add
    from plugins, so no duplicate button.
  • Chore: bump JEMEM_VERSION → 1.0.23.

1.0.22 — 2026-05-20
  • PDP: register two new product tabs alongside Description —
    "What's included" and "Care & delivery" — matching the
    three-tab editorial rail in `Product.html`. Each is wired to a
    per-product meta field (`_jemem_included`, `_jemem_care`) edited
    from a new "PDP tabs" meta box on the Product edit screen; empty
    fields fall back to studio defaults. WC's built-in
    `additional_information` and `reviews` tabs are unset to keep the
    rail to three editorial tabs.
  • Existing `.panel h4` + `.panel ul ` styles (✿ terracotta bullet,
    Cormorant display H4 at 22px) already cover the new tabs — no
    additional CSS needed.
  • Chore: bump JEMEM_VERSION → 1.0.22.

1.0.21 — 2026-05-20
  • PDP: fix the description tab rail rendering narrow + centred on
    the live preview when it should align flush left-edge to
    right-edge with the "You may also love" section below it.
    `.pdp__tabs` (the wrapper our single-product.php emits) already
    provides max-width: var(--container) + gutter padding — but the
    inner `body.single-product .woocommerce-tabs` rule was stacking
    ANOTHER max-width + margin:auto + padding-top + border-top on top
    of that, which (combined with WC core's own widths) shrank the
    rail to a centred strip. Reset the inner rule to width:100%,
    max-width:none, margin:0, padding:0, border:0 so it sits flush
    inside the parent. Also dropped `max-width: 720px` from the
    `.panel` so the description body reads at the same width as the
    rail above it and the related-products grid below.
  • Chore: bump JEMEM_VERSION → 1.0.21.

1.0.20 — 2026-05-20
  • PDP: suppress WooCommerce's auto-rendered `<h2>Description</h2>`
    inside the description tab panel. WC core inserts that heading
    on top of the tab label we already render in the rail above, so
    the live PDP showed the word "Description" twice — once as the
    tab and again as a serif heading inside the panel — diverging
    from the `Product.html` mock which has only the tab label.
    Filter `woocommerce_product_description_heading` returns false
    (also did the same for additional_information for consistency).
  • Chore: bump JEMEM_VERSION → 1.0.20.

1.0.19 — 2026-05-19
  • PDP: STRUCTURAL fix for related-products full-bleed. The CSS-only
    attempts in 1.0.17/1.0.18 were fighting WC's default
    `content-single-product.php`, which wraps EVERYTHING (gallery,
    summary, tabs AND the related-products call) inside one
    `<div class=“product”>` that our main.css styles as a 2-column
    CSS grid. Trying to escape that grid cell with transforms left
    the section visually offset (cards cut off on the right, etc).
    Fix: new WC template override at
    `woocommerce/content-single-product.php` that mirrors WC core's
    9.0 layout but `remove_action`'s the related-products hook from
    inside the wrapper and calls `woocommerce_output_related_products()`
    AFTER `</div>` as a top-level sibling. `<section class=“related”>`
    is now a direct child of `<main>` and full-bleeds cleanly.
  • main.css: simplified `section.related` full-bleed back to the
    classic `width: 100vw; margin-left: calc(50% - 50vw)` pattern.
    Removed the grid-column-spanning + translateX hacks introduced
    in 1.0.18 — the new template structure makes them unnecessary.
  • Chore: bump JEMEM_VERSION → 1.0.19.

1.0.18 — 2026-05-19
  • PDP: fix related products section being squashed to the LEFT
    half of the page after the full-bleed work in 1.0.17. Real root
    cause: the WC wrapper `body.single-product div.product` is
    `display: grid; grid-template-columns: 1.05fr 1fr` at desktop
    (so gallery + summary sit side-by-side). A child <section>
    inherits one grid cell — the gallery column — which is why
    the related section was showing only on the left half.
    Fix in main.css:
      • `grid-column: 1 / -1 !important` so `section.related` spans
        the whole grid first.
      • Swapped the `calc(50% - 50vw)` margins for the more reliable
        `width: 100vw; margin-left: 50%; transform: translateX(-50%)`
        full-bleed pattern — `50%` here resolves against the now-
        full-grid section rather than a single cell, and the
        transform centres the 100vw band on the section's centre.
  • Chore: bump JEMEM_VERSION → 1.0.18.

1.0.17 — 2026-05-19
  • PDP: Related products section is now truly FULL-BLEED on the
    live site (left edge → right edge of viewport). Root cause:
    WooCommerce's default `content-single-product.php` template
    wraps everything (including the related-products call) inside
    `<div class=“product”>`, which has gutter padding — so any child
    section was capped at the inner column width, regardless of what
    we did in `single-product.php` or `related.php`.
    Fix in main.css: `section.related` now uses the standard
    full-bleed trick — `margin-left: calc(50% - 50vw)` /
    `margin-right: calc(50% - 50vw)` — which pushes the edges out
    to ±50vw regardless of nesting depth.
  • main.css: added `body.single-product { overflow-x: hidden; }` so
    the full-bleed trick can't cause a 1px horizontal scrollbar from
    fractional sub-pixel rounding.
  • Chore: bump JEMEM_VERSION → 1.0.17.

1.0.16 — 2026-05-19
  • PDP: “Related products” section now matches `Product.html`
    exactly. New template override at
    `woocommerce/single-product/related.php` emits the editorial
    markup:
      <section class=“related”>
        <div class=“wrap”>
          <div class=“featured__head”>
            <span class=“eyebrow”>Pairs beautifully</span>
            <h2 class=“display”>You may also love</h2>
            <a class=“link” href=“/product-category/…”>
              View all <category>
            </a>
          </div>
          <ul class=“products”>…</ul>
        </div>
      </section>
    The “View all <category>” CTA auto-resolves to the current
    product’s primary product_cat term archive (falls back to /shop/).
    Heading text is filterable via the standard
    `woocommerce_product_related_products_heading` filter; eyebrow
    via `jemem_related_products_eyebrow`.
  • PDP: related section moved OUT of `<section class=“pdp”>` in
    `woocommerce/single-product.php` and is now a sibling, so its
    ivory background bleeds left-edge → right-edge of the viewport
    instead of being trapped inside `.pdp`'s page-wrap padding.
  • main.css: `.related` container rules updated to accept the new
    `> .wrap` child (was direct-child `> h2`, `> ul.products` only).
    Old default-WC markup still styled as a fallback. Grid template
    columns selector relaxed from `>` to descendant so the loop
    works whether wrapped or not.
  • Chore: bump JEMEM_VERSION → 1.0.16.

1.0.15 — 2026-05-19
  • Shop archive: cards in the same row now keep their NATURAL
    heights — a long 2-line product name no longer forces shorter
    neighbours to grow empty space below the price row. Added
    `align-items: start` to the `.shop-grid` / `.woocommerce
    ul.products.shop-grid` rule. Matches the screenshot the client
    sent of the live archive.
  • Shop archive: WC sale-price markup is `<del>was</del> <ins>now</ins>`
    — the old price comes BEFORE the sale price. Our `.product__price del`
    rule had `margin-left: 6px` (assuming `<del>` came after), which
    glued it to the sale price. Flipped to `margin-right: 6px` so the
    strike-through old price has breathing room from the new price.
  • PDP gallery: STOP cropping the product photo. Earlier rule forced
    `aspect-ratio: 4/5` + `object-fit: cover` on the gallery image,
    which mangled portrait-shaped flower-stand photos (visible on
    “Auspicious Bloom” etc). Replaced with `max-height: 720px` +
    `aspect-ratio: auto` + `object-fit: contain` so the preview shows
    the photo's true ratio.
  • PDP tabs: the active “Description” tab was rendering as a filled
    ink pill, centered, with a centered serif heading inside the
    panel — totally off-system. Restyled to match `Product.html`'s
    editorial tabs nav: left-aligned, 28px gap, underline-active,
    no pill background. `.panel` is now `max-width: 720px`, left-
    aligned, with `<h2>` rendered in `--display` (Cormorant Garamond)
    at clamp(28px, 3vw, 38px). Bullet lists get the ✨ terracotta
    marker.
  • Chore: bump JEMEM_VERSION → 1.0.15.

1.0.14 — 2026-05-18
  • Copy: rename WC’s “Add to cart” → “Add to bag” on every loop
    card and the single-product page, via
    `woocommerce_product_add_to_cart_text` and
    `woocommerce_product_single_add_to_cart_text` filters.
  • PDP: bring back the breadcrumb. Stock template emits
    `<nav class=“woocommerce-breadcrumb”>` — styled to match our
    `.crumbs` editorial breadcrumb.
  • PDP: sale flash repositioned + restyled — stock `<span class=“onsale”>`
    now floats top-left of the gallery as the terracotta pill
    matching `.product__tag--sale`.
  • PDP: category meta line restyled as the eyebrow chip we use on
    loop cards (“Grand Opening · 开张花篮” look) — terracotta uppercase
    label, no underline.
  • PDP: gallery hero capped at `aspect-ratio: 4/5` and `max-height
    720px` from ≥720px viewports so portrait product photos don’t
    stretch the page height by 1500px.
  • Chore: bump JEMEM_VERSION → 1.0.14.

1.0.13 — 2026-05-18
  • Fix: PDP on the live site was clearly using WC’s STOCK
    single-product template (purple Add to cart button, plain price,
    image with no rounded corners, default tab chrome) — our
    `single-product.php` override either wasn’t uploaded or wasn’t
    being picked up. Added comprehensive defensive CSS that styles
    every native WC PDP selector so the page matches the theme even
    without the template override:
      – `div.product` becomes the page wrap with proper gutter padding
        and a responsive 2-column grid at ≥960px.
      – `.woocommerce-product-gallery__image img` gets the same
        `border-radius: var(--r)` as our card thumbnails, plus an
        ivory frame; the magnifier trigger is restyled as a cream
        circular icon button.
      – `.product_title` uses display serif at clamp size.
      – `.price` renders in display serif, ink color; `<ins>` (sale
        price) tints terracotta; `<del>` (was-price) becomes a 0.65em
        ink-50 strikethrough.
      – Short description gets the same hairline top/bottom rules as
        our prototype.
      – `.quantity input.qty` becomes a cream pill with hidden number
        spinners.
      – `.single_add_to_cart_button` overrides the WC default purple
        with the theme ink pill (terracotta on hover).
      – `.product_meta` becomes a meta line with cognac links.
      – `.woocommerce-tabs ul.tabs` chips: ink-pill on active, ivory
        on hover, no WC border ::before/::after pseudo borders.
      • Empty first cell: added a CSS last-resort — `ul.products >
    li.product:first-of-type { grid-column-start: 1 !important; }` —
    forcing the first real product card into column 1 regardless of
    whatever element a plugin is parking in the slot before it. Sweeps
    the layout clean on every install.
  • Chore: bump JEMEM_VERSION → 1.0.13.

1.0.12 — 2026-05-18
  • Fix: PDP content still bled to viewport edge on the live site even
    with !important gutter padding. Bumped specificity by chaining
    `body.single-product .pdp__inner` and added a defensive fallback
    for WC’s native `div.product` wrapper in case the
    `single-product.php` template override isn’t loaded on the install.
  • Fix: related-products grid still showed an empty first cell. Added
    a modern `:has()` CSS sweep — `ul.products > li.product:not(:has(
    .product__media)):not(:has(a[href]))` hides any LI that’s tagged as
    a product but has no real link/media inside. Strengthened the JS
    sweep to also run on `load` and watch every `ul.products` via
    MutationObserver so late-inserted children get caught.
  • Chore: bump JEMEM_VERSION → 1.0.12.

1.0.11 — 2026-05-18
  • Fix: header cart hover state didn’t cover the full button — the
    cart-count number rendered as inline text beneath the SVG, stretching
    the icon button into a tall oval and leaving the hover circle
    looking clipped. Repositioned `.icon-btn__count` as an absolute
    terracotta badge in the top-right corner of the cart `.icon-btn`
    (with a cream stroke so it reads against any background), and made
    `.icon-btn` `position: relative` to anchor it. The hover circle now
    cleanly covers the whole 40×40 icon button.
  • Chore: bump JEMEM_VERSION → 1.0.11.

1.0.10 — 2026-05-18
  • Design: WooCommerce notice banners (“X has been added to your
    cart.”, cart errors, info messages) now match the editorial florist
    palette — cream card, cognac left accent, status glyph in a circular
    badge (✓ / i / !) and product names rendered in display serif italic
    for warmth. The “View cart” / “Continue shopping” link uses the same
    ink pill button as the loop add-to-cart, with a trailing “→”.
  • Variants: `.woocommerce-message` (cognac), `.woocommerce-info`
    (terracotta), `.woocommerce-error` (cognac-red on a tinted bg).
  • Chore: bump JEMEM_VERSION → 1.0.10.

1.0.9 — 2026-05-18
  • Fix: “Add to cart” button was duplicating on each product card on the
    live site — 1.0.3 had us removing WC’s default loop add-to-cart hook
    AND calling `woocommerce_template_loop_add_to_cart()` manually inside
    `jemem_card_body()`. If a plugin (or theme parent) re-attached the
    default hook, both fired. Reverted to the pure WC pattern: the
    button is now emitted exclusively by
    `woocommerce_template_loop_add_to_cart` on
    `woocommerce_after_shop_loop_item` (default priority 10). Our
    `jemem_card_body()` no longer outputs the button itself.
  • Style: the native WC link `<a class="button add_to_cart_button">`
    is now a direct child of `<li class="product">`; updated selectors
    in `main.css` to style it (and the AJAX “View cart” follow-up,
    `.loading` and `.added` states) with the same ivory pill as before.
  • Chore: bump JEMEM_VERSION → 1.0.9.

1.0.8 — 2026-05-18
  • Fix: empty first grid slot on the shop archive still showed up on
    the production site after 1.0.6 — because the offending element
    apparently carries a `.product` class and slipped through the CSS
    `:not(.product)` filter. Added a runtime sweep in `main.js` that
    walks every `ul.products`, finds children with no media / link /
    text content (despite their class list) and hides them. CSS still
    handles the common cases; this is the belt-and-braces for plugin
    weirdness.
  • Chore: bump JEMEM_VERSION → 1.0.8.

1.0.7 — 2026-05-18
  • Fix: PDP content (gallery, summary) had no left/right padding on the
    live site. Root cause: third-party / WC core CSS was stripping our
    `.pdp__inner` page gutters at single-class specificity. Promoted the
    `padding: 0 var(--gutter)` to `!important` so the inset is preserved
    on every install.
  • Fix: “Related products” block rendered one giant card per row with
    a heading flush to the viewport edge. Root cause: WC outputs
    `<section class="related products">` directly (no `.wrap`) and our
    earlier rules only constrained `.wrap > ul.products`. The section
    now carries the full-bleed ivory background + page-wrap padding
    itself, and the inner UL is forced onto our 2/3/4-column responsive
    grid (ignoring WC's `columns-N` helper).
  • Refactor: removed the redundant inline-styled wrapper in
    `woocommerce/single-product.php` — the section is fully styled in
    `main.css` now, so the template just calls
    `woocommerce_output_related_products()` directly.
  • Chore: bump JEMEM_VERSION → 1.0.7.

1.0.6 — 2026-05-18
  • Fix: shop / category archive sometimes opened with an empty first
    cell on page 2+ (cards started in grid column 2 of the first row).
    Root cause: WooCommerce — or a third-party plugin hooking into the
    loop — injects non-product children into `ul.products` (a11y
    announcers, hidden notices, `<li class="screen-reader-text">`
    placeholders). Each one consumes a CSS-grid slot before the real
    products start. Added a defensive `ul.products > *:not(.product)…`
    rule to hide anything that isn’t a real product card or category
    tile, plus belt-and-braces selectors for empty / aria-hidden /
    screen-reader-text list items.
  • Chore: bump JEMEM_VERSION → 1.0.6.

1.0.5 — 2026-05-18
  • Fix: pagination chips were doubling up — each number rendered
    both as a small rectangular WC-default cell AND a larger oval from
    our theme. Root cause: WC core ships selectors like
    `.woocommerce nav.woocommerce-pagination ul li a` (specificity
    0,2,3) which outranked our `.woocommerce-pagination .page-numbers`
    (0,2,0), so WC's border/padding kept painting the inner rectangle.
    Mirrored WC's full selector chain (with `!important` on layout
    properties) so our pill styling is the only thing that paints.
  • Reset `<ul>` and `<li>` padding/border/float so WC's white-space:
    nowrap and 1px wrapper border don't bleed through.
  • Chore: bump JEMEM_VERSION → 1.0.5.

1.0.4 — 2026-05-18
  • Fix: shop archive + category pagination on the live site rendered
    as unstyled browser-default boxes because WooCommerce outputs
    `<nav class="woocommerce-pagination"><ul class="page-numbers">…</ul></nav>`
    — our `.pagination` CSS only targeted the static prototype markup.
    Added matching styles for `.woocommerce-pagination .page-numbers`,
    `.current`, `.dots`, `.prev` and `.next` (with chevron mask icons)
    so the live archive matches the prototype.
  • Chore: bump JEMEM_VERSION → 1.0.4.

1.0.3 — 2026-05-18
  • Feature: shop archive + category loops now render WooCommerce's
    native "Add to cart" button on every product card, styled to match
    the editorial card system (pill, ivory fill, ink-on-hover). Variable
    products and out-of-stock items use WC's default labels ("Select
    options", "Read more") through the same selectors.
  • Style: also targets the "View cart" link WC injects after a card is
    added to the bag — outlined cognac variant.
  • Chore: bump JEMEM_VERSION → 1.0.3.

1.0.2 — 2026-05-18
  • Fix: shop archive — leftmost column had no gutter against the viewport
    edge while the rest of the row sat inside the page wrap. Root cause:
    WooCommerce core CSS selector `.woocommerce ul.products` (specificity
    0,2,1) was overriding our `.shop-grid` padding + max-width + grid
    template (specificity 0,1,0), so cards fell back to WC's default
    float/flex layout and wrapped at intrinsic width. Compound selector
    `.woocommerce ul.products.shop-grid` now wins; cards stay inside the
    1280px wrap with proper `var(--gutter)` on both sides.
  • Fix: same root cause was making cards on `ul.products` (loops outside
    the archive) ignore our grid — added `display: grid !important` and
    matching reset rules under the `.woocommerce ul.products` selector.
  • Chore: bump JEMEM_VERSION → 1.0.2.

1.0.1 — 2026-05-18
  • Fix: dedicated product page (PDP) hero image — main gallery <img> now
    gets explicit `display:block` and `object-position:center` so portrait
    and landscape source files render without the broken cropping seen on
    1080×810 originals.
  • Fix: related products grid on PDP — WooCommerce outputs
    `<section class="related products">`, and our `.products` grid rule
    was inheriting onto the section itself, collapsing the heading + list
    into two skinny grid tracks. Scoped the grid to `ul.products` and
    forced `section.related.products { display: block }`.
  • Fix: shop archive cards too small — neutralised WooCommerce's default
    `float: left; width: 22.05%` on `ul.products li.product` so our CSS
    grid owns the layout and product images fill the card again.
  • Chore: bump JEMEM_VERSION → 1.0.1 (cache-buster on main.css + main.js).

1.0.0 — Initial release.
*/
